From patchwork Sat Mar 28 13:32:42 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Needham <06needhamt@gmail.com> X-Patchwork-Id: 18461 Delivered-To: andriy.gelman@gmail.com Received: by 2002:a0c:ab15:0:0:0:0:0 with SMTP id h21csp348275qvb; Sat, 28 Mar 2020 06:33:05 -0700 (PDT) X-Google-Smtp-Source: ADFU+vuNm0/AL0rbsSUFmp6psyaWNUAm6a1q7uA3iipzAP0MYIuhZyIKAgPUUAnzctIY0cchadoS X-Received: by 2002:a17:906:30c5:: with SMTP id b5mr3717361ejb.102.1585402385438; Sat, 28 Mar 2020 06:33:05 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1585402385; cv=none; d=google.com; s=arc-20160816; b=SzcP0JbPKXyyQSLnPWaz5aRMORCZGp+aSDqxd0Ib1qJMaEjQunswnwS1eY0xIj90Ex wVr7k060+o3fx9NakEZ5Uy7EG+jGQDjwgITz5JDiwBzhHO99oQ3Hfha33aoZOKJm+9Ea GEF8ptmxhsjEu2fTJWlX4u/2ycp+sBPZSU4ZpoJ37CJM2nR2QQpz5RTkFg7B6oPMsS+r 8Qa/niWQII7dDtS+vYtMUdS4tfP+Y8EsmyhipNyOx5RtKnUG3Thy6YSRyxcyd5g+qbD2 aymfRbCOYJcw0ETRL3jEqsDNZwU/fwrvuKDYuN1UdF1LnKOCKNa3vTNeqwV6cFRIwwRI C0vA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:to:message-id:date:from:in-reply-to:references :mime-version:dkim-signature:delivered-to; bh=t23hA9fQReTSAR0jwXQoMG18KTV2KcbCeA9nFjo+/aI=; b=iAWKypX5/zmbhwn6MN7Rw/e0u/BzFkH7qsG2+3IqJ1RdY0Ly+uuxBfltxLu+ZuOaCV KwRttz/MJvW3JwwoLl6oZYTjv1FHvMb8Chv6U6d8wgKr7OzellL1mfJZXc46jkGm2ieK NpHsU/tmveoyDmhowdYn1SWvT3z+crbmMUJDN1c/zT9dNuHoXdnU/5zudjF1zY6TR61j eGYKCqAFXqYXeVvDnTelEFz04E5/bxC99GltxDHWFLTNJsAVyBM1dAxEni7gKpHBpFbt uRBf/vKngLdJN9KKDaVczhNh/xCWI3NgyUsO4e6a8R1pr70JF/2qdGh4hdMGR/Z+6zft NQ7w== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=sJKdKECd; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id qw9si5311031ejb.267.2020.03.28.06.33.04; Sat, 28 Mar 2020 06:33:05 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=sJKdKECd; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 7E7B168AFD7; Sat, 28 Mar 2020 15:33:03 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-io1-f48.google.com (mail-io1-f48.google.com [209.85.166.48]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 6083C68B108 for ; Sat, 28 Mar 2020 15:32:56 +0200 (EET) Received: by mail-io1-f48.google.com with SMTP id k9so12827395iov.7 for ; Sat, 28 Mar 2020 06:32:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to; bh=KqK1Da2R2pYq+Pgp95IrJp+3LuHFSGxdt+cdzbbyn+I=; b=sJKdKECd9cv4ZUr/czEuxWYBsNaDORK91IhQgiIUa+KAa7B52rs98Jnz+VvyLvFhrs RfCYvSrIwT/Bx7Q4HyOsjGmmlKoogBCSVvX3GXK27g59kP4n2uqWz0+MACT3WfYZb6iO 2WBqurd7V25ZcympWNJYrOeVEqQUzWilKIavKzn5T0WIiOlKzcXLXyYu9yCR3yGnW0Hh Ms+zM+ogM5NiXYDWyvGeHJscwxEAJUCpitfz0NzXIooj7YOpTiI2ASEMjHSlKjPuAS7q aIXCXz/nYVCHXk4s2u/qf+koHn1JyI53fNOW26wg2JYqiLnKxTSC8Z502GNZ41nmA073 qPIw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to; bh=KqK1Da2R2pYq+Pgp95IrJp+3LuHFSGxdt+cdzbbyn+I=; b=BwBnZ4X3NblTjIw0IxW1SrPyo3gxasDnH+wqeGutuxm1Ue6+SDzOBSvID62ScANO4P VN391GI3SAc6D7ORF0BunJubIbqpyDyNi3Jfij/a/bkOQpWd8jbOmwjUCBJZng0UEiQP DimsVHP+eUAMSyMcrPGZXGEEu14ponCCP25ReXwhnHLWSVWTNS/e4blXms4UaIHiWxZH OhFF3pOpIsnqA70KUWy8c/LBiRnsJiTc7i4Pc6UW3a9lW/omuBBeUeGISuPJ/Y8oyqaJ Wgw/psqghwnLOUwAPEqodc3mUtCRjG0RgwTAzY+8hwBzlYD5j2CzURXEx9z13sGca8Zf 6c+Q== X-Gm-Message-State: ANhLgQ3H8OYM/Zu1dw5an3rxPF2a+qJKVxwk95wtjmjPaLMcrMZQapJJ 40uPfNgYKIPf9tBH69ilhHf0V4dhPXhZ3C71UDrmVgbNPig= X-Received: by 2002:a5e:c70b:: with SMTP id f11mr3083606iop.28.1585402373937; Sat, 28 Mar 2020 06:32:53 -0700 (PDT) MIME-Version: 1.0 References: <20200327175331.GV10055@michaelspb> In-Reply-To: <20200327175331.GV10055@michaelspb> From: Tom Needham <06needhamt@gmail.com> Date: Sat, 28 Mar 2020 13:32:42 +0000 Message-ID: To: FFmpeg development discussions and patches X-Content-Filtered-By: Mailman/MimeDel 2.1.20 Subject: [FFmpeg-devel] [PATCH V2] avformat: Add Dynacolor MVC Demuxer X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" X-TUID: Yendyr5NlQ1f Content-Length: 50573 Hi Michael I have fixed the build errors you mentioned by not inlining those functions so they do not get relocated. Updated patch below. From 4c17850cb2546487d789b86f511a30bdef40f817 Mon Sep 17 00:00:00 2001 From: Tom Needham <06needhamt@gmail.com> Date: Sun, 21 Jul 2019 21:11:19 +0100 Subject: [PATCH] avformat: Add Dynacolor MVC Demuxer This demuxer adds support for demuxing files in the Dynacolor format such as the sample located at: http://samples.ffmpeg.org/camera-dvr/dynacolor/dynacolor-camera-sample However some decode errors are showing on the resulting MPEG4 stream. I don't know whether this is a bug with the demuxer or the file as there is only one sample but the output results in a 1 second mp4 file that is playable in VLC media player. Signed-off-by: Tom Needham <06needhamt@gmail.com> --- Changelog | 1 + doc/general.texi | 1 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/dynacolor.c | 497 +++++++++++++++++++++++++++++++++++++++ libavformat/dynacolor.h | 276 ++++++++++++++++++++++ libavformat/version.h | 2 +- 7 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 libavformat/dynacolor.c create mode 100644 libavformat/dynacolor.h #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ diff --git a/Changelog b/Changelog index 711861bda9..79d39494c9 100644 --- a/Changelog +++ b/Changelog @@ -54,6 +54,7 @@ version : - DERF demuxer - CRI HCA decoder - CRI HCA demuxer +- Dynacolor MVC Demuxer version 4.2: diff --git a/doc/general.texi b/doc/general.texi index 752618a00b..4eb4716d87 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -452,6 +452,7 @@ library: @item DXA @tab @tab X @tab This format is used in the non-Windows version of the Feeble Files game and different game cutscenes repacked for use with ScummVM. +@item Dynacolor MVC @tab @tab X @item Electronic Arts cdata @tab @tab X @item Electronic Arts Multimedia @tab @tab X @tab Used in various EA games; files have extensions like WVE and UV2. diff --git a/libavformat/Makefile b/libavformat/Makefile index 8fd0d43721..4d1ca8b7ed 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -169,6 +169,7 @@ OBJS-$(CONFIG_DV_MUXER) += dvenc.o OBJS-$(CONFIG_DVBSUB_DEMUXER) += dvbsub.o rawdec.o OBJS-$(CONFIG_DVBTXT_DEMUXER) += dvbtxt.o rawdec.o OBJS-$(CONFIG_DXA_DEMUXER) += dxa.o +OBJS-$(CONFIG_DYNACOLOR_DEMUXER) += dynacolor.o OBJS-$(CONFIG_EA_CDATA_DEMUXER) += eacdata.o OBJS-$(CONFIG_EA_DEMUXER) += electronicarts.o OBJS-$(CONFIG_EAC3_DEMUXER) += ac3dec.o rawdec.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 39d2c352f5..50f3926b05 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -131,6 +131,7 @@ extern AVOutputFormat ff_dv_muxer; extern AVInputFormat ff_dvbsub_demuxer; extern AVInputFormat ff_dvbtxt_demuxer; extern AVInputFormat ff_dxa_demuxer; +extern AVInputFormat ff_dynacolor_demuxer; extern AVInputFormat ff_ea_demuxer; extern AVInputFormat ff_ea_cdata_demuxer; extern AVInputFormat ff_eac3_demuxer; diff --git a/libavformat/dynacolor.c b/libavformat/dynacolor.c new file mode 100644 index 0000000000..cccbbf82f2 --- /dev/null +++ b/libavformat/dynacolor.c @@ -0,0 +1,497 @@ +/* + * Dynacolor MVC Demuxer + * Copyright (c) 2020 Tom Needham + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "avformat.h" +#include "internal.h" +#include "dynacolor.h" +#include "libavutil/channel_layout.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/mathematics.h" +#include "libavutil/timecode.h" +#include "libavutil/avassert.h" + +//max total size=sizeof(tHeader)+sizeof(tSuperExtraIdx1)+PENTAMICRO_PES_HEADER_SIZE+frame size +unsigned char ff_dyna_callback_checksum(tBasicIdx *header) +{ + int i; + unsigned char chksum = 0, *pHeader; + pHeader = (unsigned char *)header; + if (*pHeader == '@' && *(pHeader + 1) == '2') { + for (i = 0; i < sizeof(tBasicIdx) - 1; ++i) { + chksum ^= *(pHeader + i); + } + } + return chksum; +} + +unsigned char ff_dyna_extra_checksum(tBasicIdx *header) +{ + int i; + unsigned char chksum = 0, *pHeader; + pHeader = (unsigned char *)header; + if (*pHeader == '@' && *(pHeader + 1) == '2') { + for (i = 0; i < sizeof(tBasicIdx) - 1; ++i) { + chksum ^= *(pHeader + i) & 0xFF; + } + } + return chksum; +} + +void ff_dyna_make_drv_header(tHeader *pHdr, int serno, unsigned int size, int pic_type) +{ + // init pHdr + memset(pHdr, 0, sizeof(tHeader)); + + // set basic header + pHdr->Basic.Header1 = '@'; + pHdr->Basic.Header2 = '2'; + pHdr->Basic.QI = (pHdr->Basic.WidthBase) ? 1 : 2; + pHdr->Basic.Type = pic_type; //frame type 1=I, 2=P,3=B + pHdr->Basic.Source = 1; //for IP dev + pHdr->Basic.Size = size; //drv header + frame + pHdr->Basic.Time = time(NULL); + pHdr->Basic.NTSC_PAL = 1; //dyna_get_video_type(&pHdr->Basic); //0=NTSC,1=PAL + pHdr->Basic.Audio = 0; //audio=1. video=0 + pHdr->Basic.ExtraEnable = 1; + pHdr->Basic.Chksum = ff_dyna_callback_checksum(&pHdr->Basic); + + // Set extra header + pHdr->Extra.Serno = serno; + if (pHdr->Basic.Motion) + pHdr->Extra.Motion[0] |= 0x01; + if (pHdr->Basic.AlarmIn) + pHdr->Extra.AlmIn[0] |= 0x01; + + pHdr->Extra.SuperExtraSize = sizeof(tSuperExtraIdx); + pHdr->Extra.Chksum = ff_dyna_extra_checksum(&pHdr->Basic); +} + +tHeader *ff_dyna_get_audio_header(tHeader *frame, unsigned int chksum, unsigned int audioserno) +{ + //audio is ms adpcm. Each package size is 32(tHeader) +2048(ms adpcm). + tHeader *Header = frame; + Header->Basic.Header1 = '@'; + Header->Basic.Header2 = '2'; + Header->Basic.Size = 2048; + Header->Basic.Time = time(NULL); + Header->Basic.Audio = 1; // 5 bit : 0:Video/1:Audio + Header->Basic.ExtraEnable = 1; + Header->Extra.Serno = audioserno++; + + for (unsigned int i = 0; i < sizeof(tBasicIdx) - 1; ++i) + chksum ^= (unsigned int)(frame + i); + + Header->Basic.Chksum = chksum; + return Header; +} + +int ff_dyna_read_file_header(AVFormatContext *ctx, AVIOContext *pb, unsigned char *pesData, tPesHeader *pes, + unsigned int *size, time_t *time, tHeader *cdata_L, unsigned int *basicIdx_H, unsigned int *basicIdx_L) +{ + int ret = 0; + unsigned int stream_format; + + av_log(ctx, AV_LOG_DEBUG, "Parsing tBasicIdx Header \n"); + + *(basicIdx_H) = avio_rl32(pb); + + cdata_L->Basic.Header1 = *(basicIdx_H)&0xFF; + cdata_L->Basic.Header2 = *(basicIdx_H) >> 8 & 0xFF; + cdata_L->Basic.reserved = *(basicIdx_H) >> 16 & 0x0F; + cdata_L->Basic.Source = *(basicIdx_H) >> 20 & 0x01; + cdata_L->Basic.WidthBase = *(basicIdx_H) >> 21 & 0x03; + cdata_L->Basic.Reserved0 = *(basicIdx_H) >> 23 & 0x01; + cdata_L->Basic.Channel = *(basicIdx_H) >> 24 & 0x02; + cdata_L->Basic.StreamType = *(basicIdx_H) >> 26 & 0x02; + cdata_L->Basic.QI = *(basicIdx_H) >> 28 & 0x0F; + + *size = avio_rl32(pb); + cdata_L->Basic.Size = *size; + + *time = (time_t)avio_rl32(pb); + cdata_L->Basic.Time = ((unsigned int) *time); + + *(basicIdx_L) = avio_rl32(pb); + + cdata_L->Basic.NTSC_PAL = *(basicIdx_L)&0x01; + cdata_L->Basic.ImgMode = *(basicIdx_L) >> 1 & 0x01; + cdata_L->Basic.Audio = *(basicIdx_L) >> 2 & 0x01; + cdata_L->Basic.DST = *(basicIdx_L) >> 3 & 0x01; + cdata_L->Basic.Covert = *(basicIdx_L) >> 4 & 0x01; + cdata_L->Basic.Vloss = *(basicIdx_L) >> 5 & 0x01; + cdata_L->Basic.AlarmIn = *(basicIdx_L) >> 6 & 0x01; + cdata_L->Basic.Motion = *(basicIdx_L) >> 7 & 0x01; + cdata_L->Basic.ExtraEnable = *(basicIdx_L) >> 8 & 0x01; + cdata_L->Basic.EventEnable = *(basicIdx_L) >> 9 & 0x01; + cdata_L->Basic.PPS = *(basicIdx_L) >> 10 & 0x40; + cdata_L->Basic.Type = *(basicIdx_L) >> 16 & 0x08; + cdata_L->Basic.Channel = *(basicIdx_L) >> 19 & 0x20; + cdata_L->Basic.Chksum = *(basicIdx_L) >> 24 & 0xFF; + + av_log(ctx, AV_LOG_DEBUG, "tBasicIdx Header: %d \n", *(basicIdx_H) | *(basicIdx_L)); + + if (cdata_L->Basic.ExtraEnable) { + // File has tExtraIdx header so parse it + unsigned int start; + unsigned char end; + char checksum; + + av_log(ctx, AV_LOG_DEBUG, "Parsing tExtraIdx Header \n"); + + start = avio_rl32(pb); + cdata_L->Extra.IsDST_S_W = start & 0x01; + cdata_L->Extra.DST_Minutes = start >> 1 & 0xFF; + cdata_L->Extra.OverSpeed = start >> 9 & 0x01; + cdata_L->Extra.SkipPicCnt = start >> 10 & 0x20; + cdata_L->Extra.Exception = start >> 15 & 0x01; + cdata_L->Extra.SuperExtraSize = start >> 16 & 0xFFFF; + + cdata_L->Extra.Serno = avio_rl32(pb); + cdata_L->Extra.AlmIn[0] = (unsigned char)avio_r8(pb); + cdata_L->Extra.AlmIn[1] = (unsigned char)avio_r8(pb); + cdata_L->Extra.Vloss[0] = (unsigned char)avio_r8(pb); + cdata_L->Extra.Vloss[1] = (unsigned char)avio_r8(pb); + cdata_L->Extra.Motion[0] = (unsigned char)avio_r8(pb); + cdata_L->Extra.Motion[1] = (unsigned char)avio_r8(pb); + + end = (unsigned char)avio_r8(pb); + cdata_L->Extra.IsDVRRec = end & 0x03; + cdata_L->Extra.TimeZone = end >> 2 & 0x40; + + checksum = (char)avio_r8(pb); + + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx SuperExtraSize %i \n", cdata_L->Extra.SuperExtraSize); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx First 4 %d \n", start); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx Alarm In %i \n", (short)(cdata_L->Extra.AlmIn[1] << 8 | cdata_L->Extra.AlmIn[0])); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx Video Loss %i \n", (short)(cdata_L->Extra.Vloss[1] << 8 | cdata_L->Extra.Vloss[0])); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx Motion %i \n", (short)(cdata_L->Extra.Motion[1] << 8 | cdata_L->Extra.Motion[0])); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx End %d \n", end); + av_log(ctx, AV_LOG_DEBUG, "tExtraIdx Checksum %d \n", checksum); + + av_log(ctx, AV_LOG_DEBUG, "Parsing tSuperExtraIdx Header \n"); + + cdata_L->SuperExtra.type = (int)avio_rl32(pb); + cdata_L->SuperExtra.remain_size = (int)avio_rl32(pb); + + av_log(ctx, AV_LOG_DEBUG, "tSuperExtraIdx type: %d \n", cdata_L->SuperExtra.type); + av_log(ctx, AV_LOG_DEBUG, "tSuperExtraIdx remain_size: %i \n", cdata_L->SuperExtra.remain_size); + + if (avio_read(pb, cdata_L->SuperExtra.title, PENTAMICRO_SUPEREXTRA_TITLE_SIZE) != PENTAMICRO_SUPEREXTRA_TITLE_SIZE) + return AVERROR_INVALIDDATA; + + if (avio_read(pb, cdata_L->SuperExtra.extra_data, PENTAMICRO_SUPEREXTRA_EXTRA_DATA_SIZE) != PENTAMICRO_SUPEREXTRA_EXTRA_DATA_SIZE) + return AVERROR_INVALIDDATA; + + av_log(ctx, AV_LOG_DEBUG, "tSuperExtraIdx title: %s \n", cdata_L->SuperExtra.title); + av_log(ctx, AV_LOG_DEBUG, "tSuperExtraIdx extra_data: %s \n", cdata_L->SuperExtra.extra_data); + + av_log(ctx, AV_LOG_DEBUG, "Successfully Parsed tSuperExtraIdx Header \n"); + } else { + // File has no tExtraIdx header so skip 16 bytes + av_log(ctx, AV_LOG_DEBUG, "Skipping tExtraIdx and tSuperExtraIdx Header \n"); + avio_skip(pb, PENTAMICRO_EXTRA_SIZE + PENTAMICRO_SUPEREXTRA_SIZE); + + memset(&cdata_L->Extra, 0xFF, sizeof(tExtraIdx)); + } + + av_log(ctx, AV_LOG_DEBUG, "Size %d \n", *size); + av_log(ctx, AV_LOG_DEBUG, "Time %ld \n", *time); + + if (avio_read(pb, pesData, PENTAMICRO_PES_HEADER_SIZE) != PENTAMICRO_PES_HEADER_SIZE) + return AVERROR_INVALIDDATA; + + // Try And Build PES Header + av_log(ctx, AV_LOG_DEBUG, "Building PES Header \n"); + ret = ff_dyna_make_pes_packet_header(ctx, pesData, cdata_L->Basic.Type, *size, 0x01, 0x00); + + if (ret) { + av_log(ctx, AV_LOG_DEBUG, "Failed to Build PES Header \n"); + return AVERROR_INVALIDDATA; + } else { + av_log(ctx, AV_LOG_DEBUG, "Successfully Built PES Header Determining Stream Format \n"); + + stream_format = ff_dyna_get_stream_format(ctx, cdata_L, size); + if (!stream_format) { + av_log(ctx, AV_LOG_WARNING, "File Has Unkown Stream Format: 0x%02X", stream_format); + return AVERROR_PATCHWELCOME; + } else if (stream_format == AVERROR_INVALIDDATA) { + av_log(ctx, AV_LOG_WARNING, "Could not Determine Stream Format \n"); + return AVERROR_INVALIDDATA; + } + + av_log(ctx, AV_LOG_DEBUG, "File Has 0x%02X Stream Format \n", stream_format); + } + + return ret; +} + +int ff_dyna_get_stream_format(AVFormatContext *ctx, tHeader *cdata_L, unsigned int *size) +{ + int ret = 0; + + // Try And Build Header for H264 Format + av_log(ctx, AV_LOG_DEBUG, "Validating H264 PES Header \n"); + ret = ff_dyna_check_pes_packet_header(ctx, cdata_L->Basic.Type, *size, 0x01, 0x00, PENTAMICRO_H264_FORMAT_PREFIX); + + if (!ret) { + // Format was not H264 so try And Build Header for MPEG4 Format + av_log(ctx, AV_LOG_DEBUG, "Validating MPEG4 PES Header \n"); + ret = ff_dyna_check_pes_packet_header(ctx, cdata_L->Basic.Type, *size, 0x01, 0x00, PENTAMICRO_MPEG4_FORMAT_PREFIX); + + if (!ret) { + // Format was not MPEG4 or H264 so try And Build Header for JPEG Format + av_log(ctx, AV_LOG_DEBUG, "Validating JPEG PES Header \n"); + ret = ff_dyna_check_pes_packet_header(ctx, cdata_L->Basic.Type, *size, 0x01, 0x00, PENTAMICRO_JPEG_FORMAT_PREFIX); + + if (!ret) { + // Format was not MPEG4 or H264 or JPEG so try And Build Header for AUDIO Format + av_log(ctx, AV_LOG_DEBUG, "Validating AUDIO PES Header \n"); + ret = ff_dyna_check_pes_packet_header(ctx, cdata_L->Basic.Type, *size, 0x01, 0x00, PENTAMICRO_AUDIO_FORMAT_PREFIX); + + if (!ret) { + return AVERROR_PATCHWELCOME; + } else { + av_log(ctx, AV_LOG_DEBUG, "Successfully Validated AUDIO PES Header \n"); + return PENTAMICRO_JPEG_FORMAT_PREFIX; + } + } else { + av_log(ctx, AV_LOG_DEBUG, "Successfully Validated JPEG PES Header \n"); + return PENTAMICRO_JPEG_FORMAT_PREFIX; + } + } else { + av_log(ctx, AV_LOG_DEBUG, "Successfully Validated MPEG4 PES Header \n"); + return PENTAMICRO_MPEG4_FORMAT_PREFIX; + } + } else { + av_log(ctx, AV_LOG_DEBUG, "Successfully Validated H264 PES Header \n"); + return PENTAMICRO_H264_FORMAT_PREFIX; + } + + return 0; +} + +int ff_dyna_check_pes_packet_header(AVFormatContext *ctx, int pic_type, + unsigned int size, long long dts, int stream_id, int format_prefix) +{ + + DynacolorContext *dyna = ctx->priv_data; + tPesHeader *pes = &dyna->pes_header; + + // the size should contain this 32-byte PES header. + size += PENTAMICRO_PES_HEADER_SIZE; + + return pes->format_id == format_prefix; +} + +int ff_dyna_make_pes_packet_header(AVFormatContext *ctx, unsigned char *pes, int pic_type, + unsigned int size, long long dts, int stream_id) +{ + DynacolorContext *dyna = ctx->priv_data; + tPesHeader *hdr = &dyna->pes_header; + + // the size should contain this 32-byte PES header. + size += PENTAMICRO_PES_HEADER_SIZE; + + hdr->start_code0 = pes[0]; + hdr->start_code1 = pes[1]; + hdr->start_code2 = pes[2]; + hdr->format_id = pes[3] & 0x0F; + hdr->channel_id = pes[3] >> 8 & 0x0F; + + hdr->unused_0 = (unsigned int)(pes[4] << 24 | pes[5] << 16 | pes[6] << 8 | pes[7]); + hdr->unused_1 = (unsigned int)(pes[8] << 24 | pes[9] << 16 | pes[10] << 8 | pes[11]); + hdr->unused_2 = (unsigned int)(pes[12] << 24 | pes[13] << 16 | pes[14] << 8 | pes[15]); + hdr->unused_3 = (unsigned int)(pes[16] << 24 | pes[17] << 16 | pes[18] << 8 | pes[19]); + hdr->unused_4 = (unsigned int)(pes[20] << 24 | pes[21] << 16 | pes[22] << 8 | pes[23]); + + hdr->size_bit7to0 = pes[24]; + hdr->size_bit10to8 = pes[25] & 0x08; + hdr->size_bit14to11 = pes[26] & 0xF; + hdr->size_bit21to15 = (unsigned int)pes[27] & 0x80; // Cast To Suppress Overflow Warning + hdr->size_marker0 = pes[28] & 0x01; + hdr->size_marker1 = pes[29] & 0x01; + hdr->picture_type = pes[30]; + hdr->is_interleaved = pes[31] & 0x01; + hdr->field_id = pes[31] >> 1 & 0x03; + + return 0; +} + +static int dyna_read_probe(const AVProbeData *p) +{ + unsigned char* bytes = av_malloc(p->buf_size); + + for (int i = 0; i < p->buf_size; i++) { + bytes[i] = p->buf[i]; + } + + if (bytes[0] == 0x40 && bytes[1] == 0x32 && bytes[2] == 0x10 && bytes[3] == 0x20) + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int dyna_read_header(AVFormatContext *ctx) +{ + int ret = 0; + DynacolorContext *priv = ctx->priv_data; + AVIOContext *pb = ctx->pb; + AVIOContext *start = NULL; + + AVStream *vstream = NULL; + AVCodec *vcodec = NULL; + AVCodecParameters *vcodec_params = NULL; + + AVStream *astream = NULL; + AVCodec *acodec = NULL; + AVCodecParameters *acodec_params = NULL; + + time_t time; + unsigned char *pesData; + tPesHeader *pes; + unsigned int size; + + unsigned int basicIdx_H, basicIdx_L; + + start = av_malloc(sizeof(AVIOContext)); + memcpy(start, pb, sizeof(AVIOContext)); + + pesData = av_malloc_array(PENTAMICRO_PES_HEADER_SIZE, 1); + pes = av_malloc(sizeof(tPesHeader)); + + ret = ff_dyna_read_file_header(ctx, pb, pesData, pes, &size, &time, + &priv->header, &basicIdx_H, &basicIdx_L); + + if (ret) + return AVERROR_INVALIDDATA; + + memset(&priv->header, 0xFF, sizeof(tHeader)); + + av_log(ctx, AV_LOG_DEBUG, "Dynacolor Header Successfully Parsed Detecting Streams... \n"); + + if (priv->pes_header.format_id == PENTAMICRO_JPEG_FORMAT_PREFIX) { + av_log(ctx, AV_LOG_DEBUG, "Demuxing JPEG Video Stream \n"); + + vcodec = avcodec_find_decoder(AV_CODEC_ID_MJPEG); + vstream = avformat_new_stream(ctx, vcodec); + vcodec_params = vstream->codecpar; + + if (!vstream || !vcodec_params) + return AVERROR_INVALIDDATA; + } else if (priv->pes_header.format_id == PENTAMICRO_MPEG4_FORMAT_PREFIX) { + av_log(ctx, AV_LOG_DEBUG, "Demuxing MPEG4 Video Stream \n"); + + vcodec = avcodec_find_decoder(AV_CODEC_ID_MPEG4); + vstream = avformat_new_stream(ctx, vcodec); + vcodec_params = vstream->codecpar; + + if (!vstream || !vcodec_params) + return AVERROR_INVALIDDATA; + } else if (priv->pes_header.format_id == PENTAMICRO_H264_FORMAT_PREFIX) { + av_log(ctx, AV_LOG_DEBUG, "Demuxing H264 Video Stream \n"); + + vcodec = avcodec_find_decoder(AV_CODEC_ID_H264); + vstream = avformat_new_stream(ctx, vcodec); + vcodec_params = vstream->codecpar; + + if (!vstream || !vcodec_params) + return AVERROR_INVALIDDATA; + } else if (priv->pes_header.format_id == PENTAMICRO_AUDIO_FORMAT_PREFIX) { + av_log(ctx, AV_LOG_DEBUG, "Demuxing Audio Stream \n"); + + acodec = avcodec_find_decoder(AV_CODEC_ID_PCM_F16LE); + astream = avformat_new_stream(ctx, acodec); + acodec_params = astream->codecpar; + + if (!astream || !acodec_params) + return AVERROR_INVALIDDATA; + } + + return 0; +} + +static int dyna_read_packet(AVFormatContext *ctx, AVPacket *pkt) +{ + int ret = 0; + DynacolorContext *priv = ctx->priv_data; + AVIOContext *pb = ctx->pb; + uint8_t* pkt_data; + unsigned int size; + + avio_read(pb, (uint8_t*)&priv->pes_header, PENTAMICRO_PES_HEADER_SIZE); + + size = (priv->pes_header.size_bit7to0 & 0xFF) + | ((priv->pes_header.size_bit10to8 & 0x04)) << 15 + | ((priv->pes_header.size_bit14to11 & 0x08) << 11) + | ((priv->pes_header.size_bit21to15 & 0x7F) << 8); + + av_log(ctx, AV_LOG_DEBUG, "Size = %i \n", size); + pkt_data = av_malloc(size + 1); + + ret = avio_read(pb, pkt_data, size); + av_log(ctx, AV_LOG_DEBUG, "first ret = %i \n", ret); + + if (ret != size) + return ret; + + ret = av_packet_from_data(pkt, pkt_data, size); + av_log(ctx, AV_LOG_DEBUG, "second ret = %i \n", ret); + + if (ret < 0) + return ret; + + return ret; +} + +static int dyna_read_close(AVFormatContext *ctx) +{ + return 0; +} + +static int dyna_read_seek(AVFormatContext *ctx, int stream_index, + int64_t timestamp, int flags) +{ + DynacolorContext *priv = ctx->priv_data; + unsigned int size = 0; + + size = (priv->pes_header.size_bit7to0 & 0xFF) + | ((priv->pes_header.size_bit10to8 & 0x04)) << 15 + | ((priv->pes_header.size_bit14to11 & 0x08) << 11) + | ((priv->pes_header.size_bit21to15 & 0x7F) << 8); + + if (avio_seek(ctx->pb, size, SEEK_SET) < 0) + return -1; + + return 0; +} + +AVInputFormat ff_dynacolor_demuxer = { + .name = "dynacolor", + .long_name = NULL_IF_CONFIG_SMALL("Dynacolor MVC"), + .priv_data_size = sizeof(DynacolorContext), + .read_probe = dyna_read_probe, + .read_header = dyna_read_header, + .read_packet = dyna_read_packet, + .read_close = dyna_read_close, + .read_seek = dyna_read_seek, + .extensions = "dyna,dyn", +}; diff --git a/libavformat/dynacolor.h b/libavformat/dynacolor.h new file mode 100644 index 0000000000..1c3837e434 --- /dev/null +++ b/libavformat/dynacolor.h @@ -0,0 +1,276 @@ +/* + * Dynacolor MVC Demuxer + * Copyright (c) 2020 Tom Needham + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_DYNACOLOR_H +#define AVFORMAT_DYNACOLOR_H + +#include "avformat.h" + +// The data structure of the streaming video: +// [16-byte tBasicIdx header] + [16-byte tExtraIdx header] + ['SuperExtraSize' bytes] + [32-byte PES header] + [video body] +// The 'SuperExtraSize' is defined in the 'tExtraIdx' structure to store the additional information, and usually equals zero. + +#define PENTAMICRO_PES_HEADER_SIZE 32 +#define PENTAMICRO_EXTRA_SIZE 16 +#define PENTAMICRO_SUPEREXTRA_SIZE 108 +#define PENTAMICRO_SUPEREXTRA_TITLE_SIZE 12 +#define PENTAMICRO_SUPEREXTRA_EXTRA_DATA_SIZE 96 + +#define PENTAMICRO_AUDIO_FORMAT_PREFIX 0x0C +#define PENTAMICRO_MPEG4_FORMAT_PREFIX 0x0D +#define PENTAMICRO_JPEG_FORMAT_PREFIX 0x0E +#define PENTAMICRO_H264_FORMAT_PREFIX 0x0F + +// Big Endian +// DG600 uses big-endian CPU + +// Basic Idx: 16 bytes +typedef struct +{ + unsigned int Header1 : 8; // [2] "@X" -- X:device type, DG600 is "6" @6 + unsigned int Header2 : 8; // [2] "@X" -- X:device type, DG600 is "6" @6 + unsigned int reserved : 4; // 4 bits + unsigned int Source : 1; // 1 bit : (0 - DVR, 1 - IP Dev) + unsigned int WidthBase : 2; // 0:720 , 1:704, 2:640 + unsigned int Reserved0 : 1; // 1 bit : (0 - Disable(backward compatibile), 1 - Enable) + // this is basically for VSS use since VSS needs to support 96 channels, and + // there is originally 5 bits only for channel number in the header. + // With these two extra bits, the channel number becomes ((Channel_Ext<<5)|Channel). + unsigned int Channel_Ext : 2; + unsigned int StreamType : 2; // 0-NormalStream, 1-VStream, 2-DualStream + unsigned int QI : 4; // 4 bits : QI (0~16) + + unsigned int Size; // [4] + unsigned int Time; // [4] + + unsigned int NTSC_PAL : 1; // 1 bit : 0:NTSC/1:PAL + unsigned int ImgMode : 1; // 1 bit : 0:Live/1:Playback + unsigned int Audio : 1; // 1 bit : 0:Video/1:Audio + unsigned int DST : 1; // 1 bit : DST + unsigned int Covert : 1; // 1 bit : Covert + unsigned int Vloss : 1; // 1 bit : Video loss + unsigned int AlarmIn : 1; // 1 bit : Alarm In + unsigned int Motion : 1; // 1 bit : Motion + + unsigned int ExtraEnable : 1; // 1 bit : 0: no extra 1: have extra + unsigned int EventEnable : 1; // 1 bit : 0: Normal status 1: Event duration + unsigned int PPS : 6; // 6 bits : Per-channel PPS Idx (0~127 level) + + unsigned int Type : 3; // 3 bits : type (0~7) 0: none 1: I-pic, 2: P-Pic, 3: B-Pic + unsigned int Channel : 5; // 5 bits : Channel No (0~31) + unsigned int Chksum : 8; // [1] + +} tBasicIdx; + +// Extra Idx: 16 bytes + +typedef struct +{ + unsigned int DST_Minutes : 8; // 0~7 = 0 ~ 120 minutes + unsigned int IsDST_S_W : 1; // 0 = summer time , 1 =winter time + unsigned int OverSpeed : 1; + unsigned int SkipPicCnt : 5; + + // use one bit to represent exception alarm + // can't get exactually exception no here + unsigned int Exception : 1; + unsigned int SuperExtraSize : 16; + + unsigned int Serno; // [4] + + // Original tEventInfo16CH was broken 'cause of the byte alignment problem + // - use the following directly + // the first bit can descript first channel's evnet status, so that it can totally script 16 channels' status. + // if IP camera, the first bit also descripts the first IP cmaera's status. + char AlmIn[2]; // 16 channel, if Source of tBasicIdx = 0, it means analog camera's status. If Source = 1, it means IP camera's. + char Vloss[2]; // 16 channel, if Source of tBasicIdx = 0, it means analog camera's status. If Source = 1, it means IP camera's. + char Motion[2]; // 16 channel, if Source of tBasicIdx = 0, it means analog camera's status. If Source = 1, it means IP camera's. + + unsigned char IsDVRRec : 2; + + // For TimeZone setting + // - 0 means OFF (backward compatibility) + // - Delta : 30 min + // - Start from : -12:00 + // - Aka. + // 1 : -12:00 (-720 min) + // 2 : -11:30 (-690 min) + // 3 : -11:00 (-660 min) + // .... + // 25 : +00:00 (+000 min) + // .... + // 41 : +08:00 (+480 min) + // 42 : +08:30 (+510 min) + // 43 : +09:00 (+540 min) + // .... + // 51 : +13:00 (+780 min) + + unsigned char TimeZone : 6; + char Chksum; + +} tExtraIdx; + +// Super Extra Index : +// type = 0 +// remain_size = 108 +// structure to store cam title & extra data(GPS/Text) + +typedef struct +{ + + // the type for tSuperExtraIdx is fixed to 0 + int type; + + // the remain_size for tSuperExtraIdx is fixed to : 12 + 96 = 108 + int remain_size; + + // CAM_TIT_LEN + char title[12]; + + // Merge original account0 ~ account3 + // - GPS & Text support will use the same place + // - REC_INDEX_EXTRA_TYPE, REC_INDEX_EXTRA_CH, REC_INDEX_EXTRA_LENGTH are + // used for some specific info + // - refer to list.h for detail + + char extra_data[96]; + +} tSuperExtraIdx; + +typedef struct +{ + tBasicIdx Basic; + tExtraIdx Extra; + tSuperExtraIdx SuperExtra; +} tHeader; + +// According to tBasicIdx's Source and Audio, you can understand when to use tIPCamAudioExtraIdx. +// Source = 1 and Audio = 1 means the audio data is from IP camera. +// And Channel to know the audio data is from which one IP camera. + +typedef struct +{ + unsigned int DST_Minutes : 8; // 0~7 = 0 ~ 120 minutes + unsigned int IsDST_S_W : 1; // 0 = summer time , 1 =winter time + unsigned int OverSpeed : 1; + unsigned int SkipPicCnt : 5; + + // use one bit to represent exception alarm + // - can't get exactually exception no here + unsigned int Exception : 1; + unsigned int SuperExtraSize : 16; + + + unsigned int Serno; + // Original tEventInfo16CH was broken 'cause of the byte alignment problem + // use the following directly + // the first bit can descript first channel's evnet status, so that it can totally script 16 channels' status. + // if IP camera, the first bit also descripts the first IP cmaera's status. + + unsigned short FormatTag; // audio format tag followed MS's definition + unsigned short BitsPerSample; // bits per sample (eg. 8, 16, or 24) + unsigned short SamplesPerSec; // samples per second (eg. 8000, 16000, or 44100) + + unsigned char IsDVRRec : 2; + + // For TimeZone setting + // - 0 means OFF (backward compatibility) + // - Delta : 30 min + // - Start from : -12:00 + // - Aka. + // 1 : -12:00 (-720 min) + // 2 : -11:30 (-690 min) + // 3 : -11:00 (-660 min) + // .... + // 25 : +00:00 (+000 min) + // .... + // 41 : +08:00 (+480 min) + // 42 : +08:30 (+510 min) + // 43 : +09:00 (+540 min) + // .... + // 51 : +13:00 (+780 min) + + unsigned char TimeZone : 6; + char Chksum; + +} tIPCamAudioExtraIdx; + +typedef struct +{ + // 4 bytes + unsigned int start_code0 : 8; // must be 0x00 + unsigned int start_code1 : 8; // must be 0x00 + unsigned int start_code2 : 8; // must be 0x01 + unsigned int format_id : 4; // JPEG:0xd, MPEG4:0xe, H.264:0xf, Audio:0xc + unsigned int channel_id : 4; // channel id from 0 to 15 for CH1 to CH16 + + unsigned int unused_0; + unsigned int unused_1; + unsigned int unused_2; + unsigned int unused_3; + unsigned int unused_4; + + // size of the video body and this PES header (total 3 bytes including the markers) + unsigned int size_bit7to0 : 8; // from bit7 to bit0 + unsigned int size_bit10to8 : 3; // from bit10 to bit8 + unsigned int size_bit14to11 : 4; // from bit14 to bit11 + unsigned int size_bit21to15 : 7; // from bit21 to bit15 + unsigned int size_marker0 : 1; // must be 0x1 + unsigned int size_marker1 : 1; // must be 0x1 + + // 1 byte for the picture type + unsigned int picture_type : 8; // 1: I-slice, 2: P-slice, 3: B-slice + unsigned int is_interleaved : 1; // 0: even/odd fields are separated horizontally + // 1: even/odd fields are interleaved + unsigned int field_id : 2; // 0: odd field, 1: even field, 2/3: undefined + unsigned int unused_5 : 29; + +} tPesHeader; + +typedef struct { + tHeader header; + tPesHeader pes_header; +} DynacolorContext; + +// Function Defs + +//stream_id ane dts can be set to 0. +//size is the frame size. +//pic_type=pHdr->Basic.Type + +unsigned char ff_dyna_callback_checksum(tBasicIdx *header); +unsigned char ff_dyna_extra_checksum(tBasicIdx *header); + +int ff_dyna_check_pes_packet_header(AVFormatContext *ctx, int pic_type, + unsigned int size, long long dts, int stream_id, int format_prefix); + +int ff_dyna_make_pes_packet_header(AVFormatContext *ctx, unsigned char *pes, int pic_type, + unsigned int size, long long dts, int stream_id); + +void ff_dyna_make_drv_header(tHeader *pHdr, int serno, unsigned int size, int pic_type); + +tHeader *ff_dyna_get_audio_header(tHeader *frame, unsigned int chksum, unsigned int audioserno); + +int ff_dyna_read_file_header(AVFormatContext *ctx, AVIOContext *pb, unsigned char *pesData, tPesHeader *pes, unsigned int *size, + time_t *time, tHeader *cdata_B, unsigned int *basicIdx_H, unsigned int *basicIdx_L); +int ff_dyna_get_stream_format(AVFormatContext *ctx, tHeader *cdata_L, unsigned int *size); + +#endif diff --git a/libavformat/version.h b/libavformat/version.h index 18c2f5fec2..493a0b337f 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -32,7 +32,7 @@ // Major bumping may affect Ticket5467, 5421, 5451(compatibility with Chromium) // Also please add any ticket numbers that you believe might be affected here #define LIBAVFORMAT_VERSION_MAJOR 58 -#define LIBAVFORMAT_VERSION_MINOR 42 +#define LIBAVFORMAT_VERSION_MINOR 43 #define LIBAVFORMAT_VERSION_MICRO 100