From patchwork Fri Jul 5 20:44:57 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Tom Needham <06needhamt@gmail.com> X-Patchwork-Id: 13826 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id 6045F449D22 for ; Fri, 5 Jul 2019 23:53:39 +0300 (EEST) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 3A64868AD25; Fri, 5 Jul 2019 23:53:39 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-io1-f66.google.com (mail-io1-f66.google.com [209.85.166.66]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 0597768ACF2 for ; Fri, 5 Jul 2019 23:53:32 +0300 (EEST) Received: by mail-io1-f66.google.com with SMTP id h6so13493137iom.7 for ; Fri, 05 Jul 2019 13:53:31 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to; bh=y8gNdiexloQggOMfyiIOawozWHM2SCjxmcH2RPfMd4o=; b=cTkF98TB9ZHKym5hEK7PpJC4e5zK9yJiViLEj5hflZVO63VZ2D9Grtan/kgP0urKwZ Ln8ZIX0Ni8/QFHvqU7cqVo2FY9X01wKndtIpmDBFWHYy7oQ+SdX0/fk0ozpBGo5rMUfL NjH0+hdPZLNS8mOsamxsJ9c0sSKWveUwU42SoF33lSBB/PmphAB5ypRkKvXAcIp/VlMB L4i26G+EMwzLeGrYyhB8leHUSvfuBaECWqv0aC/F0+7edsp608JPUZloL25XHnijaEJN brzgjwxhqvyXL9RR56WgBWRm5c8l7iXqQpFzNy0psiz3k/h/umm+OnUCeuf/qAlAZZIi KUhg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to; bh=y8gNdiexloQggOMfyiIOawozWHM2SCjxmcH2RPfMd4o=; b=czxO5BqcREYTzO5YxscwSarLrGH8+KGJUDNU+LRms30aeh/XlLZ1yPJcy1ptM0+pDz x7vhz2ypG4uMTtMXuXvkH3Gt77i/NMVHzj7B0wO6VBy5cr2lpmBxhPN5lCO1K3/kyKJq 6msaY60b8POlC93k2Twx9yZZKDkDijB0Rg/hENEe+R8ok/uFoewRuWajlDFiG+wBfeci T0ebswMNrEicLiwB9MtfTjHHxYgkxUSC2PGYzERASz4r6aGzjb1v5zWr1myk0/vAXHbJ Azh2u7+hOtan0PUgx0wrwvL/+wGm3ZGXcLRnn3mKGCTVUsIkXOa3+P8QBvucwqrkp8Mi cm+Q== X-Gm-Message-State: APjAAAXrlOAQ7uDmAaMFB3p3tYzeOmT9LEuBgOzHsbusHK/H5iWEkgyL /1iI4XjmHYnQ/yj47wKOHTCkpmJc0LHGVeTc4OLoCMYoTVk= X-Google-Smtp-Source: APXvYqw2rY951cwXle+xULqCk6/MEcvJht75oza4U152vheCqhXy+nKDiiVfA9//qadbbL7CGNkAe42EekvfE8To/I4= X-Received: by 2002:a6b:dc13:: with SMTP id s19mr5317147ioc.53.1562359510080; Fri, 05 Jul 2019 13:45:10 -0700 (PDT) MIME-Version: 1.0 From: Tom Needham <06needhamt@gmail.com> Date: Fri, 5 Jul 2019 21:44:57 +0100 Message-ID: To: ffmpeg-devel@ffmpeg.org X-Content-Filtered-By: Mailman/MimeDel 2.1.20 Subject: [FFmpeg-devel] [PATCH] avcodec: Implement DM PAR Muxer/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" Samples are Available from https://transfernow.net/131xk9g4u0jt Signed-off-by: Thomas Needham <06needhamt@gmail.com> --- Changelog | 1 + configure | 7 + libavformat/Makefile | 10 + libavformat/adaudio.c | 137 ++++ libavformat/adbinary.c | 609 ++++++++++++++++ libavformat/adcommon.c | 1106 +++++++++++++++++++++++++++++ libavformat/adffmpeg_errors.h | 94 +++ libavformat/adjfif.c | 551 ++++++++++++++ libavformat/adjfif.h | 41 ++ libavformat/admime.c | 822 +++++++++++++++++++++ libavformat/adpic.h | 116 +++ libavformat/adraw.c | 131 ++++ libavformat/allformats.c | 7 + libavformat/ds.c | 1262 +++++++++++++++++++++++++++++++++ libavformat/ds.h | 135 ++++ libavformat/ds_exports.h | 173 +++++ libavformat/dsenc.c | 488 +++++++++++++ libavformat/dsenc.h | 35 + libavformat/dspic.c | 317 +++++++++ libavformat/libpar.c | 1030 +++++++++++++++++++++++++++ libavformat/libpar.h | 40 ++ libavformat/netvu.c | 214 ++++++ libavformat/netvu.h | 21 + libavformat/version.h | 4 +- 24 files changed, 7349 insertions(+), 2 deletions(-) create mode 100644 libavformat/adaudio.c create mode 100644 libavformat/adbinary.c create mode 100644 libavformat/adcommon.c create mode 100644 libavformat/adffmpeg_errors.h create mode 100644 libavformat/adjfif.c create mode 100644 libavformat/adjfif.h create mode 100644 libavformat/admime.c create mode 100644 libavformat/adpic.h create mode 100644 libavformat/adraw.c create mode 100644 libavformat/ds.c create mode 100644 libavformat/ds.h create mode 100644 libavformat/ds_exports.h create mode 100644 libavformat/dsenc.c create mode 100644 libavformat/dsenc.h create mode 100644 libavformat/dspic.c create mode 100644 libavformat/libpar.c create mode 100644 libavformat/libpar.h create mode 100644 libavformat/netvu.c create mode 100644 libavformat/netvu.h #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ LIBAVFORMAT_VERSION_MINOR, \ diff --git a/Changelog b/Changelog index 86167b76a1..41d12c092e 100644 --- a/Changelog +++ b/Changelog @@ -35,6 +35,7 @@ version : - IFV demuxer - derain filter - deesser filter +- AD Holdings PAR Muxer and Demuxer version 4.1: diff --git a/configure b/configure index 7cea9d4d73..39c4356c00 100755 --- a/configure +++ b/configure @@ -317,6 +317,7 @@ External library support: --enable-vapoursynth enable VapourSynth demuxer [no] --disable-xlib disable xlib [autodetect] --disable-zlib disable zlib [autodetect] + --enable-libparreader enable PAR (de)muxing via libparreader [no] The following libraries provide various hardware acceleration features: --disable-amf disable AMF video encoding code [autodetect] @@ -1720,6 +1721,7 @@ EXTERNAL_LIBRARY_NONFREE_LIST=" libfdk_aac openssl libtls + libparreader " EXTERNAL_LIBRARY_VERSION3_LIST=" @@ -2768,6 +2770,8 @@ on2avc_decoder_select="mdct" opus_decoder_deps="swresample" opus_decoder_select="mdct15" opus_encoder_select="audio_frame_queue mdct15" +libparreader_demuxer_deps="libparreader" +libparreader_muxer_deps="libparreader" png_decoder_deps="zlib" png_encoder_deps="zlib" png_encoder_select="llvidencdsp" @@ -6136,7 +6140,10 @@ for func in $COMPLEX_FUNCS; do done # these are off by default, so fail if requested and not available + enabled cuda_nvcc && { check_nvcc || die "ERROR: failed checking for nvcc."; } +enabled libparreader && require libparreader "parreader.h parreader_types.h" parReader_getPicStructSize -lparreader +enabled cuda_sdk && require cuda_sdk cuda.h cuCtxCreate -lcuda enabled chromaprint && require chromaprint chromaprint.h chromaprint_get_version -lchromaprint enabled decklink && { require_headers DeckLinkAPI.h && { test_cpp_condition DeckLinkAPIVersion.h "BLACKMAGIC_DECKLINK_API_VERSION >= 0x0a090500" || die "ERROR: Decklink API version must be >= 10.9.5."; } } diff --git a/libavformat/Makefile b/libavformat/Makefile index a434b005a4..363159009d 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -569,11 +569,21 @@ OBJS-$(CONFIG_YUV4MPEGPIPE_DEMUXER) += yuv4mpegdec.o OBJS-$(CONFIG_YUV4MPEGPIPE_MUXER) += yuv4mpegenc.o # external library muxers/demuxers +OBJS-$(CONFIG_ADAUDIO_DEMUXER) += adaudio.o +OBJS-$(CONFIG_ADBINARY_DEMUXER) += adbinary.o adcommon.o adjfif.o +OBJS-$(CONFIG_ADMIME_DEMUXER) += admime.o adcommon.o adjfif.o +OBJS-$(CONFIG_ADRAW_DEMUXER) += adraw.o adcommon.o adjfif.o OBJS-$(CONFIG_AVISYNTH_DEMUXER) += avisynth.o OBJS-$(CONFIG_CHROMAPRINT_MUXER) += chromaprint.o +OBJS-$(CONFIG_DM_PROTOCOL) += dsenc.o ds.o +OBJS-$(CONFIG_DSPIC_DEMUXER) += ds.o dspic.o adcommon.o OBJS-$(CONFIG_LIBGME_DEMUXER) += libgme.o OBJS-$(CONFIG_LIBMODPLUG_DEMUXER) += libmodplug.o OBJS-$(CONFIG_LIBOPENMPT_DEMUXER) += libopenmpt.o +OBJS-$(CONFIG_LIBPARREADER_DEMUXER) += libpar.o adcommon.o +OBJS-$(CONFIG_LIBPARREADER_MUXER) += libpar.o adcommon.o +OBJS-$(CONFIG_LIBMODPLUG_DEMUXER) += libmodplug.o +OBJS-$(CONFIG_NETVU_PROTOCOL) += netvu.o OBJS-$(CONFIG_VAPOURSYNTH_DEMUXER) += vapoursynth.o # protocols I/O diff --git a/libavformat/adaudio.c b/libavformat/adaudio.c new file mode 100644 index 0000000000..0c28497953 --- /dev/null +++ b/libavformat/adaudio.c @@ -0,0 +1,137 @@ +/* + * AD-Holdings demuxer for AD audio stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * AD-Holdings demuxer for AD audio stream format + */ + +#include "avformat.h" +#include "ds_exports.h" +#include "adpic.h" + + +#define SIZEOF_RTP_HEADER 12 + + +static int adaudio_probe(AVProbeData *p); +static int adaudio_read_header(AVFormatContext *s); +static int adaudio_read_packet(struct AVFormatContext *s, AVPacket *pkt); + + +static int adaudio_probe(AVProbeData *p) +{ + if( p->buf_size < 4 ) + return 0; + + /* Check the value of the first byte and the payload byte */ + if( + p->buf[0] == 0x80 && + ( + p->buf[1] == RTP_PAYLOAD_TYPE_8000HZ_ADPCM || + p->buf[1] == RTP_PAYLOAD_TYPE_11025HZ_ADPCM || + p->buf[1] == RTP_PAYLOAD_TYPE_16000HZ_ADPCM || + p->buf[1] == RTP_PAYLOAD_TYPE_22050HZ_ADPCM + ) + ) { + return AVPROBE_SCORE_MAX; + } + + return 0; +} + +static int adaudio_read_header(AVFormatContext *s) +{ + s->ctx_flags |= AVFMTCTX_NOHEADER; + + return 0; +} + +static int adaudio_read_packet(struct AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext * ioContext = s->pb; + int retVal = AVERROR(EIO); + int packetSize = 0; + int sampleSize = 0; + AVStream * st = NULL; + int isPacketAlloced = 0; +#ifdef AD_SIDEDATA_IN_PRIV + struct ADFrameData * frameData = NULL; +#endif + + /* Get the next packet */ + if( (packetSize = ioContext->read_packet( ioContext->opaque, ioContext->buf_ptr, ioContext->buffer_size )) > 0 ) { + /* Validate the 12 byte RTP header as best we can */ + if( ioContext->buf_ptr[1] == RTP_PAYLOAD_TYPE_8000HZ_ADPCM || + ioContext->buf_ptr[1] == RTP_PAYLOAD_TYPE_11025HZ_ADPCM || + ioContext->buf_ptr[1] == RTP_PAYLOAD_TYPE_16000HZ_ADPCM || + ioContext->buf_ptr[1] == RTP_PAYLOAD_TYPE_22050HZ_ADPCM + ) { + /* Calculate the size of the sample data */ + sampleSize = packetSize - SIZEOF_RTP_HEADER; + + /* Create a new AVPacket */ + if( av_new_packet( pkt, sampleSize ) >= 0 ) { + isPacketAlloced = 1; + + /* Copy data into packet */ + audiodata_network2host(pkt->data, &ioContext->buf_ptr[SIZEOF_RTP_HEADER], sampleSize); + + /* Configure stream info */ + if( (st = ad_get_audio_stream(s, NULL)) != NULL ) { + pkt->stream_index = st->index; + pkt->duration = ((int)(AV_TIME_BASE * 1.0)); + +#ifdef AD_SIDEDATA_IN_PRIV + if( (frameData = av_malloc(sizeof(*frameData))) != NULL ) { + /* Set the frame info up */ + frameData->frameType = RTPAudio; + frameData->frameData = (void*)(&ioContext->buf_ptr[1]); + frameData->additionalData = NULL; + + pkt->priv = (void*)frameData; + retVal = 0; + } + else + retVal = AVERROR(ENOMEM); +#endif + } + } + } + } + + /* Check whether we need to release the packet data we allocated */ + if( retVal < 0 && isPacketAlloced != 0 ) { + av_free_packet( pkt ); + } + + return retVal; +} + + +AVInputFormat ff_adaudio_demuxer = { + .name = "adaudio", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings audio format"), + .read_probe = adaudio_probe, + .read_header = adaudio_read_header, + .read_packet = adaudio_read_packet, +}; diff --git a/libavformat/adbinary.c b/libavformat/adbinary.c new file mode 100644 index 0000000000..5808d41c8f --- /dev/null +++ b/libavformat/adbinary.c @@ -0,0 +1,609 @@ +/* + * AD-Holdings demuxer for AD stream format (binary) + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * AD-Holdings demuxer for AD stream format (binary) + */ + +#include + +#include "avformat.h" +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" + +#include "adpic.h" +#include "adffmpeg_errors.h" + + +enum pkt_offsets { PKT_DATATYPE, + PKT_DATACHANNEL, + PKT_SIZE_BYTE_0, + PKT_SIZE_BYTE_1, + PKT_SIZE_BYTE_2, + PKT_SIZE_BYTE_3, + PKT_SEPARATOR_SIZE + }; + +typedef struct { // PRC 002 + uint32_t t; + uint16_t ms; + uint16_t mode; +} MinimalAudioHeader; + + +static void audioheader_network2host(struct NetVuAudioData *dst, const uint8_t *src) +{ + dst->version = AV_RB32(src); + dst->mode = AV_RB32(src + 4); + dst->channel = AV_RB32(src + 8); + dst->sizeOfAdditionalData = AV_RB32(src + 12); + dst->sizeOfAudioData = AV_RB32(src + 16); + dst->seconds = AV_RB32(src + 20); + dst->msecs = AV_RB32(src + 24); + if ((void*)dst != (const void*)src) // Copy additionalData pointer if needed + memcpy(&dst->additionalData, src + 28, sizeof(unsigned char *)); +} + +/** + * MPEG4 or H264 video frame with a Netvu header + */ +static int adbinary_mpeg(AVFormatContext *s, + AVPacket *pkt, + struct NetVuImageData *vidDat, + char **txtDat) +{ + static const int hdrSize = NetVuImageDataHeaderSize; + AVIOContext *pb = s->pb; + int textSize = 0; + int n, status, errorVal = 0; + + n = avio_read(pb, (uint8_t *)vidDat, hdrSize); + if (n < hdrSize) { + av_log(s, AV_LOG_ERROR, "%s: short of data reading header, " + "expected %d, read %d\n", + __func__, hdrSize, n); + return ADFFMPEG_AD_ERROR_MPEG4_PIC_BODY; + } + ad_network2host(vidDat, (uint8_t *)vidDat); + if (!pic_version_valid(vidDat->version)) { + av_log(s, AV_LOG_ERROR, "%s: invalid pic version 0x%08X\n", __func__, + vidDat->version); + return ADFFMPEG_AD_ERROR_MPEG4_PIC_VERSION_VALID; + } + + // Get the additional text block + textSize = vidDat->start_offset; + *txtDat = av_malloc(textSize + 1); + + if (*txtDat == NULL) { + av_log(s, AV_LOG_ERROR, "%s: Failed to allocate memory for text\n", __func__); + return AVERROR(ENOMEM); + } + + // Copy the additional text block + n = avio_get_str(pb, textSize, *txtDat, textSize+1); + if (n < textSize) + avio_skip(pb, textSize - n); + + status = av_get_packet(pb, pkt, vidDat->size); + if (status < 0) { + av_log(s, AV_LOG_ERROR, "%s: av_get_packet (size %d) failed, status %d\n", + __func__, vidDat->size, status); + return ADFFMPEG_AD_ERROR_MPEG4_NEW_PACKET; + } + + if ( (vidDat->vid_format == PIC_MODE_MPEG4_411_I) || (vidDat->vid_format == PIC_MODE_MPEG4_411_GOV_I) ) + pkt->flags |= AV_PKT_FLAG_KEY; + + return errorVal; +} + +/** + * MPEG4 or H264 video frame with a minimal header + */ +static int adbinary_mpeg_minimal(AVFormatContext *s, + AVPacket *pkt, int size, int channel, + struct NetVuImageData *vidDat, char **text_data, + int adDataType) +{ + static const int titleLen = sizeof(vidDat->title) / sizeof(vidDat->title[0]); + AdContext* adContext = s->priv_data; + AVIOContext * pb = s->pb; + int dataSize = size - (4 + 2); + int errorVal = 0; + + // Get the minimal video header and copy into generic video data structure + memset(vidDat, 0, sizeof(struct NetVuImageData)); + vidDat->session_time = avio_rb32(pb); + vidDat->milliseconds = avio_rb16(pb); + + if ( pb->error || (vidDat->session_time == 0) ) { + av_log(s, AV_LOG_ERROR, "%s: Reading header, errorcode %d\n", + __func__, pb->error); + return ADFFMPEG_AD_ERROR_MPEG4_MINIMAL_GET_BUFFER; + } + vidDat->version = PIC_VERSION; + vidDat->cam = channel + 1; + vidDat->utc_offset = adContext->utc_offset; + snprintf(vidDat->title, titleLen, "Camera %d", vidDat->cam); + + // Now get the main frame data into a new packet + errorVal = av_get_packet(pb, pkt, dataSize); + if( errorVal < 0 ) { + av_log(s, AV_LOG_ERROR, "%s: av_get_packet (size %d) failed, status %d\n", + __func__, dataSize, errorVal); + return ADFFMPEG_AD_ERROR_MPEG4_MINIMAL_NEW_PACKET; + } + + if (adContext->streamDatatype == 0) { + //if (adDataType == AD_DATATYPE_MININAL_H264) + // adContext->streamDatatype = PIC_MODE_H264I; + //else + adContext->streamDatatype = mpegOrH264(AV_RB32(pkt->data)); + } + vidDat->vid_format = adContext->streamDatatype; + + return errorVal; +} + +/** + * Audio frame with a Netvu header + */ +static int ad_read_audio(AVFormatContext *s, + AVPacket *pkt, int size, + struct NetVuAudioData *data, + enum AVCodecID codec_id) +{ + AVIOContext *pb = s->pb; + int status; + + // Get the fixed size portion of the audio header + size = NetVuAudioDataHeaderSize - sizeof(unsigned char *); + if (avio_read( pb, (uint8_t*)data, size) != size) + return ADFFMPEG_AD_ERROR_AUDIO_ADPCM_GET_BUFFER; + + // endian fix it... + audioheader_network2host(data, (uint8_t*)data); + + // Now get the additional bytes + if( data->sizeOfAdditionalData > 0 ) { + data->additionalData = av_malloc( data->sizeOfAdditionalData ); + if( data->additionalData == NULL ) + return AVERROR(ENOMEM); + + if (avio_read( pb, data->additionalData, data->sizeOfAdditionalData) != data->sizeOfAdditionalData) + return ADFFMPEG_AD_ERROR_AUDIO_ADPCM_GET_BUFFER2; + } + else + data->additionalData = NULL; + + status = av_get_packet(pb, pkt, data->sizeOfAudioData); + if (status < 0) { + av_log(s, AV_LOG_ERROR, "%s: av_get_packet (size %d) failed, status %d\n", + __func__, data->sizeOfAudioData, status); + return ADFFMPEG_AD_ERROR_AUDIO_ADPCM_MIME_NEW_PACKET; + } + + if (codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) + audiodata_network2host(pkt->data, pkt->data, data->sizeOfAudioData); + + return status; +} + +/** + * Audio frame with a minimal header + */ +static int adbinary_audio_minimal(AVFormatContext *s, + AVPacket *pkt, int size, + struct NetVuAudioData *data) +{ + AVIOContext *pb = s->pb; + int dataSize = size - (4 + 2 + 2); + int status; + + // Get the minimal audio header and copy into generic audio data structure + memset(data, 0, sizeof(struct NetVuAudioData)); + data->seconds = avio_rb32(pb); + data->msecs = avio_rb16(pb); + data->mode = avio_rb16(pb); + if ( pb->error || (data->seconds == 0) ) { + av_log(s, AV_LOG_ERROR, "%s: Reading header, errorcode %d\n", + __func__, pb->error); + return ADFFMPEG_AD_ERROR_MINIMAL_AUDIO_ADPCM_GET_BUFFER; + } + + // Now get the main frame data into a new packet + status = av_get_packet(pb, pkt, dataSize); + if (status < 0) { + av_log(s, AV_LOG_ERROR, "%s: av_get_packet (size %d) failed, status %d\n", + __func__, data->sizeOfAudioData, status); + return ADFFMPEG_AD_ERROR_MINIMAL_AUDIO_ADPCM_NEW_PACKET; + } + + audiodata_network2host(pkt->data, pkt->data, dataSize); + + return status; +} + + + +/** + * Identify if the stream as an AD binary stream + */ +static int adbinary_probe(AVProbeData *p) +{ + int score = 0; + unsigned char *dataPtr; + uint32_t dataSize; + int bufferSize = p->buf_size; + uint8_t *bufPtr = p->buf; + + // Netvu protocol can only send adbinary or admime + if ( (p->filename) && (av_stristart(p->filename, "netvu://", NULL) == 1)) + score += AVPROBE_SCORE_MAX / 4; + + while ((bufferSize >= PKT_SEPARATOR_SIZE) && (score < AVPROBE_SCORE_MAX)) { + dataSize = (bufPtr[PKT_SIZE_BYTE_0] << 24) + + (bufPtr[PKT_SIZE_BYTE_1] << 16) + + (bufPtr[PKT_SIZE_BYTE_2] << 8 ) + + (bufPtr[PKT_SIZE_BYTE_3]); + + // Sanity check on dataSize + if ((dataSize < 6) || (dataSize > 0x1000000)) + return 0; + + // Maximum of 32 cameras can be connected to a system + if (bufPtr[PKT_DATACHANNEL] > 32) + return 0; + + dataPtr = &bufPtr[PKT_SEPARATOR_SIZE]; + bufferSize -= PKT_SEPARATOR_SIZE; + + switch (bufPtr[PKT_DATATYPE]) { + case AD_DATATYPE_JPEG: + case AD_DATATYPE_MPEG4I: + case AD_DATATYPE_MPEG4P: + case AD_DATATYPE_H264I: + case AD_DATATYPE_H264P: + if (bufferSize >= NetVuImageDataHeaderSize) { + struct NetVuImageData test; + ad_network2host(&test, dataPtr); + if (pic_version_valid(test.version)) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected video packet\n", __func__); + score += AVPROBE_SCORE_MAX; + } + } + break; + case AD_DATATYPE_JFIF: + if (bufferSize >= 2) { + if ( (*dataPtr == 0xFF) && (*(dataPtr + 1) == 0xD8) ) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected JFIF packet\n", __func__); + score += AVPROBE_SCORE_MAX; + } + } + break; + case AD_DATATYPE_AUDIO_ADPCM: + if (bufferSize >= NetVuAudioDataHeaderSize) { + struct NetVuAudioData test; + audioheader_network2host(&test, dataPtr); + if (test.version == AUD_VERSION) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected audio packet\n", __func__); + score += AVPROBE_SCORE_MAX; + } + } + break; + case AD_DATATYPE_AUDIO_RAW: + // We don't handle this format + av_log(NULL, AV_LOG_DEBUG, "%s: Detected raw audio packet (unsupported)\n", __func__); + break; + case AD_DATATYPE_MINIMAL_MPEG4: + if (bufferSize >= 10) { + uint32_t sec = AV_RB32(dataPtr); + uint32_t vos = AV_RB32(dataPtr + 6); + + if (mpegOrH264(vos) == PIC_MODE_MPEG4_411) { + // Check for MPEG4 start code in data along with a timestamp + // of 1980 or later. Crappy test, but there isn't much data + // to go on. Should be able to use milliseconds <= 1000 but + // servers often send larger values than this, + // nonsensical as that is + if (sec > 315532800) { + if ((vos >= 0x1B0) && (vos <= 0x1B6)) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected minimal MPEG4 packet %u\n", __func__, dataSize); + score += AVPROBE_SCORE_MAX / 4; + } + } + break; + } + } + // Servers can send h264 identified as MPEG4, so fall through + // to next case + //case(AD_DATATYPE_MINIMAL_H264): + if (bufferSize >= 10) { + uint32_t sec = AV_RB32(dataPtr); + uint32_t vos = AV_RB32(dataPtr + 6); + // Check for h264 start code in data along with a timestamp + // of 1980 or later. Crappy test, but there isn't much data + // to go on. Should be able to use milliseconds <= 1000 but + // servers often send larger values than this, + // nonsensical as that is + if (sec > 315532800) { + if (vos == 0x01) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected minimal h264 packet %u\n", __func__, dataSize); + score += AVPROBE_SCORE_MAX / 4; + } + } + } + break; + case AD_DATATYPE_MINIMAL_AUDIO_ADPCM: + if (bufferSize >= 8) { + MinimalAudioHeader test; + test.t = AV_RB32(dataPtr); + test.ms = AV_RB16(dataPtr + 4); + test.mode = AV_RB16(dataPtr + 6); + + switch(test.mode) { + case RTP_PAYLOAD_TYPE_8000HZ_ADPCM: + case RTP_PAYLOAD_TYPE_11025HZ_ADPCM: + case RTP_PAYLOAD_TYPE_16000HZ_ADPCM: + case RTP_PAYLOAD_TYPE_22050HZ_ADPCM: + case RTP_PAYLOAD_TYPE_32000HZ_ADPCM: + case RTP_PAYLOAD_TYPE_44100HZ_ADPCM: + case RTP_PAYLOAD_TYPE_48000HZ_ADPCM: + case RTP_PAYLOAD_TYPE_8000HZ_PCM: + case RTP_PAYLOAD_TYPE_11025HZ_PCM: + case RTP_PAYLOAD_TYPE_16000HZ_PCM: + case RTP_PAYLOAD_TYPE_22050HZ_PCM: + case RTP_PAYLOAD_TYPE_32000HZ_PCM: + case RTP_PAYLOAD_TYPE_44100HZ_PCM: + case RTP_PAYLOAD_TYPE_48000HZ_PCM: + av_log(NULL, AV_LOG_DEBUG, "%s: Detected minimal audio packet\n", __func__); + score += AVPROBE_SCORE_MAX / 4; + } + } + break; + case AD_DATATYPE_LAYOUT: + av_log(NULL, AV_LOG_DEBUG, "%s: Detected layout packet\n", __func__); + break; + case AD_DATATYPE_INFO: + if ( (bufferSize >= 1) && (dataPtr[0] == 0) || (dataPtr[0] == 1) ) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected info packet (%d)\n", __func__, dataPtr[0]); + if ((bufferSize >= 5) && (strncmp(&dataPtr[1], "SITE", 4) == 0)) + score += AVPROBE_SCORE_MAX; + else if ((bufferSize >= 15) && (strncmp(&dataPtr[1], "(JPEG)TARGSIZE", 14) == 0)) + score += AVPROBE_SCORE_MAX; + else + score += 5; + } + break; + case AD_DATATYPE_XML_INFO: + if (bufferSize >= dataSize) { + const char *infoString = ""; + int infoStringLen = strlen(infoString); + if ( (infoStringLen <= dataSize) && (av_strncasecmp(dataPtr, infoString, infoStringLen) == 0) ) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected xml info packet\n", __func__); + score += AVPROBE_SCORE_MAX; + } + } + break; + case AD_DATATYPE_BMP: + av_log(NULL, AV_LOG_DEBUG, "%s: Detected bmp packet\n", __func__); + break; + case AD_DATATYPE_PBM: + if (bufferSize >= 3) { + if ((dataPtr[0] == 'P') && (dataPtr[1] >= '1') && (dataPtr[1] <= '6')) { + if (dataPtr[2] == 0x0A) { + score += AVPROBE_SCORE_MAX; + av_log(NULL, AV_LOG_DEBUG, "%s: Detected pbm packet\n", __func__); + } + } + } + break; + case AD_DATATYPE_SVARS_INFO: + if ( (bufferSize >= 1) && (dataPtr[0] == 0) || (dataPtr[0] == 1) ) { + av_log(NULL, AV_LOG_DEBUG, "%s: Detected svars info packet (%d)\n", __func__, dataPtr[0]); + score += 5; + } + break; + default: + av_log(NULL, AV_LOG_DEBUG, "%s: Detected unknown packet type\n", __func__); + break; + } + + if (dataSize <= bufferSize) { + bufferSize -= dataSize; + bufPtr = dataPtr + dataSize; + } + else { + bufferSize = 0; + bufPtr = p->buf; + } + } + + if (score > AVPROBE_SCORE_MAX) + score = AVPROBE_SCORE_MAX; + + av_log(NULL, AV_LOG_DEBUG, "%s: Score %d\n", __func__, score); + + return score; +} + +static int adbinary_read_header(AVFormatContext *s) +{ + AdContext *adContext = s->priv_data; + return ad_read_header(s, &adContext->utc_offset); +} + +static int adbinary_read_packet(struct AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext * pb = s->pb; + void * payload = NULL; + char * txtDat = NULL; + int errorVal = -1; + unsigned char * tempbuf = NULL; + enum AVMediaType mediaType = AVMEDIA_TYPE_UNKNOWN; + enum AVCodecID codecId = AV_CODEC_ID_NONE; + int data_type, data_channel; + unsigned int size; + uint8_t temp[6]; + + // First read the 6 byte separator + if (avio_read(pb, temp, 6) >= 6) { + data_type = temp[0]; + data_channel = temp[1]; + size = AV_RB32(temp + 2); + if (data_type >= AD_DATATYPE_MAX) { + av_log(s, AV_LOG_WARNING, "%s: No handler for data_type = %d", __func__, data_type); + return ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR; + } + //if (data_channel >= 32) { + // av_log(s, AV_LOG_WARNING, "%s: Channel number %d too high", __func__, data_channel); + // return ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR; + //} + if (size >= 0x1000000) { + av_log(s, AV_LOG_WARNING, "%s: Packet too large, %d bytes", __func__, size); + return ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR; + } + } + else + return ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR; + + if (size == 0) { + if(pb->eof_reached) + errorVal = AVERROR_EOF; + else { + av_log(s, AV_LOG_ERROR, "%s: Reading separator, error code %d\n", + __func__, pb->error); + errorVal = ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR; + } + return errorVal; + } + + // Prepare for video or audio read + errorVal = initADData(data_type, &mediaType, &codecId, &payload); + if (errorVal >= 0) { + // Proceed based on the type of data in this frame + switch(data_type) { + case AD_DATATYPE_JPEG: + errorVal = ad_read_jpeg(s, pkt, payload, &txtDat); + break; + case AD_DATATYPE_JFIF: + errorVal = ad_read_jfif(s, pkt, 0, size, payload, &txtDat); + break; + case AD_DATATYPE_MPEG4I: + case AD_DATATYPE_MPEG4P: + case AD_DATATYPE_H264I: + case AD_DATATYPE_H264P: + errorVal = adbinary_mpeg(s, pkt, payload, &txtDat); + break; + case AD_DATATYPE_MINIMAL_MPEG4: + //case(AD_DATATYPE_MINIMAL_H264): + errorVal = adbinary_mpeg_minimal(s, pkt, size, data_channel, + payload, &txtDat, data_type); + break; + case AD_DATATYPE_MINIMAL_AUDIO_ADPCM: + errorVal = adbinary_audio_minimal(s, pkt, size, payload); + break; + case AD_DATATYPE_AUDIO_ADPCM: + errorVal = ad_read_audio(s, pkt, size, payload, AV_CODEC_ID_ADPCM_IMA_WAV); + break; + case AD_DATATYPE_INFO: + case AD_DATATYPE_XML_INFO: + case AD_DATATYPE_SVARS_INFO: + // May want to handle INFO, XML_INFO and SVARS_INFO separately in future + errorVal = ad_read_info(s, pkt, size); + break; + case AD_DATATYPE_LAYOUT: + errorVal = ad_read_layout(s, pkt, size); + break; + case AD_DATATYPE_BMP: + // av_dlog(s, "Bitmap overlay\n"); + tempbuf = av_malloc(size); + if (tempbuf) { + avio_read(pb, tempbuf, size); + av_free(tempbuf); + } + else + return AVERROR(ENOMEM); + return ADFFMPEG_AD_ERROR_DEFAULT; + case AD_DATATYPE_PBM: + errorVal = ad_read_overlay(s, pkt, data_channel, size, &txtDat); + break; + default: + av_log(s, AV_LOG_WARNING, "%s: No handler for data_type = %d " + "Surrounding bytes = %02x%02x%08x\n", + __func__, data_type, data_type, data_channel, size); + + // Would like to use avio_skip, but that needs seek support, + // so just read the data into a buffer then throw it away + tempbuf = av_malloc(size); + avio_read(pb, tempbuf, size); + av_free(tempbuf); + + return ADFFMPEG_AD_ERROR_DEFAULT; + } + } + + if (errorVal >= 0) { + errorVal = ad_read_packet(s, pkt, data_channel, mediaType, codecId, payload, txtDat); + } + else { + av_log(s, AV_LOG_ERROR, "%s: Error %d creating packet\n", __func__, errorVal); + +#ifdef AD_SIDEDATA_IN_PRIV + // If there was an error, release any memory that has been allocated + if (payload != NULL) + av_freep(&payload); + + if( txtDat != NULL ) + av_freep(&txtDat); +#endif + } + +#ifndef AD_SIDEDATA_IN_PRIV + if (payload != NULL) + av_freep(&payload); + + if( txtDat != NULL ) + av_freep(&txtDat); +#endif + + return errorVal; +} + +static int adbinary_read_close(AVFormatContext *s) +{ + return 0; +} + + +AVInputFormat ff_adbinary_demuxer = { + .name = "adbinary", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings video format (binary)"), + .priv_data_size = sizeof(AdContext), + .read_probe = adbinary_probe, + .read_header = adbinary_read_header, + .read_packet = adbinary_read_packet, + .read_close = adbinary_read_close, + .flags = AVFMT_TS_DISCONT | AVFMT_VARIABLE_FPS | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/adcommon.c b/libavformat/adcommon.c new file mode 100644 index 0000000000..76a97d4c56 --- /dev/null +++ b/libavformat/adcommon.c @@ -0,0 +1,1106 @@ +/* + * AD-Holdings common functions for demuxers + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 "internal.h" +#include "url.h" +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" + +#include "adffmpeg_errors.h" +#include "adpic.h" +#include "adjfif.h" +#include "netvu.h" + + +static const AVRational MilliTB = {1, 1000}; + + +int ad_read_header(AVFormatContext *s, int *utcOffset) +{ + AVIOContext* pb = s->pb; + URLContext* urlContext = pb->opaque; + NetvuContext* nv = NULL; + + if (urlContext && urlContext->is_streamed) { + if ( av_stristart(urlContext->filename, "netvu://", NULL) == 1) + nv = urlContext->priv_data; + } + if (nv) { + int ii; + char temp[12]; + + if (utcOffset) + *utcOffset = nv->utc_offset; + + for(ii = 0; ii < NETVU_MAX_HEADERS; ii++) { + av_dict_set(&s->metadata, nv->hdrNames[ii], nv->hdrs[ii], 0); + } + if ( (nv->utc_offset >= 0) && (nv->utc_offset <= 1440) ) { + snprintf(temp, sizeof(temp), "%d", nv->utc_offset); + av_dict_set(&s->metadata, "timezone", temp, 0); + } + } + + s->ctx_flags |= AVFMTCTX_NOHEADER; + return 0; +} + +void ad_network2host(struct NetVuImageData *pic, uint8_t *data) +{ + pic->version = AV_RB32(data + 0); + pic->mode = AV_RB32(data + 4); + pic->cam = AV_RB32(data + 8); + pic->vid_format = AV_RB32(data + 12); + pic->start_offset = AV_RB32(data + 16); + pic->size = AV_RB32(data + 20); + pic->max_size = AV_RB32(data + 24); + pic->target_size = AV_RB32(data + 28); + pic->factor = AV_RB32(data + 32); + pic->alm_bitmask_hi = AV_RB32(data + 36); + pic->status = AV_RB32(data + 40); + pic->session_time = AV_RB32(data + 44); + pic->milliseconds = AV_RB32(data + 48); + if ((uint8_t *)pic != data) { + memcpy(pic->res, data + 52, 4); + memcpy(pic->title, data + 56, 31); + memcpy(pic->alarm, data + 87, 31); + } + pic->format.src_pixels = AV_RB16(data + 118); + pic->format.src_lines = AV_RB16(data + 120); + pic->format.target_pixels = AV_RB16(data + 122); + pic->format.target_lines = AV_RB16(data + 124); + pic->format.pixel_offset = AV_RB16(data + 126); + pic->format.line_offset = AV_RB16(data + 128); + if ((uint8_t *)pic != data) + memcpy(pic->locale, data + 130, 30); + pic->utc_offset = AV_RB32(data + 160); + pic->alm_bitmask = AV_RB32(data + 164); +} + +static AVStream * netvu_get_stream(AVFormatContext *s, struct NetVuImageData *p) +{ + time_t dateSec; + char dateStr[18]; + AVStream *stream = ad_get_vstream(s, + p->format.target_pixels, + p->format.target_lines, + p->cam, + p->vid_format, + p->title); + stream->start_time = p->session_time * 1000LL + p->milliseconds; + dateSec = p->session_time; + strftime(dateStr, sizeof(dateStr), "%Y-%m-%d %H:%MZ", gmtime(&dateSec)); + av_dict_set(&stream->metadata, "date", dateStr, 0); + return stream; +} + +int ad_adFormatToCodecId(AVFormatContext *s, int32_t adFormat) +{ + int codec_id = AV_CODEC_ID_NONE; + + switch(adFormat) { + case PIC_MODE_JPEG_422: + case PIC_MODE_JPEG_411: + codec_id = AV_CODEC_ID_MJPEG; + break; + + case PIC_MODE_MPEG4_411: + case PIC_MODE_MPEG4_411_I: + case PIC_MODE_MPEG4_411_GOV_P: + case PIC_MODE_MPEG4_411_GOV_I: + codec_id = AV_CODEC_ID_MPEG4; + break; + + case PIC_MODE_H264I: + case PIC_MODE_H264P: + case PIC_MODE_H264J: + codec_id = AV_CODEC_ID_H264; + break; + + default: + av_log(s, AV_LOG_WARNING, + "ad_get_stream: unrecognised vid_format %d\n", + adFormat); + codec_id = AV_CODEC_ID_NONE; + } + return codec_id; +} + +AVStream * ad_get_vstream(AVFormatContext *s, uint16_t w, uint16_t h, uint8_t cam, int32_t format, const char *title) +{ + uint8_t codec_type = 0; + int codec_id, id; + int i, found; + char textbuffer[4]; + AVStream *st; + + codec_id = ad_adFormatToCodecId(s, format); + if (codec_id == AV_CODEC_ID_MJPEG) + codec_type = 0; + else if (codec_id == AV_CODEC_ID_MPEG4) + codec_type = 1; + else if (codec_id) + codec_type = 2; + + id = ((codec_type & 0x0003) << 29) | + (((cam - 1) & 0x001F) << 24) | + (((w >> 4) & 0x0FFF) << 12) | + (((h >> 4) & 0x0FFF) << 0); + + found = FALSE; + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if (st->id == id) { + found = TRUE; + break; + } + } + if (!found) { + st = avformat_new_stream(s, NULL); + if (st) { + st->id = id; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = codec_id; + st->codec->width = w; + st->codec->height = h; + st->index = i; + + // Set pixel aspect ratio, display aspect is (sar * width / height) + // May get overridden by codec + if( (st->codec->width > 360) && (st->codec->height < 480) ) + st->sample_aspect_ratio = (AVRational) { 1, 2 }; + else + st->sample_aspect_ratio = (AVRational) { 1, 1 }; + + // Use milliseconds as the time base + st->r_frame_rate = MilliTB; + avpriv_set_pts_info(st, 32, MilliTB.num, MilliTB.den); + st->codec->time_base = MilliTB; + + if (title) + av_dict_set(&st->metadata, "title", title, 0); + snprintf(textbuffer, sizeof(textbuffer), "%u", cam); + av_dict_set(&st->metadata, "track", textbuffer, 0); + + av_dict_set(&st->metadata, "type", "camera", 0); + } + } + return st; +} + +static unsigned int RSHash(int camera, const char *name, unsigned int len) +{ + unsigned int b = 378551; + unsigned int a = (camera << 16) + (camera << 8) + camera; + unsigned int hash = 0; + unsigned int i = 0; + + for(i = 0; i < len; name++, i++) { + hash = hash * a + (*name); + a = a * b; + } + return hash; +} + +static AVStream * ad_get_overlay_stream(AVFormatContext *s, int channel, const char *title) +{ + static const int codec_id = AV_CODEC_ID_PBM; + unsigned int id; + int i, found; + AVStream *st; + + id = RSHash(channel+1, title, strlen(title)); + + found = FALSE; + for (i = 0; i < s->nb_streams; i++) { + st = s->streams[i]; + if ((st->codec->codec_id == codec_id) && (st->id == id)) { + found = TRUE; + break; + } + } + if (!found) { + st = avformat_new_stream(s, NULL); + if (st) { + st->id = id; + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + st->codec->codec_id = codec_id; + st->index = i; + + // Use milliseconds as the time base + st->r_frame_rate = MilliTB; + avpriv_set_pts_info(st, 32, MilliTB.num, MilliTB.den); + st->codec->time_base = MilliTB; + + av_dict_set(&st->metadata, "title", title, 0); + av_dict_set(&st->metadata, "type", "mask", 0); + } + } + return st; +} + +AVStream * ad_get_audio_stream(AVFormatContext *s, struct NetVuAudioData* audioHeader) +{ + int i, found; + AVStream *st; + + found = FALSE; + for( i = 0; i < s->nb_streams; i++ ) { + st = s->streams[i]; + if ( (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) && (st->id == audioHeader->channel) ) { + found = TRUE; + break; + } + } + + // Did we find our audio stream? If not, create a new one + if( !found ) { + st = avformat_new_stream(s, NULL); + if (st) { + st->id = audioHeader->channel; + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = 1; + st->codec->block_align = 0; + + if (audioHeader) { + switch(audioHeader->mode) { + default: + case(RTP_PAYLOAD_TYPE_8000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_16000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_44100HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_11025HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_22050HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_32000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_48000HZ_ADPCM): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + break; + case(RTP_PAYLOAD_TYPE_8000HZ_PCM): + case(RTP_PAYLOAD_TYPE_16000HZ_PCM): + case(RTP_PAYLOAD_TYPE_44100HZ_PCM): + case(RTP_PAYLOAD_TYPE_11025HZ_PCM): + case(RTP_PAYLOAD_TYPE_22050HZ_PCM): + case(RTP_PAYLOAD_TYPE_32000HZ_PCM): + case(RTP_PAYLOAD_TYPE_48000HZ_PCM): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + break; + } + switch(audioHeader->mode) { + default: + case(RTP_PAYLOAD_TYPE_8000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_8000HZ_PCM): + st->codec->sample_rate = 8000; + break; + case(RTP_PAYLOAD_TYPE_16000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_16000HZ_PCM): + st->codec->sample_rate = 16000; + break; + case(RTP_PAYLOAD_TYPE_44100HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_44100HZ_PCM): + st->codec->sample_rate = 441000; + break; + case(RTP_PAYLOAD_TYPE_11025HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_11025HZ_PCM): + st->codec->sample_rate = 11025; + break; + case(RTP_PAYLOAD_TYPE_22050HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_22050HZ_PCM): + st->codec->sample_rate = 22050; + break; + case(RTP_PAYLOAD_TYPE_32000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_32000HZ_PCM): + st->codec->sample_rate = 32000; + break; + case(RTP_PAYLOAD_TYPE_48000HZ_ADPCM): + case(RTP_PAYLOAD_TYPE_48000HZ_PCM): + st->codec->sample_rate = 48000; + break; + } + } + else { + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 8000; + } + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + st->index = i; + } + } + + return st; +} + +/** + * Returns the data stream associated with the current connection. + * + * If there isn't one already, a new one will be created and added to the + * AVFormatContext passed in. + * + * \param s Pointer to AVFormatContext + * \return Pointer to the data stream on success, NULL on failure + */ +static AVStream * ad_get_data_stream(AVFormatContext *s, enum AVCodecID codecId) +{ + int i, found = FALSE; + AVStream *st = NULL; + + for (i = 0; i < s->nb_streams && !found; i++ ) { + st = s->streams[i]; + if (st->id == codecId) + found = TRUE; + } + + // Did we find our data stream? If not, create a new one + if( !found ) { + st = avformat_new_stream(s, NULL); + if (st) { + st->id = codecId; + st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE; + st->codec->codec_id = AV_CODEC_ID_TEXT; + + // Use milliseconds as the time base + //st->r_frame_rate = MilliTB; + avpriv_set_pts_info(st, 32, MilliTB.num, MilliTB.den); + //st->codec->time_base = MilliTB; + + st->index = i; + } + } + return st; +} + +//static AVStream *ad_get_stream(AVFormatContext *s, enum AVMediaType media, +// enum AVCodecID codecId, void *data) +//{ +// switch (media) { +// case(AVMEDIA_TYPE_VIDEO): +// if (codecId == AV_CODEC_ID_PBM) +// return ad_get_overlay_stream(s, (const char *)data); +// else +// return netvu_get_stream(s, (struct NetVuImageData *)data); +// break; +// case(AVMEDIA_TYPE_AUDIO): +// return ad_get_audio_stream(s, (struct NetVuAudioData *)data); +// break; +// case(AVMEDIA_TYPE_DATA): +// return ad_get_data_stream(s); +// break; +// default: +// break; +// } +// return NULL; +//} + +#ifdef AD_SIDEDATA_IN_PRIV +static void ad_release_packet( AVPacket *pkt ) +{ + if (pkt == NULL) + return; + + if (pkt->priv) { + // Have a look what type of frame we have and then free as appropriate + struct ADFrameData *frameData = (struct ADFrameData *)pkt->priv; + struct NetVuAudioData *audHeader; + + switch(frameData->frameType) { + case(NetVuAudio): + audHeader = (struct NetVuAudioData *)frameData->frameData; + if( audHeader->additionalData ) + av_free( audHeader->additionalData ); + case(NetVuVideo): + case(DMVideo): + case(DMNudge): + case(NetVuDataInfo): + case(NetVuDataLayout): + case(RTPAudio): + av_freep(&frameData->frameData); + av_freep(&frameData->additionalData); + break; + default: + // Error, unrecognised frameType + break; + } + av_freep(&pkt->priv); + } + + // Now use the default routine to release the rest of the packet's resources + av_destruct_packet( pkt ); +} +#endif + +#ifdef AD_SIDEDATA_IN_PRIV +int ad_new_packet(AVPacket *pkt, int size) +{ + int retVal = av_new_packet( pkt, size ); + pkt->priv = NULL; + if( retVal >= 0 ) { + // Give the packet its own destruct function + pkt->destruct = ad_release_packet; + } + + return retVal; +} + +static void ad_keyvalsplit(const char *line, char *key, char *val) +{ + int ii, jj = 0; + int len = strlen(line); + int inKey, inVal; + + inKey = 1; + inVal = 0; + for (ii = 0; ii < len; ii++) { + if (inKey) { + if (line[ii] == ':') { + key[jj++] = '\0'; + inKey = 0; + inVal = 1; + jj = 0; + } + else { + key[jj++] = line[ii]; + } + } + else if (inVal) { + val[jj++] = line[ii]; + } + } + val[jj++] = '\0'; +} + +static int ad_splitcsv(const char *csv, int *results, int maxElements, int base) +{ + int ii, jj, ee; + char element[8]; + int len = strlen(csv); + + for (ii = 0, jj = 0, ee = 0; ii < len; ii++) { + if ((csv[ii] == ',') || (csv[ii] == ';') || (ii == (len -1))) { + element[jj++] = '\0'; + if (base == 10) + sscanf(element, "%d", &results[ee++]); + else + sscanf(element, "%x", &results[ee++]); + if (ee >= maxElements) + break; + jj = 0; + } + else + element[jj++] = csv[ii]; + } + return ee; +} + +static void ad_parseVSD(const char *vsd, struct ADFrameData *frame) +{ + char key[64], val[128]; + ad_keyvalsplit(vsd, key, val); + + if ((strlen(key) == 2) && (key[0] == 'M')) { + switch(key[1]) { + case('0'): + ad_splitcsv(val, frame->vsd[VSD_M0], VSDARRAYLEN, 10); + break; + case('1'): + ad_splitcsv(val, frame->vsd[VSD_M1], VSDARRAYLEN, 10); + break; + case('2'): + ad_splitcsv(val, frame->vsd[VSD_M2], VSDARRAYLEN, 10); + break; + case('3'): + ad_splitcsv(val, frame->vsd[VSD_M3], VSDARRAYLEN, 10); + break; + case('4'): + ad_splitcsv(val, frame->vsd[VSD_M4], VSDARRAYLEN, 10); + break; + case('5'): + ad_splitcsv(val, frame->vsd[VSD_M5], VSDARRAYLEN, 10); + break; + case('6'): + ad_splitcsv(val, frame->vsd[VSD_M6], VSDARRAYLEN, 10); + break; + } + } + else if ((strlen(key) == 3) && (av_strncasecmp(key, "FM0", 3) == 0)) { + ad_splitcsv(val, frame->vsd[VSD_FM0], VSDARRAYLEN, 10); + } + else if ((strlen(key) == 1) && (key[0] == 'F')) { + ad_splitcsv(val, frame->vsd[VSD_F], VSDARRAYLEN, 16); + } + else if ((strlen(key) == 3) && (av_strncasecmp(key, "EM0", 3) == 0)) { + ad_splitcsv(val, frame->vsd[VSD_EM0], VSDARRAYLEN, 10); + } + else + av_log(NULL, AV_LOG_DEBUG, "Unknown VSD key: %s: Val: %s", key, val); +} + +static void ad_parseLine(AVFormatContext *s, const char *line, struct ADFrameData *frame) +{ + char key[32], val[128]; + ad_keyvalsplit(line, key, val); + + if (av_strncasecmp(key, "Active-zones", 12) == 0) { + sscanf(val, "%d", &frame->activeZones); + } + else if (av_strncasecmp(key, "FrameNum", 8) == 0) { + sscanf(val, "%u", &frame->frameNum); + } +// else if (av_strncasecmp(key, "Site-ID", 7) == 0) { +// } + else if (av_strncasecmp(key, "ActMask", 7) == 0) { + for (int ii = 0, jj = 0; (ii < ACTMASKLEN) && (jj < strlen(val)); ii++) { + sscanf(&val[jj], "0x%04hx", &frame->activityMask[ii]); + jj += 7; + } + } + else if (av_strncasecmp(key, "VSD", 3) == 0) { + ad_parseVSD(val, frame); + } +} + +static void ad_parseText(AVFormatContext *s, struct ADFrameData *frameData) +{ + const char *src = frameData->additionalData; + int len = strlen(src); + char line[512]; + int ii, jj = 0; + + for (ii = 0; ii < len; ii++) { + if ( (src[ii] == '\r') || (src[ii] == '\n') ) { + line[jj++] = '\0'; + if (strlen(line) > 0) + ad_parseLine(s, line, frameData); + jj = 0; + } + else + line[jj++] = src[ii]; + } +} +#endif + +int initADData(int data_type, enum AVMediaType *mediaType, enum AVCodecID *codecId, void **payload) +{ + switch(data_type) { + case(AD_DATATYPE_JPEG): + case(AD_DATATYPE_JFIF): + case(AD_DATATYPE_MPEG4I): + case(AD_DATATYPE_MPEG4P): + case(AD_DATATYPE_H264I): + case(AD_DATATYPE_H264P): + case(AD_DATATYPE_MINIMAL_MPEG4): + //case(AD_DATATYPE_MINIMAL_H264): + *payload = av_mallocz( sizeof(struct NetVuImageData) ); + if( *payload == NULL ) + return AVERROR(ENOMEM); + *mediaType = AVMEDIA_TYPE_VIDEO; + switch(data_type) { + case(AD_DATATYPE_JPEG): + case(AD_DATATYPE_JFIF): + *codecId = AV_CODEC_ID_MJPEG; + break; + case(AD_DATATYPE_MPEG4I): + case(AD_DATATYPE_MPEG4P): + case(AD_DATATYPE_MINIMAL_MPEG4): + *codecId = AV_CODEC_ID_MPEG4; + break; + case(AD_DATATYPE_H264I): + case(AD_DATATYPE_H264P): + //case(AD_DATATYPE_MINIMAL_H264): + *codecId = AV_CODEC_ID_H264; + break; + } + break; + case(AD_DATATYPE_AUDIO_ADPCM): + case(AD_DATATYPE_MINIMAL_AUDIO_ADPCM): + *payload = av_malloc( sizeof(struct NetVuAudioData) ); + if( *payload == NULL ) + return AVERROR(ENOMEM); + *mediaType = AVMEDIA_TYPE_AUDIO; + *codecId = AV_CODEC_ID_ADPCM_IMA_WAV; + break; + case(AD_DATATYPE_INFO): + case(AD_DATATYPE_XML_INFO): + case(AD_DATATYPE_LAYOUT): + case(AD_DATATYPE_SVARS_INFO): + *mediaType = AVMEDIA_TYPE_DATA; + *codecId = AV_CODEC_ID_FFMETADATA; + break; + case(AD_DATATYPE_BMP): + *mediaType = AVMEDIA_TYPE_VIDEO; + *codecId = AV_CODEC_ID_BMP; + break; + case(AD_DATATYPE_PBM): + *mediaType = AVMEDIA_TYPE_VIDEO; + *codecId = AV_CODEC_ID_PBM; + break; + default: + *mediaType = AVMEDIA_TYPE_UNKNOWN; + *codecId = AV_CODEC_ID_NONE; + } + return 0; +} + +#if CONFIG_ADBINARY_DEMUXER || CONFIG_ADMIME_DEMUXER +int ad_read_jpeg(AVFormatContext *s, AVPacket *pkt, struct NetVuImageData *video_data, + char **text_data) +{ + static const int nviSize = NetVuImageDataHeaderSize; + AVIOContext *pb = s->pb; + int hdrSize; + char jfif[2048], *ptr; + int n, textSize, errorVal = 0; + int status; + + // Check if we've already read a NetVuImageData header + // (possible if this is called by adraw demuxer) + if (video_data && (!pic_version_valid(video_data->version))) { + // Read the pic structure + if ((n = avio_read(pb, (uint8_t*)video_data, nviSize)) != nviSize) { + av_log(s, AV_LOG_ERROR, "%s: Short of data reading " + "struct NetVuImageData, expected %d, read %d\n", + __func__, nviSize, n); + return ADFFMPEG_AD_ERROR_JPEG_IMAGE_DATA_READ; + } + + // Endian convert if necessary + ad_network2host(video_data, (uint8_t *)video_data); + } + + if ((video_data==NULL) || !pic_version_valid(video_data->version)) { + av_log(s, AV_LOG_ERROR, "%s: invalid struct NetVuImageData version " + "0x%08X\n", __func__, video_data->version); + return ADFFMPEG_AD_ERROR_JPEG_PIC_VERSION; + } + + // Get the additional text block + textSize = video_data->start_offset; + *text_data = av_malloc(textSize + 1); + if( *text_data == NULL ) { + av_log(s, AV_LOG_ERROR, "%s: text_data allocation failed " + "(%d bytes)", __func__, textSize + 1); + return AVERROR(ENOMEM); + } + + // Copy the additional text block + if( (n = avio_read( pb, *text_data, textSize )) != textSize ) { + av_log(s, AV_LOG_ERROR, "%s: short of data reading text block" + " data, expected %d, read %d\n", + __func__, textSize, n); + return ADFFMPEG_AD_ERROR_JPEG_READ_TEXT_BLOCK; + } + + // Somtimes the buffer seems to end with a NULL terminator, other times it + // doesn't. Adding a terminator here regardless + (*text_data)[textSize] = '\0'; + + // Use the struct NetVuImageData struct to build a JFIF header + if ((hdrSize = build_jpeg_header( jfif, video_data, 2048)) <= 0) { + av_log(s, AV_LOG_ERROR, "%s: build_jpeg_header failed\n", __func__); + return ADFFMPEG_AD_ERROR_JPEG_HEADER; + } + // We now know the packet size required for the image, allocate it. + if ((status = ad_new_packet(pkt, hdrSize + video_data->size + 2)) < 0) { + av_log(s, AV_LOG_ERROR, "%s: ad_new_packet %d failed, " + "status %d\n", __func__, + hdrSize + video_data->size + 2, status); + return ADFFMPEG_AD_ERROR_JPEG_NEW_PACKET; + } + ptr = pkt->data; + // Copy the JFIF header into the packet + memcpy(ptr, jfif, hdrSize); + ptr += hdrSize; + // Now get the compressed JPEG data into the packet + if ((n = avio_read(pb, ptr, video_data->size)) != video_data->size) { + av_log(s, AV_LOG_ERROR, "%s: short of data reading pic body, " + "expected %d, read %d\n", __func__, + video_data->size, n); + return ADFFMPEG_AD_ERROR_JPEG_READ_BODY; + } + ptr += video_data->size; + // Add the EOI marker + *ptr++ = 0xff; + *ptr++ = 0xd9; + + return errorVal; +} + +int ad_read_jfif(AVFormatContext *s, AVPacket *pkt, int imgLoaded, int size, + struct NetVuImageData *video_data, char **text_data) +{ + int n, status, errorVal = 0; + AVIOContext *pb = s->pb; + + if(!imgLoaded) { + if ((status = ad_new_packet(pkt, size)) < 0) { // PRC 003 + av_log(s, AV_LOG_ERROR, "ad_read_jfif: ad_new_packet %d failed, status %d\n", size, status); + return ADFFMPEG_AD_ERROR_JFIF_NEW_PACKET; + } + + if ((n = avio_read(pb, pkt->data, size)) < size) { + av_log(s, AV_LOG_ERROR, "ad_read_jfif: short of data reading jfif image, expected %d, read %d\n", size, n); + return ADFFMPEG_AD_ERROR_JFIF_GET_BUFFER; + } + } + if ( parse_jfif(s, pkt->data, video_data, size, text_data ) <= 0) { + av_log(s, AV_LOG_ERROR, "ad_read_jfif: parse_jfif failed\n"); + return ADFFMPEG_AD_ERROR_JFIF_MANUAL_SIZE; + } + return errorVal; +} + +/** + * Data info extraction. A DATA_INFO frame simply contains a + * type byte followed by a text block. Due to its simplicity, we'll + * just extract the whole block into the AVPacket's data structure and + * let the client deal with checking its type and parsing its text + * + * \todo Parse the string and use the information to add metadata and/or update + * the struct NetVuImageData struct + * + * Example strings, taken from a minimal mp4 stream: (Prefixed by a zero byte) + * SITE DVIP3S;CAM 1:TV;(JPEG)TARGSIZE 0:(MPEG)BITRATE 262144;IMAGESIZE 0,0:704,256; + * SITE DVIP3S;CAM 2:Directors office;(JPEG)TARGSIZE 0:(MPEG)BITRATE 262144;IMAGESIZE 0,0:704,256; + * SITE DVIP3S;CAM 3:Development;(JPEG)TARGSIZE 0:(MPEG)BITRATE 262144;IMAGESIZE 0,0:704,256; + * SITE DVIP3S;CAM 4:Rear road;(JPEG)TARGSIZE 0:(MPEG)BITRATE 262144;IMAGESIZE 0,0:704,256; + */ +int ad_read_info(AVFormatContext *s, AVPacket *pkt, int size) +{ + int n, status, errorVal = 0; + AVIOContext *pb = s->pb; + //uint8_t dataDatatype; + + // Allocate a new packet + if( (status = ad_new_packet( pkt, size )) < 0 ) + return ADFFMPEG_AD_ERROR_INFO_NEW_PACKET; + + //dataDatatype = avio_r8(pb); + //--size; + + // Get the data + if( (n = avio_read( pb, pkt->data, size)) != size ) + return ADFFMPEG_AD_ERROR_INFO_GET_BUFFER; + + return errorVal; +} + +int ad_read_layout(AVFormatContext *s, AVPacket *pkt, int size) +{ + int n, status, errorVal = 0; + AVIOContext *pb = s->pb; + + // Allocate a new packet + if( (status = ad_new_packet( pkt, size )) < 0 ) + return ADFFMPEG_AD_ERROR_LAYOUT_NEW_PACKET; + + // Get the data + if( (n = avio_read( pb, pkt->data, size)) != size ) + return ADFFMPEG_AD_ERROR_LAYOUT_GET_BUFFER; + + return errorVal; +} + +int ad_read_overlay(AVFormatContext *s, AVPacket *pkt, int channel, int insize, char **text_data) +{ + AdContext* adContext = s->priv_data; + AVIOContext *pb = s->pb; + AVStream *st = NULL; + uint8_t *inbuf = NULL; + int n, w, h; + char *comment = NULL; + + inbuf = av_malloc(insize); + n = avio_read(pb, inbuf, insize); + if (n != insize) { + av_log(s, AV_LOG_ERROR, "%s: short of data reading pbm data body, expected %d, read %d\n", __func__, insize, n); + return ADFFMPEG_AD_ERROR_OVERLAY_GET_BUFFER; + } + + pkt->size = ad_pbmDecompress(&comment, &inbuf, insize, pkt, &w, &h); + if (pkt->size <= 0) { + av_log(s, AV_LOG_ERROR, "ADPIC: ad_pbmDecompress failed\n"); + return ADFFMPEG_AD_ERROR_OVERLAY_PBM_READ; + } + + if (text_data) { + int len = 12 + strlen(comment); + *text_data = av_malloc(len); + snprintf(*text_data, len-1, "Camera %u: %s", channel+1, comment); + + st = ad_get_overlay_stream(s, channel, *text_data); + st->codec->width = w; + st->codec->height = h; + } + + av_free(comment); + + if (adContext) + pkt->dts = pkt->pts = adContext->lastVideoPTS; + + return 0; +} +#endif + + +int ad_pbmDecompress(char **comment, uint8_t **src, int size, AVPacket *pkt, int *width, int *height) +{ + static const uint8_t pbm[3] = { 0x50, 0x34, 0x0A }; + static const uint8_t rle[4] = { 0x52, 0x4C, 0x45, 0x20 }; + int isrle = 0; + const uint8_t *ptr = *src; + const uint8_t *endPtr = (*src) + size; + uint8_t *dPtr = NULL; + int strSize = 0; + const char *strPtr = NULL; + const char *endStrPtr = NULL; + unsigned int elementsRead; + + if ((size >= sizeof(pbm)) && (ptr[0] == 'P') && (ptr[1] >= '1') && (ptr[1] <= '6') && (ptr[2] == 0x0A) ) + ptr += sizeof(pbm); + else + return -1; + + while ( (ptr < endPtr) && (*ptr == '#') ) { + ++ptr; + + if ( ((endPtr - ptr) > sizeof(rle)) && (memcmp(ptr, rle, sizeof(rle)) == 0) ) { + isrle = 1; + ptr += sizeof(rle); + } + + strPtr = ptr; + while ( (ptr < endPtr) && (*ptr != 0x0A) ) { + ++ptr; + } + endStrPtr = ptr; + strSize = endStrPtr - strPtr; + + if (comment) { + if (*comment) + av_free(*comment); + *comment = av_malloc(strSize + 1); + + memcpy(*comment, strPtr, strSize); + (*comment)[strSize] = '\0'; + } + ++ptr; + } + + elementsRead = sscanf(ptr, "%d", width); + ptr += sizeof(*width) * elementsRead; + elementsRead = sscanf(ptr, "%d", height); + ptr += sizeof(*height) * elementsRead; + + if (isrle) { + // Data is Runlength Encoded, alloc a new buffer and decode into it + int len; + uint8_t val; + unsigned int headerSize = (ptr - *src) - sizeof(rle); + unsigned int headerP1Size = sizeof(pbm) + 1; + unsigned int headerP2Size = headerSize - headerP1Size; + unsigned int dataSize = ((*width) * (*height)) / 8; + + ad_new_packet(pkt, headerSize + dataSize); + dPtr = pkt->data; + + memcpy(dPtr, *src, headerP1Size); + dPtr += headerP1Size; + if (strPtr) { + memcpy(dPtr, strPtr, headerP2Size); + dPtr += headerP2Size; + } + + // Decompress loop + while (ptr < endPtr) { + len = *ptr++; + val = *ptr++; + do { + len--; + *dPtr++ = val; + } while(len>0); + } + + // Free compressed data + av_freep(src); + + return headerSize + dataSize; + } + else { + ad_new_packet(pkt, size); + memcpy(pkt->data, *src, size); + av_freep(src); + return size; + } +} + +static int addSideData(AVFormatContext *s, AVPacket *pkt, + enum AVMediaType media, unsigned int size, + void *data, const char *text) +{ +#if defined(AD_SIDEDATA_IN_PRIV) + struct ADFrameData *frameData = av_mallocz(sizeof(*frameData)); + if( frameData == NULL ) + return AVERROR(ENOMEM); + + if (media == AVMEDIA_TYPE_VIDEO) + frameData->frameType = NetVuVideo; + else + frameData->frameType = NetVuAudio; + + frameData->frameData = data; + frameData->additionalData = (unsigned char *)text; + + if (text != NULL) + ad_parseText(s, frameData); + pkt->priv = frameData; +#elif defined(AD_SIDEDATA) + uint8_t *side = av_packet_new_side_data(pkt, AV_PKT_DATA_AD_FRAME, size); + if (side) + memcpy(side, data, size); + + if (text) { + size = strlen(text) + 1; + side = av_packet_new_side_data(pkt, AV_PKT_DATA_AD_TEXT, size); + if (side) + memcpy(side, text, size); + } +#endif + return 0; +} + +int ad_read_packet(AVFormatContext *s, AVPacket *pkt, int channel, + enum AVMediaType media, enum AVCodecID codecId, + void *data, char *text) +{ + AdContext *adContext = s->priv_data; + AVStream *st = NULL; + + if ((media == AVMEDIA_TYPE_VIDEO) && (codecId == AV_CODEC_ID_PBM)) { + // Get or create a data stream + if ( (st = ad_get_overlay_stream(s, channel, text)) == NULL ) { + av_log(s, AV_LOG_ERROR, "%s: ad_get_overlay_stream failed\n", __func__); + return ADFFMPEG_AD_ERROR_GET_OVERLAY_STREAM; + } + } + else if (media == AVMEDIA_TYPE_VIDEO) { + // At this point We have a legal NetVuImageData structure which we use + // to determine which codec stream to use + struct NetVuImageData *video_data = (struct NetVuImageData *)data; + if ( (st = netvu_get_stream( s, video_data)) == NULL ) { + av_log(s, AV_LOG_ERROR, "ad_read_packet: Failed get_stream for video\n"); + return ADFFMPEG_AD_ERROR_GET_STREAM; + } + else { + if (video_data->session_time > 0) { + pkt->pts = video_data->session_time; + pkt->pts *= 1000ULL; + pkt->pts += video_data->milliseconds % 1000; + } + else + pkt->pts = AV_NOPTS_VALUE; + if (adContext) + adContext->lastVideoPTS = pkt->pts; + + // Servers occasionally send insane timezone data, which can screw + // up clients. Check for this and set to 0 + if (abs(video_data->utc_offset) > 1440) { + av_log(s, AV_LOG_INFO, + "ad_read_packet: Invalid utc_offset of %d, " + "setting to zero\n", video_data->utc_offset); + video_data->utc_offset = 0; + } + + if (adContext && (!adContext->metadataSet)) { + char utcOffsetStr[12]; + snprintf(utcOffsetStr, sizeof(utcOffsetStr), "%d", video_data->utc_offset); + av_dict_set(&s->metadata, "locale", video_data->locale, 0); + av_dict_set(&s->metadata, "timezone", utcOffsetStr, 0); + adContext->metadataSet = 1; + } + + addSideData(s, pkt, media, sizeof(struct NetVuImageData), data, text); + } + } + else if (media == AVMEDIA_TYPE_AUDIO) { + // Get the audio stream + struct NetVuAudioData *audHdr = (struct NetVuAudioData *)data; + if ( (st = ad_get_audio_stream( s, audHdr )) == NULL ) { + av_log(s, AV_LOG_ERROR, "ad_read_packet: ad_get_audio_stream failed\n"); + return ADFFMPEG_AD_ERROR_GET_AUDIO_STREAM; + } + else { + if (audHdr->seconds > 0) { + int64_t milliseconds = audHdr->seconds * 1000ULL + (audHdr->msecs % 1000); + pkt->pts = av_rescale_q(milliseconds, MilliTB, st->time_base); + } + else + pkt->pts = AV_NOPTS_VALUE; + + addSideData(s, pkt, media, sizeof(struct NetVuAudioData), audHdr, text); + } + } + else if (media == AVMEDIA_TYPE_DATA) { + // Get or create a data stream + if ( (st = ad_get_data_stream(s, codecId)) == NULL ) { + av_log(s, AV_LOG_ERROR, "%s: ad_get_data_stream failed\n", __func__); + return ADFFMPEG_AD_ERROR_GET_INFO_LAYOUT_STREAM; + } + } + + if (st) { + pkt->stream_index = st->index; + + pkt->duration = 1; + pkt->pos = -1; + } + + return 0; +} + +void audiodata_network2host(uint8_t *dest, const uint8_t *src, int size) +{ + const uint8_t *dataEnd = src + size; + + uint16_t predictor = AV_RB16(src); + src += 2; + + AV_WL16(dest, predictor); + dest += 2; + + *dest++ = *src++; + *dest++ = *src++; + + for (;src < dataEnd; src++, dest++) { + *dest = (((*src) & 0xF0) >> 4) | (((*src) & 0x0F) << 4); + } +} + +int mpegOrH264(unsigned int startCode) +{ + if ( (startCode & 0xFFFFFF00) == 0x00000100) + return PIC_MODE_MPEG4_411; + else + return PIC_MODE_H264I; +} diff --git a/libavformat/adffmpeg_errors.h b/libavformat/adffmpeg_errors.h new file mode 100644 index 0000000000..97f44676d9 --- /dev/null +++ b/libavformat/adffmpeg_errors.h @@ -0,0 +1,94 @@ +#ifndef __ADFFMPEG_ERRORS_H__ +#define __ADFFMPEG_ERRORS_H__ + +enum ADHErrorCode +{ + ADFFMPEG_ERROR_NONE = 0, + + ADFFMPEG_DS_ERROR = -99, + ADFFMPEG_DS_ERROR_AUTH_REQUIRED = ADFFMPEG_DS_ERROR -1, + ADFFMPEG_DS_ERROR_DNS_HOST_RESOLUTION_FAILURE = ADFFMPEG_DS_ERROR -2, + ADFFMPEG_DS_ERROR_HOST_UNREACHABLE = ADFFMPEG_DS_ERROR -3, + ADFFMPEG_DS_ERROR_INVALID_CREDENTIALS = ADFFMPEG_DS_ERROR -4, + ADFFMPEG_DS_ERROR_SOCKET = ADFFMPEG_DS_ERROR -5, + ADFFMPEG_DS_ERROR_CREAT_CONECTION_TIMEOUT = ADFFMPEG_DS_ERROR -6, + + ADFFMPEG_AD_ERROR = -200, + + ADFFMPEG_AD_ERROR_UNKNOWN = ADFFMPEG_AD_ERROR -1, + ADFFMPEG_AD_ERROR_READ_6_BYTE_SEPARATOR = ADFFMPEG_AD_ERROR -2, + ADFFMPEG_AD_ERROR_NEW_PACKET = ADFFMPEG_AD_ERROR -3, + ADFFMPEG_AD_ERROR_PARSE_MIME_HEADER = ADFFMPEG_AD_ERROR -4, + //ADFFMPEG_AD_ERROR_NETVU_IMAGE_DATA = ADFFMPEG_AD_ERROR -5, + //ADFFMPEG_AD_ERROR_NETVU_AUDIO_DATA = ADFFMPEG_AD_ERROR -6, + + ADFFMPEG_AD_ERROR_JPEG_IMAGE_DATA_READ = ADFFMPEG_AD_ERROR -7, + ADFFMPEG_AD_ERROR_JPEG_PIC_VERSION = ADFFMPEG_AD_ERROR -8, + //ADFFMPEG_AD_ERROR_JPEG_ALLOCATE_TEXT_BLOCK = ADFFMPEG_AD_ERROR -9, + ADFFMPEG_AD_ERROR_JPEG_READ_TEXT_BLOCK = ADFFMPEG_AD_ERROR -10, + ADFFMPEG_AD_ERROR_JPEG_HEADER = ADFFMPEG_AD_ERROR -11, + ADFFMPEG_AD_ERROR_JPEG_NEW_PACKET = ADFFMPEG_AD_ERROR -12, + ADFFMPEG_AD_ERROR_JPEG_READ_BODY = ADFFMPEG_AD_ERROR -13, + + ADFFMPEG_AD_ERROR_JFIF_NEW_PACKET = ADFFMPEG_AD_ERROR -14, + ADFFMPEG_AD_ERROR_JFIF_GET_BUFFER = ADFFMPEG_AD_ERROR -15, + ADFFMPEG_AD_ERROR_JFIF_MANUAL_SIZE = ADFFMPEG_AD_ERROR -16, + + ADFFMPEG_AD_ERROR_MPEG4_MIME_NEW_PACKET = ADFFMPEG_AD_ERROR -17, + ADFFMPEG_AD_ERROR_MPEG4_MIME_GET_BUFFER = ADFFMPEG_AD_ERROR -18, + ADFFMPEG_AD_ERROR_MPEG4_MIME_PARSE_HEADER = ADFFMPEG_AD_ERROR -19, + ADFFMPEG_AD_ERROR_MPEG4_MIME_PARSE_TEXT_DATA = ADFFMPEG_AD_ERROR -20, + ADFFMPEG_AD_ERROR_MPEG4_MIME_GET_TEXT_BUFFER = ADFFMPEG_AD_ERROR -21, + //ADFFMPEG_AD_ERROR_MPEG4_MIME_ALLOCATE_TEXT_BUFFER = ADFFMPEG_AD_ERROR -22, + + ADFFMPEG_AD_ERROR_MPEG4_GET_BUFFER = ADFFMPEG_AD_ERROR -23, + ADFFMPEG_AD_ERROR_MPEG4_PIC_VERSION_VALID = ADFFMPEG_AD_ERROR -24, + //ADFFMPEG_AD_ERROR_MPEG4_ALLOCATE_TEXT_BUFFER = ADFFMPEG_AD_ERROR -25, + ADFFMPEG_AD_ERROR_MPEG4_GET_TEXT_BUFFER = ADFFMPEG_AD_ERROR -26, + ADFFMPEG_AD_ERROR_MPEG4_NEW_PACKET = ADFFMPEG_AD_ERROR -27, + ADFFMPEG_AD_ERROR_MPEG4_PIC_BODY = ADFFMPEG_AD_ERROR -28, + + ADFFMPEG_AD_ERROR_MPEG4_MINIMAL_GET_BUFFER = ADFFMPEG_AD_ERROR -29, + ADFFMPEG_AD_ERROR_MPEG4_MINIMAL_NEW_PACKET = ADFFMPEG_AD_ERROR -30, + ADFFMPEG_AD_ERROR_MPEG4_MINIMAL_NEW_PACKET2 = ADFFMPEG_AD_ERROR -31, + + ADFFMPEG_AD_ERROR_MINIMAL_AUDIO_ADPCM_GET_BUFFER = ADFFMPEG_AD_ERROR -32, + ADFFMPEG_AD_ERROR_MINIMAL_AUDIO_ADPCM_NEW_PACKET = ADFFMPEG_AD_ERROR -33, + ADFFMPEG_AD_ERROR_MINIMAL_AUDIO_ADPCM_GET_BUFFER2 = ADFFMPEG_AD_ERROR -34, + + ADFFMPEG_AD_ERROR_AUDIO_ADPCM_GET_BUFFER = ADFFMPEG_AD_ERROR -35, + //ADFFMPEG_AD_ERROR_AUDIO_ADPCM_ALLOCATE_ADDITIONAL = ADFFMPEG_AD_ERROR -36, + ADFFMPEG_AD_ERROR_AUDIO_ADPCM_GET_BUFFER2 = ADFFMPEG_AD_ERROR -37, + + ADFFMPEG_AD_ERROR_AUDIO_ADPCM_MIME_NEW_PACKET = ADFFMPEG_AD_ERROR -38, + ADFFMPEG_AD_ERROR_AUDIO_ADPCM_MIME_GET_BUFFER = ADFFMPEG_AD_ERROR -39, + + ADFFMPEG_AD_ERROR_INFO_NEW_PACKET = ADFFMPEG_AD_ERROR -40, + ADFFMPEG_AD_ERROR_INFO_GET_BUFFER = ADFFMPEG_AD_ERROR -41, + + ADFFMPEG_AD_ERROR_LAYOUT_NEW_PACKET = ADFFMPEG_AD_ERROR -42, + ADFFMPEG_AD_ERROR_LAYOUT_GET_BUFFER = ADFFMPEG_AD_ERROR -43, + + ADFFMPEG_AD_ERROR_DEFAULT = ADFFMPEG_AD_ERROR -44, + + ADFFMPEG_AD_ERROR_GET_STREAM = ADFFMPEG_AD_ERROR -45, + ADFFMPEG_AD_ERROR_GET_AUDIO_STREAM = ADFFMPEG_AD_ERROR -46, + ADFFMPEG_AD_ERROR_GET_INFO_LAYOUT_STREAM = ADFFMPEG_AD_ERROR -47, + //ADFFMPEG_AD_ERROR_END_OF_STREAM = ADFFMPEG_AD_ERROR -48, + + //ADFFMPEG_AD_ERROR_FAILED_TO_PARSE_INFOLIST = ADFFMPEG_AD_ERROR -49, + ADFFMPEG_AD_ERROR_OVERLAY_GET_BUFFER = ADFFMPEG_AD_ERROR -50, + ADFFMPEG_AD_ERROR_OVERLAY_PBM_READ = ADFFMPEG_AD_ERROR -51, + ADFFMPEG_AD_ERROR_GET_OVERLAY_STREAM = ADFFMPEG_AD_ERROR -52, + + ADFFMPEG_AD_ERROR_LAST = ADFFMPEG_AD_ERROR -53, + + ADH_GRAPH_RELOAD_NEEDED = 0x8001, + +#ifdef __midl +} ADHErrorCode; +#else +}; +#endif + +#endif diff --git a/libavformat/adjfif.c b/libavformat/adjfif.c new file mode 100644 index 0000000000..50df8257fe --- /dev/null +++ b/libavformat/adjfif.c @@ -0,0 +1,551 @@ +/* + * Helper functions for converting between raw JPEG data with + * NetVuImageData header and full JFIF image (and vice-versa) + * + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * Helper functions for converting between raw JPEG data with + * NetVuImageData header and full JFIF image (and vice-versa) + */ + +#include +#include "internal.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/avstring.h" + +#include "adjfif.h" +#include "adpic.h" + +static int find_q(unsigned char *qy); +static void parse_comment(char *text, int text_len, struct NetVuImageData *pic, + char **additionalText ); +static void calcQtabs(void); + + +static const char comment_version[] = "Version: 00.02\r\n"; +static const char comment_version_0_1[] = "Version: 00.01\r\n"; +static const char camera_title[] = "Name: "; +static const char camera_number[] = "Number: "; +static const char image_date[] = "Date: "; +static const char image_time[] = "Time: "; +static const char image_ms[] = "MSec: "; +static const char q_factor[] = "Q-Factor: "; +static const char alarm_comment[] = "Alarm-text: "; +static const char active_alarms[] = "Active-alarms: "; +static const char active_detectors[] = "Active-detectors: "; +static const char script_msg[] = "Comments: "; +static const char time_zone[] = "Locale: "; +static const char utc_offset[] = "UTCoffset: "; + +static const uint8_t jfif_header[] = { + 0xFF, 0xD8, // SOI + 0xFF, 0xE0, // APP0 + 0x00, 0x10, // APP0 header size (including + // this field, but excluding preceding) + 0x4A, 0x46, 0x49, 0x46, 0x00, // ID string 'JFIF\0' + 0x01, 0x02, // version + 0x02, // density units (0 - none, 1 - PPI, 2 - PPCM) + 0x00, 0x19, // X density + 0x00, 0x19, // Y density + 0x00, // X thumbnail size + 0x00, // Y thumbnail size +}; + +static const unsigned char sof_422_header[] = { + 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0x00, 0x01, + 0x60, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01 +}; +static const unsigned char sof_411_header[] = { + 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x01, 0x00, 0x01, + 0x60, 0x03, 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01 +}; + +static const unsigned char huf_header[] = { + 0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, + 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, + 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, 0x02, + 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, + 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22, 0x71, + 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, + 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33, + 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, + 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, + 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, + 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x73, + 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, + 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, + 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, + 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, + 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, + 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, + 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, + 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, + 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, + 0xFF, 0xC4, 0x00, 0x1F, 0x01, 0x00, 0x03, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, + 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0xFF, 0xC4, 0x00, 0xB5, 0x11, 0x00, 0x02, + 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05, + 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, + 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, + 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22, + 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, + 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, + 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, + 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27, 0x28, + 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, + 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, + 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, + 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, + 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, + 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, + 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, + 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, + 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; + +static const unsigned char sos_header[] = { + 0xFF, 0xDA, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, + 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00 +}; + +unsigned short Yvis[64] = { + 16, 11, 12, 14, 12, 10, 16, 14, + 13, 14, 18, 17, 16, 19, 24, 40, + 26, 24, 22, 22, 24, 49, 35, 37, + 29, 40, 58, 51, 61, 60, 57, 51, + 56, 55, 64, 72, 92, 78, 64, 68, + 87, 69, 55, 56, 80, 109, 81, 87, + 95, 98, 103, 104, 103, 62, 77, 113, + 121, 112, 100, 120, 92, 101, 103, 99 +}; + +unsigned short UVvis[64] = { + 17, 18, 18, 24, 21, 24, 47, 26, + 26, 47, 99, 66, 56, 66, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 +}; + +unsigned char YQuantizationFactors[256][64], UVQuantizationFactors[256][64]; + +static int q_init; + + +/** + * Build the correct JFIF headers & tables for the supplied image data + * + * \param jfif Pointer to output buffer + * \param pic Pointer to NetVuImageData - includes Q factors, image size, + * mode etc. + * \param max Maximum size of header + * \return Total bytes in the JFIF image + */ +unsigned int build_jpeg_header(void *jfif, struct NetVuImageData *pic, unsigned int max) +{ + volatile unsigned int count; + unsigned short us1; + char *bufptr = jfif; + char sof_copy[sizeof(sof_422_header)]; + + + if (!q_init) { + calcQtabs(); + q_init = 1; + } + + // Add all the fixed length headers prior to building comment field + count = sizeof(jfif_header); + if (count > max) + return 0; + + memcpy(bufptr, jfif_header, sizeof(jfif_header)); + + if( (pic->format.target_pixels > 360) && (pic->format.target_lines < 480) ) + bufptr[17] = 0x32; + + bufptr += sizeof(jfif_header); + + // Q tables and markers + count += 138; + if (count > max) + return 0; + *bufptr++ = 0xff; + *bufptr++ = 0xdb; + *bufptr++ = 0x00; + *bufptr++ = 0x43; + *bufptr++ = 0x00; + for (us1 = 0; us1 < 64; us1++) + *bufptr++ = YQuantizationFactors[pic->factor][us1]; + *bufptr++ = 0xff; + *bufptr++ = 0xdb; + *bufptr++ = 0x00; + *bufptr++ = 0x43; + *bufptr++ = 0x01; + for (us1 = 0; us1 < 64; us1++) + *bufptr++ = UVQuantizationFactors[pic->factor][us1]; + count += (sizeof(sof_copy) + sizeof(huf_header) + sizeof(sos_header)); + if (count > max) + return 0; + else { + unsigned short targ; + char *ptr1, *ptr2; + memcpy(sof_copy, (pic->vid_format == PIC_MODE_JPEG_411) ? sof_411_header : sof_422_header, sizeof( sof_copy ) ); + targ = AV_RB16(&pic->format.target_pixels); // Byte swap from native to big-endian + ptr1 = (char *)&targ; + ptr2 = &sof_copy[7]; + *ptr2++ = *ptr1++; + *ptr2 = *ptr1; + + targ = AV_RB16(&pic->format.target_lines); // Byte swap from native to big-endian + ptr1 = (char *)&targ; + ptr2 = &sof_copy[5]; + *ptr2++ = *ptr1++; + *ptr2 = *ptr1; + + memcpy(bufptr, sof_copy, sizeof(sof_copy)); + bufptr += sizeof(sof_copy); + + memcpy(bufptr, huf_header, sizeof(huf_header)); + bufptr += sizeof(huf_header); + + memcpy(bufptr, sos_header, sizeof(sos_header)); + bufptr += sizeof(sos_header); + } + + return bufptr - (char*)jfif; +} + +/** + * Calculate the Q tables + * + * Updates the module Q factor tables + */ +static void calcQtabs(void) +{ + short i; + int uvfactor; + short factor; + short yval, uvval; + for (factor = 1; factor < 256; factor++ ) { + uvfactor = factor * 1; + if (uvfactor > 255) + uvfactor = 255; + for (i = 0; i < 64; i++) { + yval = (short)(((Yvis[i] * factor) + 25) / 50); + uvval = (short)(((UVvis[i] * uvfactor) + 25) / 50); + + if ( yval < 1 ) + yval = 1; // The DC and AC values cannot be + if ( uvval < 1) // + uvval = 1; // less than 1 + + if ( yval > 255 ) + yval = 255; // The DC and AC values cannot + if ( uvval > 255) // + uvval = 255; // be more than 255 + + YQuantizationFactors[factor][i] = (uint8_t)yval; + UVQuantizationFactors[factor][i] = (uint8_t)uvval; + } + } +} + +static int find_q(unsigned char *qy) +{ + int factor, smallest_err = 0, best_factor = 0; + unsigned char *q1, *q2, *qe; + unsigned char qtest[64]; + int err_diff; + + if (!q_init) { + calcQtabs(); + q_init = 1; + } + + memcpy(&qtest[0], qy, 64); // PRC 025 + + qe = &qtest[32]; + + for (factor = 1; factor < 256; factor++ ) { + q1 = &qtest[0]; + q2 = &YQuantizationFactors[factor][0]; + err_diff = 0; + while (q1 < qe) { + if (*q1 > *q2) + err_diff = *q1 - *q2; + else + err_diff = *q2 - *q1; + + q1++; + q2++; + } + if (err_diff == 0) { + best_factor = factor; + smallest_err = 0; + break; + } + else if ( err_diff < smallest_err || best_factor == 0 ) { + best_factor = factor; + smallest_err = err_diff; + } + } + if (factor == 256) { + factor = best_factor; + av_log(NULL, AV_LOG_ERROR, "find_q: Unable to match Q setting %d\n", best_factor ); + } + return factor; +} + + +/** + * Analyses a JFIF header and fills out a NetVuImageData structure with the info + * + * \param data Input buffer + * \param pic NetVuImageData structure + * \param imgSize Total length of input buffer + * \param text Buffer in which is placed text from the JFIF comment + * that doesn't have a specific field in NetVuImageData + * \return Length of JFIF header in bytes + */ +int parse_jfif(AVFormatContext *s, unsigned char *data, struct NetVuImageData *pic, + int imgSize, char **text) +{ + int i, sos = FALSE; + unsigned short length, marker; + uint16_t xdensity = 0; + uint16_t ydensity = 0; + uint8_t *densityPtr = NULL; + unsigned int jfifMarker; + + //av_log(s, AV_LOG_DEBUG, "parse_jfif: leading bytes 0x%02X, 0x%02X, " + // "length=%d\n", data[0], data[1], imgSize); + memset(pic, 0, sizeof(*pic)); + pic->version = PIC_VERSION; + pic->factor = -1; + pic->start_offset = 0; + i = 0; + + if(data[i] != 0xff && data[i+1] != 0xd8) { + //there is a header so skip it + while( ((unsigned char)data[i] != 0xff) && (i < imgSize) ) + i++; + //if ( i > 0 ) + // av_log(s, AV_LOG_DEBUG, "parse_jfif: %d leading bytes\n", i); + + i++; + if ( (unsigned char) data[i] != 0xd8) { + //av_log(s, AV_LOG_ERROR, "parse_jfif: incorrect SOI 0xff%02x\n", data[i]); + return -1; + } + i++; + } + else { + //no header + i += 2; + } + + while ( !sos && (i < imgSize) ) { + if (data[i] != 0xff) + continue; + marker = AV_RB16(&data[i]); + i += 2; + length = AV_RB16(&data[i]) - 2; + i += 2; + + switch (marker) { + case 0xffe0 : // APP0 + jfifMarker = AV_RB32(&data[i]); + if ( (jfifMarker==0x4A464946) && (data[i+4]==0x00) ) { + xdensity = AV_RB16(&data[i+8]); + ydensity = AV_RB16(&data[i+10]); + densityPtr = &data[i+8]; + } + break; + case 0xffdb : // Q table + if (!data[i]) + pic->factor = find_q(&data[i+1]); + break; + case 0xffc0 : // SOF + pic->format.target_lines = AV_RB16(&data[i+1]); + pic->format.target_pixels = AV_RB16(&data[i+3]); + if (data[i+7] == 0x22) + pic->vid_format = PIC_MODE_JPEG_411; + else if (data[i+7] == 0x21) + pic->vid_format = PIC_MODE_JPEG_422; + else + av_log(s, AV_LOG_WARNING, "%s: Unknown SOF format byte 0x%02X\n", __func__, data[i+7]); + break; + case 0xffda : // SOS + sos = TRUE; + break; + case 0xfffe : // Comment + parse_comment((char *)&data[i], length - 2, pic, text); + break; + default : + break; + } + i += length; + + if ( densityPtr && (pic->format.target_lines > 0) && (xdensity == (ydensity*2)) ) { + if( (pic->format.target_pixels > 360) && (pic->format.target_lines < 480) ) { + // Server is sending wrong pixel aspect ratio, reverse it + //av_log(s, AV_LOG_DEBUG, "%s: Server is sending wrong pixel " + // "aspect ratio. Old = %d:%d, New = %d:%d" + // " Res = %dx%d\n", + // __func__, xdensity, ydensity, ydensity, xdensity, + // pic->format.target_pixels, pic->format.target_lines); + AV_WB16(densityPtr, ydensity); + AV_WB16(densityPtr + 2, xdensity); + } + else { + // Server is sending wrong pixel aspect ratio, set it to 1:1 + AV_WB16(densityPtr, ydensity); + } + xdensity = AV_RB16(densityPtr); + ydensity = AV_RB16(densityPtr+2); + } + } + pic->size = imgSize - i - 2; // 2 bytes for FFD9 + return i; +} + +static void parse_comment( char *text, int text_len, struct NetVuImageData *pic, char **additionalText ) +{ + char result[512]; + int i = 0; + int j = 0; + struct tm t; + time_t when = 1000000000; + + // Calculate timezone offset of client, needed because mktime uses local + // time and there is no ISO C equivalent for GMT. + struct tm utc = *gmtime(&when); + struct tm lcl = *localtime(&when); + int delta_h = mktime(&utc) - mktime(&lcl); + + memset(&t, 0, sizeof(t)); + + while( 1 ) { + j = 0; + + // Check we haven't covered all the buffer already + if( i >= text_len || text[i] <= 0 ) + break; + + // Get the next line from the text block + while (text[i] && text[i] != '\n' && (i < text_len)) { + result[j++] = text[i++]; + if ( j >= sizeof(result) ) { + --j; + break; + } + } + result[j] = '\0'; + + // Skip the \n + if( text[i] == '\n' ) { + if (j > 0) + result[j-1] = '\0'; + else + result[0] = '\0'; + i++; + } + + // Changed this line so it doesn't include the \r\n from the end of comment_version + if( !memcmp( result, comment_version, strlen(comment_version) - 2 ) ) + pic->version = 0xdecade11; + else if ( !memcmp( result, comment_version, strlen(comment_version_0_1) - 2 ) ) + pic->version = 0xdecade10; + else if( !memcmp( result, camera_title, strlen(camera_title) ) ) { + av_strlcpy( pic->title, &result[strlen(camera_title)], sizeof(pic->title) ); + } + else if( !memcmp( result, camera_number, strlen(camera_number) ) ) + sscanf( &result[strlen(camera_number)], "%d", &pic->cam ); + else if( !memcmp( result, image_date, strlen(image_date) ) ) { + sscanf( &result[strlen(image_date)], "%d/%d/%d", &t.tm_mday, &t.tm_mon, &t.tm_year ); + t.tm_year -= 1900; + t.tm_mon--; + } + else if( !memcmp( result, image_time, strlen(image_time) ) ) + sscanf( &result[strlen(image_time)], "%d:%d:%d", &t.tm_hour, &t.tm_min, &t.tm_sec ); + else if( !memcmp( result, image_ms, strlen(image_ms) ) ) + sscanf( &result[strlen(image_ms)], "%d", &pic->milliseconds ); + else if( !memcmp( result, q_factor, strlen(q_factor) ) ) + sscanf( &result[strlen(q_factor)], "%d", &pic->factor ); + else if( !memcmp( result, alarm_comment, strlen(alarm_comment) ) ) + av_strlcpy( pic->alarm, &result[strlen(alarm_comment)], sizeof(pic->alarm) ); + else if( !memcmp( result, active_alarms, strlen(active_alarms) ) ) + sscanf( &result[strlen(active_alarms)], "%X", &pic->alm_bitmask ); + else if( !memcmp( result, active_detectors, strlen(active_detectors) ) ) + sscanf( &result[strlen(active_detectors)], "%X", &pic->alm_bitmask_hi ); + else if( !memcmp( result, script_msg, strlen(script_msg) ) ) { + } + else if( !memcmp( result, time_zone, strlen(time_zone) ) ) + av_strlcpy( pic->locale, &result[strlen(time_zone)], sizeof(pic->locale) ); + else if( !memcmp( result, utc_offset, strlen(utc_offset) ) ) + sscanf( &result[strlen(utc_offset)], "%d", &pic->utc_offset ); + else { + // Any line we don't explicitly detect for extraction to the pic + // struct, we must add to the additional text block + if ( (additionalText != NULL) && (strlen(result) > 0) ) { + int strLen = 0; + const char lineEnd[3] = { '\r', '\n', '\0' }; + + // Get the length of the existing text block if it exists + if( *additionalText != NULL ) + strLen = strlen( *additionalText ); + + // Ok, now allocate some space to hold the new string + *additionalText = av_realloc( *additionalText, strLen + strlen(result) + 3 ); + + // Copy the line into the text block + memcpy( &(*additionalText)[strLen], result, strlen(result) ); + + // Add a \r\n and NULL termination + memcpy( &(*additionalText)[strLen + strlen(result)], lineEnd, 3 ); + } + } + } + + pic->session_time = mktime(&t) - delta_h; +} diff --git a/libavformat/adjfif.h b/libavformat/adjfif.h new file mode 100644 index 0000000000..b59c5823c7 --- /dev/null +++ b/libavformat/adjfif.h @@ -0,0 +1,41 @@ +/* + * Helper functions for converting between raw JPEG data with + * NetVuImageData header and full JFIF image (and vice-versa) + * + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * Helper functions for converting between raw JPEG data with + * NetVuImageData header and full JFIF image (and vice-versa) + */ + +#ifndef AVFORMAT_ADJFIF_H +#define AVFORMAT_ADJFIF_H + +#include "ds_exports.h" + +extern unsigned int build_jpeg_header(void *jfif, struct NetVuImageData *pic, + unsigned int max); +extern int parse_jfif(AVFormatContext *s, unsigned char *data, + struct NetVuImageData *pic, int imgSize, char **text); + +#endif diff --git a/libavformat/admime.c b/libavformat/admime.c new file mode 100644 index 0000000000..47d9ece2e4 --- /dev/null +++ b/libavformat/admime.c @@ -0,0 +1,822 @@ +/* + * AD-Holdings demuxer for AD stream format (binary) + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file AD-Holdings demuxer for AD stream format (binary) + */ + +#include + +#include "avformat.h" +#include "adffmpeg_errors.h" +#include "adpic.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/avstring.h" + + +#define TEMP_BUFFER_SIZE 1024 +#define MAX_IMAGE_SIZE (256 * 1024) + +// This value is only used internally within the library DATA_PLAINTEXT blocks +// should not be exposed to the client +#define DATA_PLAINTEXT (AD_DATATYPE_MAX + 1) + + +static const char * BOUNDARY_PREFIX1 = "--0plm("; +static const char * BOUNDARY_PREFIX2 = "Í -0plm("; +static const char * BOUNDARY_PREFIX3 = "ÍÍ 0plm("; +static const char * BOUNDARY_PREFIX4 = "ÍÍÍ plm("; +static const char * BOUNDARY_PREFIX5 = "ÍÍÍÍ lm("; +static const char * BOUNDARY_PREFIX6 = "ÍÍÍÍÍ m("; +static const char * BOUNDARY_PREFIX7 = "/r--0plm("; +static const char * BOUNDARY_SUFFIX = ":Server-Push:Boundary-String)1qaz"; + +static const char * MIME_TYPE_JPEG = "image/jpeg"; +static const char * MIME_TYPE_MP4 = "image/admp4"; +static const char * MIME_TYPE_TEXT = "text/plain"; +static const char * MIME_TYPE_XML = "text/xml"; +static const char * MIME_TYPE_ADPCM = "audio/adpcm"; +static const char * MIME_TYPE_LAYOUT = "data/layout"; +static const char * MIME_TYPE_PBM = "image/pbm"; +static const char * MIME_TYPE_H264 = "image/adh264"; + +static const uint8_t rawJfifHeader[] = { 0xff, 0xd8, 0xff, 0xe0, + 0x00, 0x10, 0x4a, 0x46, + 0x49, 0x46, 0x00, 0x01 }; + + + +/** + * Validates a multipart MIME boundary separator against the convention used by + * NetVu video servers + * + * \param buf Buffer containing the boundary separator + * \param bufLen Size of the buffer + * \return 1 if boundary separator is valid, 0 if not + */ +static int is_valid_separator( unsigned char * buf, int bufLen ) +{ + if (buf == NULL ) + return FALSE; + + if (bufLen < strlen(BOUNDARY_PREFIX1) + strlen(BOUNDARY_SUFFIX) ) + return FALSE; + + if ((strncmp(buf, BOUNDARY_PREFIX1, strlen(BOUNDARY_PREFIX1)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX2, strlen(BOUNDARY_PREFIX2)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX3, strlen(BOUNDARY_PREFIX3)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX4, strlen(BOUNDARY_PREFIX4)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX5, strlen(BOUNDARY_PREFIX5)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX6, strlen(BOUNDARY_PREFIX6)) == 0) || + (strncmp(buf, BOUNDARY_PREFIX7, strlen(BOUNDARY_PREFIX7)) == 0) ) { + unsigned char * b = &buf[strlen(BOUNDARY_PREFIX1)]; + + // Now we have a server type string. We must skip past this + while( !av_isspace(*b) && *b != ':' && (b - buf) < bufLen ) { + b++; + } + + if (*b == ':' ) { + if ((b - buf) + strlen(BOUNDARY_SUFFIX) <= bufLen ) { + if (strncmp(b, BOUNDARY_SUFFIX, strlen(BOUNDARY_SUFFIX)) == 0 ) + return TRUE; + } + } + } + + return FALSE; +} + + +/** + * Parse a line of MIME data + */ +static int process_line(char *line, int *line_count, int *dataType, + int *size, long *extra ) +{ + char *tag, *p = NULL; + int http_code = 0; + + // end of header + if (line[0] == '\0') + return 0; + + p = line; + + // The boundry string is missing sometimes so check for the HTTP header + // and skip to line 1 + if(*line_count == 0 && 'H' == *(p) && 'T' == *(p + 1) && 'T' == *(p + 2) && 'P' == *(p + 3)) + *line_count = 1; + + // The first valid line will be the boundary string - validate this here + if (*line_count == 0 ) { + if (is_valid_separator( p, strlen(p) ) == FALSE ) + return -1; + } + else if (*line_count == 1 ) { // Second line will contain the HTTP status code + while (!av_isspace(*p) && *p != '\0') + p++; + while (av_isspace(*p)) + p++; + http_code = strtol(p, NULL, 10); + } + else { + // Any other line we are just looking for particular headers + // if we find them, we fill in the appropriate output data + while (*p != '\0' && *p != ':') + p++; + if (*p != ':') + return 1; + + *p = '\0'; + tag = line; + p++; + while (av_isspace(*p)) + p++; + + if (!strcmp(tag, "Content-length")) { + *size = strtol(p, NULL, 10); + + if (size == 0 ) + return -1; + } + + if (!strcmp(tag, "Content-type")) { + // Work out what type we actually have + if (av_strcasecmp(p, MIME_TYPE_JPEG ) == 0 ) + *dataType = AD_DATATYPE_JFIF; + // Or if it starts image/mp4 - this covers all the supported mp4 + // variations (i and p frames) + else if(av_strncasecmp(p, MIME_TYPE_MP4, strlen(MIME_TYPE_MP4) ) == 0) { + // P for now - as they are both processed the same subsequently + *dataType = AD_DATATYPE_MPEG4P; + } + else if (av_strcasecmp(p, MIME_TYPE_TEXT ) == 0 ) + *dataType = DATA_PLAINTEXT; + else if (av_strcasecmp(p, MIME_TYPE_LAYOUT ) == 0 ) + *dataType = AD_DATATYPE_LAYOUT; + else if (av_strcasecmp(p, MIME_TYPE_XML ) == 0 ) + *dataType = AD_DATATYPE_XML_INFO; + else if(av_strncasecmp(p, MIME_TYPE_ADPCM, strlen(MIME_TYPE_ADPCM)) == 0) { + *dataType = AD_DATATYPE_AUDIO_ADPCM; + + // If we find audio in a mime header, we need to extract the + // mode out. The header takes the form, + // Content-Type: audio/adpcm;rate= + while (*p != '\0' && *p != ';') + p++; + + if (*p != ';' ) + return 1; + + p++; + while (av_isspace(*p)) + p++; + + // p now pointing at the rate. Look for the first '=' + while (*p != '\0' && *p != '=') + p++; + if (*p != '=') + return 1; + + p++; + + tag = p; + + while( *p != '\0' && !av_isspace(*p) ) + p++; + + if (*p != '\0' ) + *p = '\0'; + + *extra = strtol(tag, NULL, 10); + + // Map the rate to a RTP payload value for consistancy - + // the other audio headers contain mode values in this format + if (*extra == 8000 ) + *extra = RTP_PAYLOAD_TYPE_8000HZ_ADPCM; + else if (*extra == 11025 ) + *extra = RTP_PAYLOAD_TYPE_11025HZ_ADPCM; + else if (*extra == 16000 ) + *extra = RTP_PAYLOAD_TYPE_16000HZ_ADPCM; + else if (*extra == 22050 ) + *extra = RTP_PAYLOAD_TYPE_22050HZ_ADPCM; + else if (*extra == 32000 ) + *extra = RTP_PAYLOAD_TYPE_32000HZ_ADPCM; + else if (*extra == 44100 ) + *extra = RTP_PAYLOAD_TYPE_44100HZ_ADPCM; + else if (*extra == 48000 ) + *extra = RTP_PAYLOAD_TYPE_48000HZ_ADPCM; + else + *extra = RTP_PAYLOAD_TYPE_8000HZ_ADPCM; // Default + } + else if (av_strcasecmp(p, MIME_TYPE_PBM ) == 0 ) { + *dataType = AD_DATATYPE_PBM; + } + else if(av_strncasecmp(p, MIME_TYPE_H264, strlen(MIME_TYPE_H264) ) == 0) { + // P for now - as they are both processed the same subsequently + *dataType = AD_DATATYPE_H264P; + } + else { + *dataType = AD_DATATYPE_MAX; + } + } + } + return 1; +} + +/** + * Read and process MIME header information + * + * \return Zero on successful decode, -2 if a raw JPEG, anything else indicates + * failure + */ +static int parse_mime_header(AVIOContext *pb, uint8_t *buffer, int *bufSize, + int *dataType, int *size, long *extra) +{ + unsigned char ch, *q = NULL; + int err, lineCount = 0; + const int maxBufSize = *bufSize; + + *bufSize = 0; + + // Check for JPEG header first + do { + if (avio_read(pb, &ch, 1) < 0) + break; + if (buffer && (ch == rawJfifHeader[*bufSize])) { + buffer[*bufSize] = ch; + ++(*bufSize); + if ((*bufSize) == sizeof(rawJfifHeader)) + return -2; + } + else + break; + } while( (*bufSize) < sizeof(rawJfifHeader)); + + q = buffer + *bufSize; + // Try and parse the header + for(;;) { + if (ch == '\n') { + // process line + if (q > buffer && q[-1] == '\r') + q--; + *q = '\0'; + + err = process_line( buffer, &lineCount, dataType, size, extra ); + // First line contains a \n + if (!(err == 0 && lineCount == 0) ) { + if (err < 0 ) + return err; + + if (err == 0 ) + return 0; + lineCount++; + } + + q = buffer; + } + else { + if ((q - buffer) < (maxBufSize - 1)) + *q++ = ch; + else + return ADFFMPEG_AD_ERROR_PARSE_MIME_HEADER; + } + + err = avio_read(pb, &ch, 1); + if (err < 0) { + if (pb->eof_reached) + return err; + else + return ADFFMPEG_AD_ERROR_PARSE_MIME_HEADER; + } + } + + return ADFFMPEG_AD_ERROR_PARSE_MIME_HEADER; +} + + +/** + * Parse a line of MIME data for MPEG video frames + */ +static int process_mp4data_line( char *line, int line_count, + struct NetVuImageData *vidDat, struct tm *tim, + char ** txtDat ) +{ + static const int titleLen = sizeof(vidDat->title) / sizeof(vidDat->title[0]); + char *tag = NULL, *p = NULL; + int lineLen = 0; + + // end of header + if (line[0] == '\0') + return 0; + + p = line; + + + while (*p != '\0' && *p != ':') + p++; + if (*p != ':') + return 1; + + tag = line; + p++; + + while( *p != '\0' && *p == ' ' ) // While the current char is a space + p++; + + if (*p == '\0') + return 1; + else { + char * temp = p; + + // Get the length of the rest of the line + while( *temp != '\0' ) + temp++; + + lineLen = temp - line; + } + + if (!memcmp( tag, "Number", strlen( "Number" ) ) ) + vidDat->cam = strtol(p, NULL, 10); + else if (!memcmp( tag, "Name", strlen( "Name" ) ) ) + memcpy( vidDat->title, p, FFMIN( titleLen, strlen(p) ) ); + else if (!memcmp( tag, "Version", strlen( "Version" ) ) ) { + int verNum = strtod(p, NULL) * 100.0 - 1; + vidDat->version = 0xDECADE10 + verNum; + } + else if (!memcmp( tag, "Date", strlen( "Date" ) ) ) { + sscanf( p, "%d/%d/%d", &tim->tm_mday, &tim->tm_mon, &tim->tm_year ); + tim->tm_year -= 1900; + tim->tm_mon--; + + if ((tim->tm_sec != 0) || (tim->tm_min != 0) || (tim->tm_hour != 0)) + vidDat->session_time = mktime(tim); + } + else if (!memcmp( tag, "Time", strlen( "Time" ) ) ) { + sscanf( p, "%d:%d:%d", &tim->tm_hour, &tim->tm_min, &tim->tm_sec ); + + if (tim->tm_year != 0) + vidDat->session_time = mktime(tim); + } + else if (!memcmp( tag, "MSec", strlen( "MSec" ) ) ) + vidDat->milliseconds = strtol(p, NULL, 10); + else if (!memcmp( tag, "Locale", strlen( "Locale" ) ) ) + memcpy( vidDat->locale, p, FFMIN( titleLen, strlen(p) ) ); + else if (!memcmp( tag, "UTCoffset", strlen( "UTCoffset" ) ) ) + vidDat->utc_offset = strtol(p, NULL, 10); + else { + // Any lines that aren't part of the pic struct, + // tag onto the additional text block + // \todo Parse out some of these and put them into metadata + if (txtDat != NULL && lineLen > 0 ) { +#define LINE_END_LEN 3 + int strLen = 0; + const char lineEnd[LINE_END_LEN] = { '\r', '\n', '\0' }; + + // Get the length of the existing text block if it exists + if (*txtDat != NULL ) + strLen = strlen( *txtDat ); + + // Ok, now allocate some space to hold the new string + *txtDat = av_realloc(*txtDat, strLen + lineLen + LINE_END_LEN); + + // Copy the line into the text block + memcpy( &(*txtDat)[strLen], line, lineLen ); + + // Add a NULL terminator + memcpy( &(*txtDat)[strLen + lineLen], lineEnd, LINE_END_LEN ); + } + } + + return 1; +} + +/** + * Parse MIME data that is sent after each MPEG video frame + */ +static int parse_mp4_text_data( unsigned char *mp4TextData, int bufferSize, + struct NetVuImageData *vidDat, char **txtDat ) +{ + unsigned char buffer[TEMP_BUFFER_SIZE]; + int ch, err; + unsigned char * q = NULL; + int lineCount = 0; + unsigned char * currentChar = mp4TextData; + struct tm tim; + + memset( &tim, 0, sizeof(struct tm) ); + + // Try and parse the header + q = buffer; + for(;;) { + ch = *currentChar++; + + if (ch < 0) + return 1; + + if (ch == '\n') { + // process line + if (q > buffer && q[-1] == '\r') + q--; + *q = '\0'; + + err = process_mp4data_line(buffer, lineCount, vidDat, &tim, txtDat); + + if (err < 0 ) + return err; + + if (err == 0 ) + return 0; + + // Check we're not at the end of the buffer. If the following + // statement is true and we haven't encountered an error then we've + // finished parsing the buffer + if (err == 1 ) { + // Not particularly happy with this code but it seems there's + // little consistency in the way these buffers end. This block + // catches all of those variations. The variations that indicate + // the end of a MP4 MIME text block are: + // + // 1. The amount of buffer parsed successfully is equal to the + // total buffer size + // + // 2. The buffer ends with two NULL characters + // + // 3. The buffer ends with the sequence \r\n\0 + + if (currentChar - mp4TextData == bufferSize ) + return 0; + + // CS - I *think* lines should end either when we've processed + // all the buffer OR it's padded with 0s + // Is detection of a NULL character here sufficient? + if (*currentChar == '\0' ) + return 0; + } + + lineCount++; + q = buffer; + } + else { + if ((q - buffer) < sizeof(buffer) - 1) + *q++ = ch; + } + } + + return 1; +} + +/** + * MPEG4 or H264 video frame with a MIME trailer + */ +static int admime_mpeg(AVFormatContext *s, + AVPacket *pkt, int size, long *extra, + struct NetVuImageData *vidDat, char **txtDat, + int adDataType) +{ + AVIOContext *pb = s->pb; + AdContext* adContext = s->priv_data; + int errorVal = 0; + int mimeBlockType = 0; + uint8_t buf[TEMP_BUFFER_SIZE]; + int bufSize = TEMP_BUFFER_SIZE; + + // Fields are set manually from MIME data with these types so need + // to set everything to zero initially in case some values aren't + // available + memset(vidDat, 0, sizeof(struct NetVuImageData)); + + // Allocate a new packet to hold the frame's image data + if (ad_new_packet(pkt, size) < 0 ) + return ADFFMPEG_AD_ERROR_MPEG4_MIME_NEW_PACKET; + + // Now read the frame data into the packet + if (avio_read( pb, pkt->data, size ) != size ) + return ADFFMPEG_AD_ERROR_MPEG4_MIME_GET_BUFFER; + + if (adContext->streamDatatype == 0) { + if (adDataType == AD_DATATYPE_H264I) + adContext->streamDatatype = PIC_MODE_H264I; + else if (adDataType == AD_DATATYPE_H264P) + adContext->streamDatatype = PIC_MODE_H264P; + else + adContext->streamDatatype = mpegOrH264(AV_RB32(pkt->data)); + } + vidDat->vid_format = adContext->streamDatatype; + + // Now we should have a text block following this which contains the + // frame data that we can place in a _image_data struct + if (parse_mime_header(pb, buf, &bufSize, &mimeBlockType, &size, extra ) != 0) + return ADFFMPEG_AD_ERROR_MPEG4_MIME_PARSE_HEADER; + + // Validate the data type and then extract the text buffer + if (mimeBlockType == DATA_PLAINTEXT ) { + unsigned char *textBuffer = av_malloc( size ); + + if (textBuffer != NULL ) { + if (avio_read( pb, textBuffer, size ) == size ) { + // Now parse the text buffer and populate the + // _image_data struct + if (parse_mp4_text_data(textBuffer, size, vidDat, txtDat ) != 0) { + av_free( textBuffer ); + return ADFFMPEG_AD_ERROR_MPEG4_MIME_PARSE_TEXT_DATA; + } + } + else { + av_free( textBuffer ); + return ADFFMPEG_AD_ERROR_MPEG4_MIME_GET_TEXT_BUFFER; + } + + av_free( textBuffer ); + } + else { + return AVERROR(ENOMEM); + } + } + return errorVal; +} + + +/** + * Audio frame + */ +static int ad_read_audio(AVFormatContext *s, + AVPacket *pkt, int size, long extra, + struct NetVuAudioData *audDat, + enum AVCodecID codec_id) +{ + AVIOContext *pb = s->pb; + + // No presentation information is sent with audio frames in a mime + // stream so there's not a lot we can do here other than ensure the + // struct contains the size of the audio data + audDat->sizeOfAudioData = size; + audDat->mode = extra; + audDat->seconds = 0; + audDat->msecs = 0; + audDat->channel = 0; + audDat->sizeOfAdditionalData = 0; + audDat->additionalData = NULL; + + if (ad_new_packet(pkt, size) < 0) + return ADFFMPEG_AD_ERROR_AUDIO_ADPCM_MIME_NEW_PACKET; + + // Now get the actual audio data + if (avio_read( pb, pkt->data, size) != size) + return ADFFMPEG_AD_ERROR_AUDIO_ADPCM_MIME_GET_BUFFER; + + if (codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) + audiodata_network2host(pkt->data, pkt->data, size); + + return 0; +} + + +/** + * Invalid mime header. + * + * However sometimes the header is missing and then there is a valid image so + * try and parse a frame out anyway. + */ +static int handleInvalidMime(AVFormatContext *s, + uint8_t *preRead, int preReadSize, AVPacket *pkt, + int *data_type, int *size, int *imgLoaded) +{ + AVIOContext *pb = s->pb; + int errorVal = 0; + unsigned char chkByte; + int status, read, found = FALSE; + uint8_t imageData[MAX_IMAGE_SIZE]; + + for(read = 0; read < preReadSize; read++) { + imageData[read] = preRead[read]; + } + + //Set the data type + *data_type = AD_DATATYPE_JFIF; + + // Read more data till we find end of image marker + for (; !found && (pb->eof_reached==0) && (pb->error==0); read++) { + if (read >= MAX_IMAGE_SIZE) + return ADFFMPEG_AD_ERROR_PARSE_MIME_HEADER; + + if (avio_read(pb, &chkByte, 1) < 0) + break; + imageData[read] = chkByte; + if(imageData[read - 1] == 0xFF && imageData[read] == 0xD9) + found = TRUE; + } + + *size = read; + if ((status = ad_new_packet(pkt, *size)) < 0) { + av_log(s, AV_LOG_ERROR, "handleInvalidMime: ad_new_packet (size %d)" + " failed, status %d\n", *size, status); + return ADFFMPEG_AD_ERROR_NEW_PACKET; + } + + memcpy(pkt->data, imageData, *size); + *imgLoaded = TRUE; + + return errorVal; +} + + +/** + * Identify if the stream as an AD MIME stream + */ +static int admime_probe(AVProbeData *p) +{ + int offset = 0; + int ii, matchedBytes = 0; + + if (p->buf_size <= sizeof(BOUNDARY_PREFIX1)) + return 0; + + // This is nasty but it's got to go here as we don't want to try and deal + // with fixes for certain server nuances in the HTTP layer. + // DS2 servers seem to end their HTTP header section with the byte sequence, + // 0x0d, 0x0a, 0x0d, 0x0a, 0x0a + // Eco9 server ends its HTTP headers section with the sequence, + // 0x0d, 0x0a, 0x0d, 0x0a, 0x0d, 0x0a + // Both of which are incorrect. We'll try and detect these cases here and + // make adjustments to the buffers so that a standard validation routine + // can be called... + + if (p->buf[0] == 0x0a ) // DS2 detection + offset = 1; + else if (p->buf[0] == 0x0d && p->buf[1] == 0x0a ) // Eco 9 detection + offset = 2; + + // Now check whether we have the start of a MIME boundary separator + if (is_valid_separator( &p->buf[offset], p->buf_size - offset ) > 0 ) + return AVPROBE_SCORE_MAX; + + // If server is only sending a single frame (i.e. fields=1) then it + // sometimes just sends the raw JPEG with no MIME or other header, check + // for this + if (p->buf_size >= sizeof(rawJfifHeader)) { + for(ii = 0; ii < sizeof(rawJfifHeader); ii++) { + if (p->buf[ii] == rawJfifHeader[ii]) + ++matchedBytes; + } + if (matchedBytes == sizeof(rawJfifHeader)) + return AVPROBE_SCORE_MAX; + } + + return 0; +} + +static int admime_read_header(AVFormatContext *s) +{ + return ad_read_header(s, NULL); +} + + +static int admime_read_packet(AVFormatContext *s, AVPacket *pkt) +{ + //AdContext* adContext = s->priv_data; + AVIOContext * pb = s->pb; + void * payload = NULL; + char * txtDat = NULL; + int data_type = AD_DATATYPE_MAX; + int size = -1; + long extra = 0; + int errorVal = ADFFMPEG_AD_ERROR_UNKNOWN; + enum AVMediaType mediaType = AVMEDIA_TYPE_UNKNOWN; + enum AVCodecID codecId = AV_CODEC_ID_NONE; + int imgLoaded = FALSE; + uint8_t buf[TEMP_BUFFER_SIZE]; + int bufSize = TEMP_BUFFER_SIZE; + unsigned char * tempbuf = NULL; + + errorVal = parse_mime_header(pb, buf, &bufSize, &data_type, &size, &extra); + if(errorVal != 0 ) { + if (errorVal == -2) + errorVal = handleInvalidMime(s, buf, bufSize, pkt, + &data_type, &size, &imgLoaded); + + if (errorVal < 0) { + return errorVal; + } + } + + // Prepare for video or audio read + errorVal = initADData(data_type, &mediaType, &codecId, &payload); + if (errorVal < 0) { + if (payload != NULL ) + av_free(payload); + return errorVal; + } + + // Proceed based on the type of data in this frame + switch(data_type) { + case AD_DATATYPE_JPEG: + errorVal = ad_read_jpeg(s, pkt, payload, &txtDat); + break; + case AD_DATATYPE_JFIF: + errorVal = ad_read_jfif(s, pkt, imgLoaded, size, payload, &txtDat); + break; + case AD_DATATYPE_MPEG4I: + case AD_DATATYPE_MPEG4P: + case AD_DATATYPE_H264I: + case AD_DATATYPE_H264P: + errorVal = admime_mpeg(s, pkt, size, &extra, payload, &txtDat, data_type); + break; + case AD_DATATYPE_AUDIO_ADPCM: + errorVal = ad_read_audio(s, pkt, size, extra, payload, AV_CODEC_ID_ADPCM_IMA_WAV); + break; + case AD_DATATYPE_INFO: + case AD_DATATYPE_XML_INFO: + case AD_DATATYPE_SVARS_INFO: + // May want to handle INFO, XML_INFO and SVARS_INFO separately in future + errorVal = ad_read_info(s, pkt, size); + break; + case AD_DATATYPE_LAYOUT: + errorVal = ad_read_layout(s, pkt, size); + break; + case AD_DATATYPE_PBM: + errorVal = ad_read_overlay(s, pkt, 1, size, &txtDat); + break; + case AD_DATATYPE_BMP: + default: { + av_log(s, AV_LOG_WARNING, "admime_read_packet: No handler for " + "data_type=%d\n", data_type); + + // Would like to use avio_skip, but that needs seek support, + // so just read the data into a buffer then throw it away + tempbuf = av_malloc(size); + if (tempbuf) { + avio_read(pb, tempbuf, size); + av_free(tempbuf); + } + else + return AVERROR(ENOMEM); + + return ADFFMPEG_AD_ERROR_DEFAULT; + } + break; + } + + if (errorVal >= 0) { + errorVal = ad_read_packet(s, pkt, 1, mediaType, codecId, payload, txtDat); + } + else { + // av_dlog(s, "admime_read_packet: Error %d\n", errorVal); + +#ifdef AD_SIDEDATA_IN_PRIV + // If there was an error, release any memory that has been allocated + if (payload != NULL) + av_free(payload); + + if (txtDat != NULL) + av_free( txtDat ); +#endif + } + +#ifndef AD_SIDEDATA_IN_PRIV + if (payload != NULL) + av_freep(&payload); + + if( txtDat != NULL ) + av_freep(&txtDat); +#endif + + return errorVal; +} + +static int admime_read_close(AVFormatContext *s) +{ + return 0; +} + + +AVInputFormat ff_admime_demuxer = { + .name = "admime", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings video format (MIME)"), + .priv_data_size = sizeof(AdContext), + .read_probe = admime_probe, + .read_header = admime_read_header, + .read_packet = admime_read_packet, + .read_close = admime_read_close, + .flags = AVFMT_TS_DISCONT | AVFMT_VARIABLE_FPS | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/adpic.h b/libavformat/adpic.h new file mode 100644 index 0000000000..c8a27a6d29 --- /dev/null +++ b/libavformat/adpic.h @@ -0,0 +1,116 @@ +/* + * Type information and function prototypes for common AD-Holdings demuxer code + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * Type information and function prototypes for common AD-Holdings demuxer code + */ + +#ifndef AVFORMAT_ADPIC_H +#define AVFORMAT_ADPIC_H + +#include "avformat.h" +#include "ds_exports.h" + + +#ifdef AD_SIDEDATA_IN_PRIV +int ad_new_packet(AVPacket *pkt, int size); +#else +#define ad_new_packet av_new_packet +#endif + + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/// These are the data types that are supported by the DS2 video servers +enum ff_ad_data_type { AD_DATATYPE_JPEG = 0, + AD_DATATYPE_JFIF, + AD_DATATYPE_MPEG4I, + AD_DATATYPE_MPEG4P, + AD_DATATYPE_AUDIO_ADPCM, + AD_DATATYPE_AUDIO_RAW, + AD_DATATYPE_MINIMAL_MPEG4, + AD_DATATYPE_MINIMAL_AUDIO_ADPCM, + AD_DATATYPE_LAYOUT, + AD_DATATYPE_INFO, + AD_DATATYPE_H264I, + AD_DATATYPE_H264P, + AD_DATATYPE_XML_INFO, + AD_DATATYPE_BMP, + AD_DATATYPE_PBM, + AD_DATATYPE_SVARS_INFO, + AD_DATATYPE_MAX + }; + +typedef struct { + int64_t lastVideoPTS; + int utc_offset; ///< Only used in minimal video case + int metadataSet; + enum ff_ad_data_type streamDatatype; +} AdContext; + + +int ad_read_header(AVFormatContext *s, int *utcOffset); +void ad_network2host(struct NetVuImageData *pic, uint8_t *data); +int initADData(int data_type, enum AVMediaType *media, enum AVCodecID *codecId, void **payload); +int ad_read_jpeg(AVFormatContext *s, AVPacket *pkt, struct NetVuImageData *vid, char **txt); +int ad_read_jfif(AVFormatContext *s, AVPacket *pkt, int manual_size, int size, + struct NetVuImageData *video_data, char **text_data); +int ad_read_info(AVFormatContext *s, AVPacket *pkt, int size); +int ad_read_layout(AVFormatContext *s, AVPacket *pkt, int size); +int ad_read_overlay(AVFormatContext *s, AVPacket *pkt, int channel, int size, char **text_data); +int ad_read_packet(AVFormatContext *s, AVPacket *pkt, int channel, + enum AVMediaType mediaType, enum AVCodecID codecId, + void *data, char *text_data); +AVStream * ad_get_vstream(AVFormatContext *s, uint16_t w, uint16_t h, + uint8_t cam, int format, const char *title); +AVStream * ad_get_audio_stream(AVFormatContext *s, struct NetVuAudioData* audioHeader); +void audiodata_network2host(uint8_t *data, const uint8_t *src, int size); +int ad_adFormatToCodecId(AVFormatContext *s, int32_t adFormat); +int mpegOrH264(unsigned int startCode); +int ad_pbmDecompress(char **comment, uint8_t **src, int size, AVPacket *pkt, int *width, int *height); + + +#define PIC_REVISION 1 +#define MIN_PIC_VERSION 0xDECADE10 +#define MAX_PIC_VERSION (MIN_PIC_VERSION + PIC_REVISION) +#define PIC_VERSION (MIN_PIC_VERSION + PIC_REVISION) +#define pic_version_valid(v) ( ( (v)>=MIN_PIC_VERSION ) && ( (v)<=MAX_PIC_VERSION ) ) + +#define AUD_VERSION 0x00ABCDEF + +#define PIC_MODE_JPEG_422 0 +#define PIC_MODE_JPEG_411 1 +#define PIC_MODE_MPEG4_411 2 +#define PIC_MODE_MPEG4_411_I 3 +#define PIC_MODE_MPEG4_411_GOV_P 4 +#define PIC_MODE_MPEG4_411_GOV_I 5 +#define PIC_MODE_H264I 6 +#define PIC_MODE_H264P 7 +#define PIC_MODE_H264J 8 + +#endif diff --git a/libavformat/adraw.c b/libavformat/adraw.c new file mode 100644 index 0000000000..c689ce4f56 --- /dev/null +++ b/libavformat/adraw.c @@ -0,0 +1,131 @@ +/* + * AD-Holdings demuxer for AD stream format (raw) + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * AD-Holdings demuxer for AD stream format (raw) + */ + +#include "avformat.h" +#include "adpic.h" + + +/** + * Identify if the stream as an AD raw stream + */ +static int adraw_probe(AVProbeData *p) +{ + int bufferSize = p->buf_size; + uint8_t *bufPtr = p->buf; + + while (bufferSize >= NetVuImageDataHeaderSize) { + struct NetVuImageData test; + ad_network2host(&test, bufPtr); + if (pic_version_valid(test.version)) { + if (ad_adFormatToCodecId(NULL, test.vid_format) == AV_CODEC_ID_MJPEG) + return AVPROBE_SCORE_MAX / 2; + } + --bufferSize; + ++bufPtr; + } + return 0; +} + +static int adraw_read_header(AVFormatContext *s) +{ + return ad_read_header(s, NULL); +} + +static int adraw_read_packet(struct AVFormatContext *s, AVPacket *pkt) +{ + AVIOContext *pb = s->pb; + uint8_t *buf = NULL; + struct NetVuImageData *vidDat = NULL; + char *txtDat = NULL; + int errVal = 0; + int ii = 0; + + vidDat = av_malloc(sizeof(struct NetVuImageData)); + buf = av_malloc(sizeof(struct NetVuImageData)); + + if (!vidDat || !buf) + return AVERROR(ENOMEM); + + // Scan for 0xDECADE11 marker + errVal = avio_read(pb, buf, sizeof(struct NetVuImageData)); + while (errVal > 0) { + ad_network2host(vidDat, buf); + if (pic_version_valid(vidDat->version)) { + break; + } + for(ii = 0; ii < (sizeof(struct NetVuImageData) - 1); ii++) { + buf[ii] = buf[ii+1]; + } + errVal = avio_read(pb, buf + sizeof(struct NetVuImageData) - 1, 1); + } + av_free(buf); + + if (errVal > 0) { + switch (ad_adFormatToCodecId(s, vidDat->vid_format)) { + case(AV_CODEC_ID_MJPEG): + errVal = ad_read_jpeg(s, pkt, vidDat, &txtDat); + break; + case(AV_CODEC_ID_MPEG4): + case(AV_CODEC_ID_H264): + default: + //errVal = adbinary_mpeg(s, pkt, vidDat, &txtDat); + av_log(s, AV_LOG_ERROR, "Unsupported format for adraw demuxer: " + "%d\n", vidDat->vid_format); + break; + } + } + if (errVal >= 0) { + errVal = ad_read_packet(s, pkt, 1, AVMEDIA_TYPE_VIDEO, AV_CODEC_ID_MJPEG, + vidDat, txtDat); + } + else { + // If there was an error, release any allocated memory + if( vidDat != NULL ) + av_free( vidDat ); + + if( txtDat != NULL ) + av_free( txtDat ); + } + + return errVal; +} + +static int adraw_read_close(AVFormatContext *s) +{ + return 0; +} + + +AVInputFormat ff_adraw_demuxer = { + .name = "adraw", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings video format (raw)"), + .read_probe = adraw_probe, + .read_header = adraw_read_header, + .read_packet = adraw_read_packet, + .read_close = adraw_read_close, + .flags = AVFMT_TS_DISCONT | AVFMT_VARIABLE_FPS | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/allformats.c b/libavformat/allformats.c index cd00834807..6988c87414 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -35,8 +35,12 @@ extern AVInputFormat ff_ac3_demuxer; extern AVOutputFormat ff_ac3_muxer; extern AVInputFormat ff_acm_demuxer; extern AVInputFormat ff_act_demuxer; +extern AVInputFormat ff_adaudio_demuxer; +extern AVInputFormat ff_adbinary_demuxer; extern AVInputFormat ff_adf_demuxer; +extern AVInputFormat ff_admime_demuxer; extern AVInputFormat ff_adp_demuxer; +extern AVInputFormat ff_adraw_demuxer; extern AVInputFormat ff_ads_demuxer; extern AVOutputFormat ff_adts_muxer; extern AVInputFormat ff_adx_demuxer; @@ -117,6 +121,7 @@ extern AVInputFormat ff_dnxhd_demuxer; extern AVOutputFormat ff_dnxhd_muxer; extern AVInputFormat ff_dsf_demuxer; extern AVInputFormat ff_dsicin_demuxer; +extern AVInputFormat ff_dspic_demuxer; extern AVInputFormat ff_dss_demuxer; extern AVInputFormat ff_dts_demuxer; extern AVOutputFormat ff_dts_muxer; @@ -494,6 +499,8 @@ extern AVOutputFormat ff_chromaprint_muxer; extern AVInputFormat ff_libgme_demuxer; extern AVInputFormat ff_libmodplug_demuxer; extern AVInputFormat ff_libopenmpt_demuxer; +extern AVInputFormat ff_libparreader_demuxer; +extern AVOutputFormat ff_libparreader_muxer; extern AVInputFormat ff_vapoursynth_demuxer; #include "libavformat/muxer_list.c" diff --git a/libavformat/ds.c b/libavformat/ds.c new file mode 100644 index 0000000000..4765c51b67 --- /dev/null +++ b/libavformat/ds.c @@ -0,0 +1,1262 @@ +/* + * Protocol for AD-Holdings Digital Sprite stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 "url.h" +#include "libavutil/avstring.h" +#include "libavutil/bswap.h" + +#include "adffmpeg_errors.h" +#include "dsenc.h" +#include "ds.h" + +/* -------------------------------------- Constants -------------------------------------- */ +#define DS_DEFAULT_PORT 8234 /* Deafult TCP and UDP port for comms */ +#define DS_HEADER_MAGIC_NUMBER 0xFACED0FF +#define MAX_USER_ID_LENGTH 30 +#define ACCESS_KEY_LENGTH 36 +#define MAC_ADDR_LENGTH 16 +#define UNIT_NAME_LENGTH 32 + +#define DS_WRITE_BUFFER_SIZE 1024 +#define DS_READ_BUFFER_SIZE 1024 + +#define DS_PLAYBACK_MODE_LIVE 0x00 +#define DS_PLAYBACK_MODE_PLAY 0x01 + +#define DS_RESOLUTION_HI 2 +#define DS_RESOLUTION_MED 1 +#define DS_RESOLUTION_LOW 0 + + +#define SET_FLAG_ZONE_UNKNOWN(flags) ((flags) |= 0x80) + +/* -------------------------------------- Structures/types -------------------------------------- */ +typedef struct _dsContext { + URLContext * TCPContext; /* Context of the underlying TCP network connection */ +} DSContext; + +typedef struct _networkMessage { + MessageHeader header; + void * body; +} NetworkMessage; + +typedef enum _imgControlMsgType { + IMG_LIVE, + IMG_PLAY, + IMG_GOTO, + IMG_DATA, + IMG_STOP +} ImgControlMsgType; + +typedef struct _clientConnectMsg { + unsigned long udpPort; + long connectType; /* ImgControlMsgType enum. long used to avoid sizeof(enum) discrepancies */ + char userID[MAX_USER_ID_LENGTH]; + char accessKey[ACCESS_KEY_LENGTH]; +} ClientConnectMsg; +#define VER_TCP_CLI_CONNECT 0x00000003 +#define SIZEOF_TCP_CLI_CONNECT_IO (MAX_USER_ID_LENGTH + ACCESS_KEY_LENGTH + 8) /* Size in bytes of the MessageHeader structure. Can't use sizeof to read/write one of these to network as structure packing may differ based on platform */ + +typedef enum _netRejectReason { + REJECT_BUSY, + REJECT_INVALID_USER_ID, + REJECT_AUTHENTIFICATION_REQUIRED, + REJECT_AUTHENTIFICATION_INVALID, + REJECT_UNAUTHORISED, + REJECT_OTHER, + REJECT_PASSWORD_CHANGE_REQUIRED, + REJECT_OUT_OF_MEMORY, + REJECT_CORRUPT_USERS_FILES +} NetRejectReason; + +typedef struct _srvConnectRejectMsg { + long reason; /* enum NET_REJECT_REASON */ + long timestamp; + char macAddr[MAC_ADDR_LENGTH]; + unsigned long appVersion; + unsigned long minViewerVersion; +} SrvConnectRejectMsg; +#define VER_TCP_SRV_CONNECT_REJECT 0x00000001 + +typedef enum _realmType { + REALM_LIVE, + REALM_PLAYBACK, + REALM_TELEM, + REALM_EVENTS, + REALM_ADMIN, + REALM_PASSWORD, + REALM_PW_ONCE, + REALM_VIEW_ALL, + REALM_MCI, + REALM_FILE_EXPORT, + REALM_WEB, + REALM_POS, + NUM_FIXED_REALMS, +} RealmType; + +typedef struct _srvConnectReplyMsg { + long numCameras; + long viewableCamMask; + long telemetryCamMask; + long failedCamMask; + long maxMsgInterval; + int64_t timestamp; /* TODO: Verify - this was a 'hyper long' before. Assuming 64 bit value. Is this correct? Is solution portable? */ + char cameraTitles[16][28]; + long unitType; + unsigned long applicationVersion; + long videoStandard; + char macAddr[MAC_ADDR_LENGTH]; + char unitName[UNIT_NAME_LENGTH]; + long numFixedRealms; /* Number of FIXED system realms */ + unsigned long realmFlags[NUM_FIXED_REALMS]; /* Indicates if user is in realm. */ + unsigned long minimumViewerVersion; +} SrvConnectReplyMsg; +#define VER_TCP_SRV_CONNECT_REPLY 0x00000001 + +typedef struct _srvFeatureConnectReplyMsg { + long numCameras; + long viewableCamMask; + long telemetryCamMask; + long failedCamMask; + long maxMsgInterval; + int64_t timestamp; /* TODO: Verify - this was a 'hyper long' before. Assuming 64 bit value. Is this correct? Is solution portable? */ + char cameraTitles[16][28]; + long unitType; + unsigned long applicationVersion; + long videoStandard; + char macAddr[MAC_ADDR_LENGTH]; + char unitName[UNIT_NAME_LENGTH]; + + unsigned long minimumViewerVersion; + unsigned long unitFeature01; + unsigned long unitFeature02; + unsigned long unitFeature03; + unsigned long unitFeature04; + long numFixedRealms; /* Number of FIXED system realms */ + unsigned long realmFlags[NUM_FIXED_REALMS]; /* Indicates if user is in realm. */ +} SrvFeatureConnectReplyMsg; +#define VER_TCP_SRV_FEATURE_CONNECT_REPLY 0x00000002 + +typedef struct _cliImgLiveRequestMsg { + long cameraMask; + long resolution; +} CliImgLiveRequestMsg; +#define VER_TCP_CLI_IMG_LIVE_REQUEST 0x00000001 +#define SIZEOF_TCP_CLI_IMG_LIVE_REQUEST_IO 8 /* Size in bytes of the MessageHeader structure. Can't use sizeof to read/write one of these to network as structure packing may differ based on platform */ + +typedef enum _vcrMode { + VM_PLAY, + VM_VIS_REW, + VM_VIS_FF, + VM_STOP, + VM_PLAY_SHUTTLE, + VM_FINISH +} vcrMode; + +typedef struct _cliImgPlayRequestMsg { + long cameraMask; + long mode; /* (enum VCR_MODE) */ + long pace; + int64_t fromTime; /* (time_u) */ + int64_t toTime; /* (time_u) */ +} CliImgPlayRequestMsg; +#define VER_TCP_CLI_IMG_PLAY_REQUEST 0x00000001 +#define SIZEOF_TCP_CLI_IMG_PLAY_REQUEST_IO 28 /* Size in bytes of the MessageHeader structure. Can't use sizeof to read/write one of these to network as structure packing may differ based on platform */ + + +/* -------------------------------------- Local function declarations -------------------------------------- */ +static NetworkMessage * CreateNetworkMessage( ControlMessageTypes messageType, long channelID ); +static void FreeNetworkMessage( NetworkMessage **message ); + +static int DSOpen( URLContext *h, const char *uri, int flags ); +static int DSRead( URLContext *h, uint8_t *buf, int size ); +static int DSWrite( URLContext *h, const uint8_t *buf, int size ); +static int DSClose( URLContext *h ); +static int DSConnect( URLContext *h, const char *path, const char *hoststr, const char *auth ); +static inline int MessageSize( const NetworkMessage *message ); + +static int ReceiveNetworkMessage( URLContext *h, NetworkMessage **message ); +static int ReadNetworkMessageHeader( URLContext *h, MessageHeader *header ); +static int ReadNetworkMessageBody( URLContext * h, NetworkMessage *message ); + +static int SendNetworkMessage( URLContext *h, NetworkMessage *message ); +static void HToNMessageHeader( MessageHeader *header, unsigned char *buf ); + +static int ReadConnectRejectMessage( URLContext * h, NetworkMessage *message ); +static int ReadConnectReplyMessage( URLContext * h, NetworkMessage *message ); +static int ReadFeatureConnectReplyMessage( URLContext * h, NetworkMessage *message ); + +static int GetUserAndPassword( const char * auth, char *user, char *password ); +static int CrackURI( const char *path, int *streamType, int *res, int *cam, time_t *from, time_t *to, int *rate, vcrMode *playMode ); +static int DSReadBuffer( URLContext *h, uint8_t *buffer, int size ); +static int64_t TimeTolong64( time_t time ); +static void NToHMessageHeader( MessageHeader *header ); + +#if HAVE_BIGENDIAN +#define HTON64(x) x +#define HTON32(x) x +#else +#define HTON64(x) (av_bswap64(x)) +#define HTON32(x) (av_bswap32(x)) +#endif + + +/**************************************************************************************************************** + * Function: CreateNetworkMessage + * Desc: Allocates and initialises a NetworkMessage for sending to the server based on the given type and channel. + * It is the caller's responsibility to release the NetworkMessage created by this function. This should be + * done with the FreeNetworkMessage function. + * Params: + * messageType - Type of the message to create + * channelID - Channel for which the message is intended + * Return: + * Pointer to new network message on success. NULL on failure + ****************************************************************************************************************/ +static NetworkMessage * CreateNetworkMessage( ControlMessageTypes messageType, long channelID ) +{ + NetworkMessage * newMessage = NULL; + int length = 0; + int version = 0; + + /* Create a new message structure */ + newMessage = av_mallocz( sizeof(NetworkMessage) ); + + if( newMessage != NULL ) { + /* Now allocate the body structure if we've been successful */ + switch( messageType ) { + /* In normal cases, set whatever member variables we can in here also */ + case TCP_CLI_CONNECT: { + if( (newMessage->body = av_mallocz( sizeof(ClientConnectMsg) )) != NULL ) { + length = SIZEOF_TCP_CLI_CONNECT_IO; + version = VER_TCP_CLI_CONNECT; + } + else + goto fail; + } + break; + + case TCP_CLI_IMG_LIVE_REQUEST: { + if( (newMessage->body = av_mallocz( sizeof(CliImgLiveRequestMsg) )) != NULL ) { + length = SIZEOF_TCP_CLI_IMG_LIVE_REQUEST_IO; + version = VER_TCP_CLI_IMG_LIVE_REQUEST; + } + else + goto fail; + } + break; + + case TCP_CLI_IMG_PLAY_REQUEST: { + if( (newMessage->body = av_mallocz( sizeof(CliImgPlayRequestMsg) )) != NULL ) { + length = SIZEOF_TCP_CLI_IMG_PLAY_REQUEST_IO; + version = VER_TCP_CLI_IMG_PLAY_REQUEST; + } + else + goto fail; + } + break; + + default: { /* Unknown (or unsupported) message type encountered */ + goto fail; + } + break; + } + + /* Set whatever header values we can in here */ + newMessage->header.messageType = messageType; + newMessage->header.magicNumber = DS_HEADER_MAGIC_NUMBER; + newMessage->header.channelID = channelID; + newMessage->header.sequence = 0; /* Currently unsupported at server */ + newMessage->header.checksum = 0; /* As suggested in protocol documentation */ + newMessage->header.messageVersion = version; + + /* Set the length of the remaining data (size of the message header - the magic number + size of the message body) */ + newMessage->header.length = SIZEOF_MESSAGE_HEADER_IO - sizeof(unsigned long) + length; + } + + return newMessage; + +fail: + /* Release whatever may have been allocated */ + FreeNetworkMessage( &newMessage ); + + return NULL; +} + +/**************************************************************************************************************** + * Function: FreeNetworkMessage + * Desc: Releases the resources associated with a network message created with CreateNetworkMessage() + * Params: + * message - Address of a pointer to a NetworkMessage struct allocated with CreateNetworkMessage + * Return: + ****************************************************************************************************************/ +static void FreeNetworkMessage( NetworkMessage **message ) +{ + /* Simply cascade free all memory allocated */ + if( *message ) { + if( (*message)->body ) { + av_free( (*message)->body ); + } + + av_free( *message ); + *message = NULL; + } +} + +/**************************************************************************************************************** + * Function: DSOpen + * Desc: Opens a connection to a DM Digital Sprite video server and executes the initial connect transaction + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * uri - The URI indicating the server's location + * flags - Flags indicating the type of connection required (URL_WRONLY, etc) + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int DSOpen( URLContext *h, const char *uri, int flags ) +{ + char hostname[1024], hoststr[1024]; + char auth[1024]; + char path1[1024]; + char buf[1024]; + int port, err; + const char * path; + URLContext * TCPContext = NULL; + DSContext * s = NULL; + + h->is_streamed = 1; + + s = av_malloc( sizeof(DSContext) ); + if (!s) { + return -ENOMEM; + } + h->priv_data = s; + + /* Crack the URL */ + av_url_split( NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, path1, sizeof(path1), uri ); + + if (port > 0) { + snprintf( hoststr, sizeof(hoststr), "%s:%d", hostname, port ); + } + else { + av_strlcpy( hoststr, hostname, sizeof(hoststr) ); + } + + /* Add the URL parameters (if any) */ + if (path1[0] == '\0') { + path = "/"; + } + else { + path = path1; + } + + /* Assume default port for this protocol if one isn't supplied */ + if (port < 0) { + port = DS_DEFAULT_PORT; + } + + /* Form the appropriate TCP URL */ + snprintf(buf, sizeof(buf), "tcp://%s:%d", hostname, port); + + /* Now open a connection to that TCP address */ + if( (err = ffurl_open(&TCPContext, buf, AVIO_FLAG_READ_WRITE, &TCPContext->interrupt_callback, NULL)) < 0 ) { + goto fail; + } + + /* Save the TCP context */ + s->TCPContext = TCPContext; + + /* Now initiate connection using the DS protocol */ + if( (err = DSConnect( h, path, hoststr, auth )) < 0 ) { + goto fail; + } + + return 0; +fail: + if( TCPContext ) { + ffurl_close( TCPContext ); + } + + av_free( s ); + + return err; +} + +/**************************************************************************************************************** + * Function: DSRead + * Desc: Reads data from the connection + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * buf - Buffer to which read data will be written + * size - Number of bytes to read into buf + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int DSRead( URLContext *h, uint8_t *buf, int size ) +{ + /* All we need to do in here is call the generic read function on our underlying TCP connection */ + DSContext * context = (DSContext *)h->priv_data; + + if( context != NULL ) + return ffurl_read( context->TCPContext, buf, size ); + + return AVERROR(EIO); +} + +/**************************************************************************************************************** + * Function: DSWrite + * Desc: Writes data to the connection + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * buf - Buffer from which data will be written + * size - Number of bytes to write from buf + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int DSWrite( URLContext *h, const uint8_t *buf, int size ) +{ + /* All we need to do in here is call the generic write function on our underlying TCP connection */ + DSContext * context = (DSContext *)h->priv_data; + + if( context != NULL ) + return ffurl_write( context->TCPContext, buf, size ); + + return AVERROR(EIO); +} + +/**************************************************************************************************************** + * Function: DSClose + * Desc: Closes the connection to the DM Digital Sprite video server + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int DSClose( URLContext *h ) +{ + DSContext * context = (DSContext *)h->priv_data; + + if( context != NULL ) { + ffurl_close( context->TCPContext ); + + av_free( context ); + } + + return 0; +} + +/**************************************************************************************************************** + * Function: MessageSize + * Desc: Calculates the size in bytes of the given NetworkMessage + * Params: + * message - Pointer to the message for which the size is required + * Return: + * The size of the message, -1 if no message passed + ****************************************************************************************************************/ +static inline int MessageSize( const NetworkMessage *message ) +{ + if( message ) + return message->header.length + sizeof(unsigned long); + + return -1; +} + +/**************************************************************************************************************** + * Function: DSConnect + * Desc: Attempts to connect to the DM Digital Sprite video server according to its connection protocol + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * path - TODO: Need to confirm whether these params are actually needed + * hoststr - + * auth - Authentication credentials + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int DSConnect( URLContext *h, const char *path, const char *hoststr, const char *auth ) +{ + NetworkMessage * sendMessage = NULL; + NetworkMessage * recvMessage = NULL; + int retVal = 0; + int isConnecting = 1; + char * encCredentials = NULL; + ClientConnectMsg * connectMsg = NULL; + int channelID = -2; + int streamType = 0, res = 0, cam = 0; + time_t from = 0, to = 0; + int rate = 0; + vcrMode playMode = VM_PLAY; + char user[MAX_USER_ID_LENGTH]; + char password[MAX_USER_ID_LENGTH]; + + /* Initialise the user and password fields */ + memset( user, 0, sizeof(char) * MAX_USER_ID_LENGTH ); + memset( password, 0, sizeof(char) * MAX_USER_ID_LENGTH ); + + /* Extract the username and password */ + if( (retVal = GetUserAndPassword( auth, user, password )) == 0 ) { + /* Crack the URI to get the control parameters */ + retVal = CrackURI( path, &streamType, &res, &cam, &from, &to, &rate, &playMode ); + } + + while( isConnecting && retVal == 0 ) { + if( (sendMessage = CreateNetworkMessage( TCP_CLI_CONNECT, channelID )) == NULL ) { + if( encCredentials != NULL ) + av_free( encCredentials ); + + return AVERROR(ENOMEM); + } + + /* Set up the connection request */ + /* Set the message body up now */ + connectMsg = (ClientConnectMsg *)sendMessage->body; + + connectMsg->udpPort = DS_DEFAULT_PORT; + + if( streamType == DS_PLAYBACK_MODE_PLAY ) + connectMsg->connectType = IMG_PLAY; + else + connectMsg->connectType = IMG_LIVE; + + if( encCredentials != NULL ) { + memcpy( connectMsg->userID, user, strlen(user) ); + memcpy( connectMsg->accessKey, encCredentials, strlen(encCredentials) ); + } + + /* Send the message to the server */ + if( (retVal = SendNetworkMessage( h, sendMessage )) >= 0 ) { + /* Receive the response */ + if( (retVal = ReceiveNetworkMessage( h, &recvMessage )) >= 0 ) { + switch( recvMessage->header.messageType ) { + case TCP_SRV_CONNECT_REJECT: { /* We expect this first time */ + /* Extract the info we need to encrypt */ + SrvConnectRejectMsg * msg = (SrvConnectRejectMsg *)recvMessage->body; + + /* What was the reason for the failure? */ + if( msg->reason == REJECT_AUTHENTIFICATION_REQUIRED ) { + channelID = recvMessage->header.channelID; + + /* Encrypt the username / password */ + if( strlen( user ) > 0 && strlen( password ) > 0 ) { + if( (encCredentials = EncryptPasswordString( user, password, msg->timestamp, msg->macAddr, msg->appVersion )) == NULL ) + retVal = AVERROR(ENOMEM); + } + else { + /* If we haven't got a user and password string then we have to notify the client */ + retVal = ADFFMPEG_DS_ERROR_AUTH_REQUIRED; + } + } + else if( msg->reason == REJECT_AUTHENTIFICATION_INVALID ) { /* Supplied credentials are invalid */ + retVal = ADFFMPEG_DS_ERROR_INVALID_CREDENTIALS; + } + else { /* Fail */ + retVal = AVERROR(EIO); + isConnecting = 0; + } + } + break; + + case TCP_SRV_FEATURE_CONNECT_REPLY: + case TCP_SRV_CONNECT_REPLY: { + /* Great, we're connected - we just need to send a IMG_LIVE_REQUEST to the server to start the streaming */ + NetworkMessage * imgRequestMsg = NULL; + + if( streamType == DS_PLAYBACK_MODE_LIVE ) { + if( (imgRequestMsg = CreateNetworkMessage( TCP_CLI_IMG_LIVE_REQUEST, channelID )) ) { + CliImgLiveRequestMsg * msgBody = (CliImgLiveRequestMsg *)imgRequestMsg->body; + + msgBody->cameraMask = cam; + msgBody->resolution = res; + } + } + else if( streamType == DS_PLAYBACK_MODE_PLAY ) { + if( (imgRequestMsg = CreateNetworkMessage( TCP_CLI_IMG_PLAY_REQUEST, channelID )) ) { + CliImgPlayRequestMsg * msgBody = (CliImgPlayRequestMsg *)imgRequestMsg->body; + + msgBody->cameraMask = cam; + msgBody->fromTime = TimeTolong64( from ); + msgBody->toTime = TimeTolong64( to ); + msgBody->pace = rate; + msgBody->mode = playMode; + } + } + else + retVal = AVERROR(EIO); + + if( retVal == 0 ) { + /* Fire the request message off */ + retVal = SendNetworkMessage( h, imgRequestMsg ); + } + + isConnecting = 0; + } + break; + + /* Anything other than a connect reply is failure */ + default: { + retVal = -1; + isConnecting = 0; + } + break; + } + } + else + isConnecting = 0; + } + else + isConnecting = 0; + + + /* We can release the messages now */ + FreeNetworkMessage( &sendMessage ); + FreeNetworkMessage( &recvMessage ); + } + + return retVal; +} + +static int GetUserAndPassword( const char * auth, char *user, char *password ) +{ + int retVal = 0; + char * authStr = NULL; + char * token = NULL; + const char * delim = ":"; + int count = 0; + + if( auth != NULL ) { + if( strcmp( auth, "" ) != 0 ) { + retVal = AVERROR_INVALIDDATA; + + /* Copy the string as strtok needs to modify as it goes */ + if( (authStr = (char*)av_mallocz( strlen(auth) + 1 )) != NULL ) { + strcpy( authStr, auth ); + + /* Now split it into the user and password */ + token = strtok( authStr, delim ); + + while( token != NULL ) { + count++; + + if( count == 1 ) { + if( strlen(token) <= MAX_USER_ID_LENGTH ) { + strcpy( user, token ); + + if( strlen(token) < MAX_USER_ID_LENGTH ) + user[strlen(token)] = '\0'; + } + } + else if( count == 2 ) { + /* TODO: Verify whether checking against the length of the max user id is ok. Ultimately, the password is hashed before transmission + so the length is not imperative here. Maybe server defines a maximum length though? */ + if( strlen(token) <= MAX_USER_ID_LENGTH ) { + strcpy( password, token ); + + if( strlen(token) < MAX_USER_ID_LENGTH ) + password[strlen(token)] = '\0'; + + retVal = 0; + } + } + else { /* There shouldn't be more than 2 tokens, better flag an error */ + retVal = AVERROR_INVALIDDATA; + } + + token = strtok( NULL, delim ); + } + + av_free( authStr ); + authStr = NULL; + } + else + retVal = AVERROR(ENOMEM); + } + } + + return retVal; +} + +static int CrackURI( const char *path, int *streamType, int *res, int *cam, time_t *from, time_t *to, int *rate, vcrMode *playMode ) +{ + int retVal = AVERROR_INVALIDDATA; + const char * delim = "?&"; + char * pathStr = NULL; + char * token = NULL; + + *res = DS_RESOLUTION_HI; + *streamType = DS_PLAYBACK_MODE_LIVE; + *cam = 1; + + if( path != NULL ) { + /* Take a copy of the path string so that strtok can modify it */ + if( (pathStr = (char*)av_mallocz( strlen(path) + 1 )) != NULL ) { + strcpy( pathStr, path ); + + retVal = 0; + + token = strtok( pathStr, delim ); + + while( token != NULL ) { + char * name = NULL; + char * value = NULL; + + /* Now look inside this token string for a name value pair separated by an = */ + if( (value = strstr(token, "=")) != NULL ) { + value++; + + name = token; + name[(value-1) - token] = '\0'; + + if( name != NULL && value != NULL ) { + /* Which parameter have we got? */ + if( av_strcasecmp(name, "res" ) == 0 ) { + if( strcmp( value, "hi" ) == 0 ) + *res = DS_RESOLUTION_HI; + else if( strcmp( value, "med" ) == 0 ) + *res = DS_RESOLUTION_MED; + else if( strcmp( value, "low" ) == 0 ) + *res = DS_RESOLUTION_LOW; + } + else if( av_strcasecmp(name, "stream" ) == 0 ) { + if( av_strcasecmp(value, "live" ) == 0 ) + *streamType = DS_PLAYBACK_MODE_LIVE; + else if( av_strcasecmp(value, "play" ) == 0 ) + *streamType = DS_PLAYBACK_MODE_PLAY; + } + else if( av_strcasecmp(name, "cam" ) == 0 ) { + *cam = atoi( value ); + } + else if( av_strcasecmp(name, "from" ) == 0 ) { + *from = (time_t)atoi( value ); + } + else if( av_strcasecmp(name, "to" ) == 0 ) { + *to = (time_t)atoi( value ); + } + else if( av_strcasecmp(name, "rate" ) == 0 ) { + *rate = atoi( value ); + } + else if( av_strcasecmp(name, "mode" ) == 0 ) { + if( av_strcasecmp(value, "play" ) == 0 ) + *playMode = VM_PLAY; + else if( av_strcasecmp(value, "rwd" ) == 0 ) + *playMode = VM_VIS_REW; + else if( av_strcasecmp(value, "fwd" ) == 0 ) + *playMode = VM_VIS_FF; + else if( av_strcasecmp(value, "stop" ) == 0 ) + *playMode = VM_STOP; + else if( av_strcasecmp(value, "shuttle" ) == 0 ) + *playMode = VM_PLAY_SHUTTLE; + else if( av_strcasecmp(value, "finish" ) == 0 ) + *playMode = VM_FINISH; + } + } + } + + token = strtok( NULL, delim ); + } + + av_free( pathStr ); + pathStr = NULL; + } + else + retVal = AVERROR(ENOMEM); + } + + return retVal; +} + +/**************************************************************************************************************** + * Function: ReceiveNetworkMessage + * Desc: Reads the next NetworkMessage from the connection. New message is allocated and must be release by the + * caller with FreeNetworkMessage() + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * message - Address of pointer to network message. This will be allocated by this function + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int ReceiveNetworkMessage( URLContext *h, NetworkMessage **message ) +{ + int retVal = 0; + + /* Allocate a new NetworkMessage struct */ + *message = av_mallocz( sizeof(NetworkMessage) ); + + if( *message == NULL ) + return AVERROR(ENOMEM); + + if( (retVal = ReadNetworkMessageHeader( h, &(*message)->header )) != 0 ) { + FreeNetworkMessage( message ); + return retVal; + } + + if( (retVal = ReadNetworkMessageBody( h, *message )) != 0 ) { + FreeNetworkMessage( message ); + return retVal; + } + + return 0; +} + +static int DSReadBuffer( URLContext *h, uint8_t *buffer, int size ) +{ + int ret; + int totalRead = 0; + + if( buffer != NULL && size > 0 ) { + while( size - totalRead != 0 ) { + ret = DSRead( h, buffer, size - totalRead ); + + if( ret < 0 ) + return ret; + else + totalRead += ret; + } + } + + return totalRead; +} + +static int ReadNetworkMessageHeader( URLContext *h, MessageHeader *header ) +{ + /* Read the header in a piece at a time... */ + if( DSReadBuffer( h, (uint8_t *)&header->magicNumber, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->length, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->channelID, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->sequence, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->messageVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->checksum, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&header->messageType, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + /* Now adjust the endianess */ + NToHMessageHeader( header ); + + return 0; +} + +static void NToHMessageHeader( MessageHeader *header ) +{ + if( header ) { + header->magicNumber = av_be2ne32(header->magicNumber); + header->length = av_be2ne32(header->length); + header->channelID = av_be2ne32(header->channelID); + header->sequence = av_be2ne32(header->sequence); + header->messageVersion = av_be2ne32(header->messageVersion); + header->checksum = av_be2ne32(header->checksum); + header->messageType = av_be2ne32(header->messageType); + } +} + +static int ReadNetworkMessageBody( URLContext * h, NetworkMessage *message ) +{ + int retVal = 0; + + if( message != NULL && message->body == NULL ) { + /* Read based on the type of message we have */ + switch( message->header.messageType ) { + case TCP_SRV_CONNECT_REJECT: { + retVal = ReadConnectRejectMessage( h, message ); + } + break; + + case TCP_SRV_FEATURE_CONNECT_REPLY: { + retVal = ReadFeatureConnectReplyMessage( h, message ); + } + break; + + case TCP_SRV_CONNECT_REPLY: { + retVal = ReadConnectReplyMessage( h, message ); + } + break; + + default: + /* We shouldn't get into this state so we'd better return an error here... */ + retVal = AVERROR(EIO); + break; + } + + } + + return retVal; +} + +static int ReadConnectRejectMessage( URLContext * h, NetworkMessage *message ) +{ + SrvConnectRejectMsg * bodyPtr = NULL; + + /* Allocate the message body */ + if( (message->body = av_malloc( sizeof(SrvConnectRejectMsg) )) == NULL ) + return AVERROR(ENOMEM); + + /* Now read from the stream into the message */ + bodyPtr = (SrvConnectRejectMsg *)message->body; + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->reason, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->timestamp, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->macAddr, MAC_ADDR_LENGTH ) != MAC_ADDR_LENGTH ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->appVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->minViewerVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + /* Correct the byte ordering */ + bodyPtr->reason = av_be2ne32(bodyPtr->reason); + bodyPtr->timestamp = av_be2ne32(bodyPtr->timestamp); + bodyPtr->appVersion = av_be2ne32(bodyPtr->appVersion); + bodyPtr->minViewerVersion = av_be2ne32(bodyPtr->minViewerVersion); + + return 0; +} + +static int ReadConnectReplyMessage( URLContext * h, NetworkMessage *message ) +{ + SrvConnectReplyMsg * bodyPtr = NULL; + + /* Allocate memory in which to store the message body */ + if( (message->body = av_malloc( sizeof(SrvConnectReplyMsg) )) == NULL ) + return AVERROR(ENOMEM); + + bodyPtr = (SrvConnectReplyMsg *)message->body; + + /* Now read the message body, a field at a time */ + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->numCameras, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->viewableCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->telemetryCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->failedCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->maxMsgInterval, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->timestamp, sizeof(int64_t) ) != sizeof(int64_t) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->cameraTitles, (16 * 28) ) != (16 * 28) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitType, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->applicationVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->videoStandard, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->macAddr, MAC_ADDR_LENGTH ) != MAC_ADDR_LENGTH ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->unitName, UNIT_NAME_LENGTH ) != UNIT_NAME_LENGTH ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->numFixedRealms, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + bodyPtr->numFixedRealms = av_be2ne32(bodyPtr->numFixedRealms); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->realmFlags, (sizeof(unsigned long) * bodyPtr->numFixedRealms) ) != (sizeof(unsigned long) * bodyPtr->numFixedRealms) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->minimumViewerVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + /* Correct the byte ordering */ + bodyPtr->numCameras = av_be2ne32(bodyPtr->numCameras); + bodyPtr->viewableCamMask = av_be2ne32(bodyPtr->viewableCamMask); + bodyPtr->telemetryCamMask = av_be2ne32(bodyPtr->telemetryCamMask); + bodyPtr->failedCamMask = av_be2ne32(bodyPtr->failedCamMask); + bodyPtr->maxMsgInterval = av_be2ne32(bodyPtr->maxMsgInterval); + bodyPtr->unitType = av_be2ne32(bodyPtr->unitType); + bodyPtr->applicationVersion = av_be2ne32(bodyPtr->applicationVersion); + bodyPtr->videoStandard = av_be2ne32(bodyPtr->videoStandard); + bodyPtr->numFixedRealms = av_be2ne32(bodyPtr->numFixedRealms); + bodyPtr->minimumViewerVersion = av_be2ne32(bodyPtr->minimumViewerVersion); + + return 0; +} + +static int ReadFeatureConnectReplyMessage( URLContext * h, NetworkMessage *message ) +{ + SrvFeatureConnectReplyMsg * bodyPtr = NULL; + + if( (message->body = av_malloc( sizeof(SrvFeatureConnectReplyMsg) )) == NULL ) + return AVERROR(ENOMEM); + + bodyPtr = (SrvFeatureConnectReplyMsg *)message->body; + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->numCameras, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->viewableCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->telemetryCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->failedCamMask, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->maxMsgInterval, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->timestamp, sizeof(int64_t) ) != sizeof(int64_t) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->cameraTitles, (16 * 28) ) != (16 * 28) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitType, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->applicationVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->videoStandard, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->macAddr, MAC_ADDR_LENGTH ) != MAC_ADDR_LENGTH ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->unitName, UNIT_NAME_LENGTH ) != UNIT_NAME_LENGTH ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->minimumViewerVersion, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitFeature01, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitFeature02, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitFeature03, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->unitFeature04, sizeof(unsigned long) ) != sizeof(unsigned long) ) + return AVERROR(EIO); + + if( DSReadBuffer( h, (uint8_t *)&bodyPtr->numFixedRealms, sizeof(long) ) != sizeof(long) ) + return AVERROR(EIO); + + bodyPtr->numFixedRealms = av_be2ne32(bodyPtr->numFixedRealms); + + if( DSReadBuffer( h, (uint8_t *)bodyPtr->realmFlags, (sizeof(unsigned long) * bodyPtr->numFixedRealms) ) != (sizeof(unsigned long) * bodyPtr->numFixedRealms) ) + return AVERROR(EIO); + + /* Correct the byte ordering */ + bodyPtr->numCameras = av_be2ne32(bodyPtr->numCameras); + bodyPtr->viewableCamMask = av_be2ne32(bodyPtr->viewableCamMask); + bodyPtr->telemetryCamMask = av_be2ne32(bodyPtr->telemetryCamMask); + bodyPtr->failedCamMask = av_be2ne32(bodyPtr->failedCamMask); + bodyPtr->maxMsgInterval = av_be2ne32(bodyPtr->maxMsgInterval); + bodyPtr->unitType = av_be2ne32(bodyPtr->unitType); + bodyPtr->applicationVersion = av_be2ne32(bodyPtr->applicationVersion); + bodyPtr->videoStandard = av_be2ne32(bodyPtr->videoStandard); + bodyPtr->minimumViewerVersion = av_be2ne32(bodyPtr->minimumViewerVersion); + bodyPtr->unitFeature01 = av_be2ne32(bodyPtr->unitFeature01); + bodyPtr->unitFeature02 = av_be2ne32(bodyPtr->unitFeature02); + bodyPtr->unitFeature03 = av_be2ne32(bodyPtr->unitFeature03); + bodyPtr->unitFeature04 = av_be2ne32(bodyPtr->unitFeature04); + + return 0; +} + +static void HToNMessageHeader( MessageHeader *header, unsigned char *buf ) +{ + MessageHeader tempHeader; + int bufIdx = 0; + + if( header != NULL && buf != NULL ) { + /* Set whatever header values we can in here */ + tempHeader.magicNumber = av_be2ne32(header->magicNumber); + memcpy( &buf[bufIdx], &tempHeader.magicNumber, sizeof(unsigned long) ); + bufIdx += sizeof(unsigned long); + + tempHeader.length = av_be2ne32(header->length); + memcpy( &buf[bufIdx], &tempHeader.length, sizeof(unsigned long) ); + bufIdx += sizeof(unsigned long); + + tempHeader.channelID = av_be2ne32(header->channelID); + memcpy( &buf[bufIdx], &tempHeader.channelID, sizeof(long) ); + bufIdx += sizeof(long); + + tempHeader.sequence = av_be2ne32(header->sequence); /* Currently unsupported at server */ + memcpy( &buf[bufIdx], &tempHeader.sequence, sizeof(long) ); + bufIdx += sizeof(long); + + tempHeader.messageVersion = av_be2ne32(header->messageVersion); + memcpy( &buf[bufIdx], &tempHeader.messageVersion, sizeof(unsigned long) ); + bufIdx += sizeof(unsigned long); + + tempHeader.checksum = av_be2ne32(header->checksum); /* As suggested in protocol documentation */ + memcpy( &buf[bufIdx], &tempHeader.checksum, sizeof(long) ); + bufIdx += sizeof(long); + + tempHeader.messageType = av_be2ne32(header->messageType); + memcpy( &buf[bufIdx], &tempHeader.messageType, sizeof(long) ); + bufIdx += sizeof(long); + } +} + +/**************************************************************************************************************** + * Function: SendNetworkMessage + * Desc: Sends the given message over the connection. + * Params: + * h - Pointer to URLContext struct used to store all connection info associated with this connection + * message - Pointer to the message to be sent + * Return: + * 0 on success, non 0 on failure + ****************************************************************************************************************/ +static int SendNetworkMessage( URLContext *h, NetworkMessage *message ) +{ + unsigned char messageBuffer[DS_WRITE_BUFFER_SIZE]; + int bufIdx = SIZEOF_MESSAGE_HEADER_IO; + + /* 0 the buffer */ + memset( messageBuffer, 0, DS_WRITE_BUFFER_SIZE ); + + /* Write the header into the buffer */ + HToNMessageHeader( &message->header, messageBuffer ); + + /* Now write the rest of the message to the buffer based on its type */ + switch( message->header.messageType ) { + case TCP_CLI_CONNECT: { + ClientConnectMsg tempMsg; + + tempMsg.udpPort = HTON32(((ClientConnectMsg *)message->body)->udpPort); + memcpy( &messageBuffer[bufIdx], &tempMsg.udpPort, sizeof(unsigned long) ); + bufIdx += sizeof(unsigned long); + + tempMsg.connectType = HTON32(((ClientConnectMsg *)message->body)->connectType); + memcpy( &messageBuffer[bufIdx], &tempMsg.connectType, sizeof(long) ); + bufIdx += sizeof(long); + + memcpy( &messageBuffer[bufIdx], ((ClientConnectMsg *)message->body)->userID, MAX_USER_ID_LENGTH ); + bufIdx += MAX_USER_ID_LENGTH; + + memcpy( &messageBuffer[bufIdx], ((ClientConnectMsg *)message->body)->accessKey, ACCESS_KEY_LENGTH ); + bufIdx += ACCESS_KEY_LENGTH; + } + break; + + case TCP_CLI_IMG_LIVE_REQUEST: { + long temp; + + temp = HTON32( ((CliImgLiveRequestMsg*)message->body)->cameraMask ); + memcpy( &messageBuffer[bufIdx], &temp, sizeof(long) ); + bufIdx += sizeof(long); + + temp = HTON32( ((CliImgLiveRequestMsg*)message->body)->resolution ); + memcpy( &messageBuffer[bufIdx], &temp, sizeof(long) ); + bufIdx += sizeof(long); + } + break; + + case TCP_CLI_IMG_PLAY_REQUEST: { + long temp; + int64_t tempBig; + + temp = HTON32( ((CliImgPlayRequestMsg*)message->body)->cameraMask ); + memcpy( &messageBuffer[bufIdx], &temp, sizeof(long) ); + bufIdx += sizeof(long); + + temp = HTON32( ((CliImgPlayRequestMsg*)message->body)->mode ); + memcpy( &messageBuffer[bufIdx], &temp, sizeof(long) ); + bufIdx += sizeof(long); + + temp = HTON32( ((CliImgPlayRequestMsg*)message->body)->pace ); + memcpy( &messageBuffer[bufIdx], &temp, sizeof(long) ); + bufIdx += sizeof(long); + + tempBig = HTON64( ((CliImgPlayRequestMsg*)message->body)->fromTime ); + memcpy( &messageBuffer[bufIdx], &tempBig, sizeof(int64_t) ); + bufIdx += sizeof(int64_t); + + tempBig = HTON64( ((CliImgPlayRequestMsg*)message->body)->toTime ); + memcpy( &messageBuffer[bufIdx], &tempBig, sizeof(int64_t) ); + bufIdx += sizeof(int64_t); + } + break; + } + + /* Write to output stream - remember to add on the 4 bytes for the magic number which precedes the length */ + return DSWrite( h, messageBuffer, message->header.length + sizeof(unsigned long) ); +} + +static int64_t TimeTolong64( time_t time ) +{ + int64_t timeOut = 0; + unsigned short flags = 0; + unsigned short ms = 0; + uint8_t * bufPtr = NULL; + + /* For now, we're saying we don't know the time zone */ + SET_FLAG_ZONE_UNKNOWN(flags); + + bufPtr = (uint8_t*)&timeOut; + + memcpy( bufPtr, &flags, sizeof(unsigned short) ); + bufPtr += sizeof(unsigned short); + + memcpy( bufPtr, &ms, sizeof(unsigned short) ); + bufPtr += sizeof(unsigned short); + + memcpy( bufPtr, &time, sizeof(time_t) ); + + return timeOut; +} + + +URLProtocol ff_dm_protocol = { + .name = "dm", + .url_open = DSOpen, + .url_read = DSRead, + .url_write = DSWrite, + .url_close = DSClose, +}; diff --git a/libavformat/ds.h b/libavformat/ds.h new file mode 100644 index 0000000000..c152d1a061 --- /dev/null +++ b/libavformat/ds.h @@ -0,0 +1,135 @@ +/* + * Protocol for AD-Holdings Digital Sprite stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 __DS_H__ +#define __DS_H__ + +#include "ds_exports.h" + +/* -------------------------------------- Constants -------------------------------------- */ +#define DS_HEADER_MAGIC_NUMBER 0xFACED0FF + + +/* -------------------------------------- Structures/types -------------------------------------- */ +typedef enum { + INVALID_MESSAGE_TYPE, /* 0 */ + TCP_CLI_CONNECT, /* 1 */ /* Initial connection */ + TCP_SRV_CONNECT_REJECT, /* 2 */ /* Connection rejected */ + TCP_SRV_CONNECT_REPLY, /* 3 */ /* Connect accepted - returns DSL configuration */ + TCP_CLI_IMG_LIVE_REQUEST, /* 4 */ /* Request an image stream */ + TCP_CLI_IMG_PLAY_REQUEST, /* 5 */ /* Request a playback stream */ + TCP_SRV_IMG_DATA, /* 6 */ /* Image stream data */ + TCP_SRV_NUDGE, /* 7 */ /* Nudge */ + TCP_SRV_DISCONNECT, /* 8 */ /* Server dropping connection - ie: unit shutting down etc. */ + UDP_CLI_IMG_CONTROL_REQUEST, /* 9 */ /* Request to change image stream ie: camera change */ + UDP_SRV_IMG_CONTROL_REPLY, /* A */ /* Acknowledgement of request */ + UDP_CLI_SYSTEM_STATUS_REQUEST, /* B */ /* Request for update of DSL configuration */ + UDP_SRV_SYSTEM_STATUS, /* C */ /* New DSL configuration - may be sent by server if menus change */ + UDP_CLI_TELEMETRY_REQUEST, /* D */ /* Telemetry star request */ + UDP_CLI_TELEMETRY_COMMAND, /* E */ /* Telemetry command request */ + UDP_CLI_TELEMETRY_STATUS_REQUEST,/* F */ /* Request for telemetry status update */ + UDP_SRV_TELEMETRY_STATUS, /* 10*/ /* Ack with new telemetry status */ + UDP_CLI_DISCONNECT, /* 11*/ /* Polite message notifying of client disconnect */ + UDP_CLI_DISCOVER, /* 12*/ /* Broadcast to identify units on subnet */ + UDP_SRV_HELLO, /* 13*/ /* Reply containing basic unit information */ + UDP_CLI_MCI_COMMAND, /* 14*/ /* Send MCI command */ + UDP_SRV_MCI_REPLY, /* 15*/ /* Reply from MCI command */ + UDP_SRV_MSG_FAIL, /* 16*/ /* UDP message recognition failure - returns status code */ + UDP_CLI_EVENT_FILTER_REQUEST, /* 17*/ /* Request to initialise an event filter. */ + UDP_SRV_EVENT_FILTER_REPLY, /* 18*/ /* Filter request either accepted, or not accepted. */ + UDP_CLI_EVENTS_REQUEST, /* 19*/ /* Request to apply filter and retrieve events */ + UDP_SRV_EVENTS_REPLY, /* 1A*/ /* Reply containing filtered event */ + UDP_CLI_ADMIN_REQUEST, /* 1B*/ /* Request from user for admin */ + UDP_SRV_ADMIN_REPLY, /* 1C*/ /* Reply - contains success or fail reason */ + UDP_CLI_USER_INFO_REQUEST, /* 1D*/ /* Request for information on a specific user */ + UDP_SRV_USER_INFO_REPLY, /* 1E*/ /* Information on the requested user */ + UDP_CLI_ADD_USER_REQUEST, /* 1F*/ /* Request to add a user to the system */ + UDP_SRV_ADD_USER_REPLY, /* 20*/ /* Reply to indicate success or failure */ + UDP_CLI_DELETE_USER_REQUEST, /* 21*/ /* Request to delete a user from the system */ + UDP_SRV_DELETE_USER_REPLY, /* 22*/ /* Reply to delete user request */ + UDP_CLI_CHANGE_PASSWORD_REQUEST, /* 23*/ /* Change password */ + UDP_SRV_CHANGE_PASSWORD_REPLY, /* 24*/ /* Reply to request */ + UDP_CLI_UPDATE_ACCESS_RIGHTS_REQUEST, /* 25*/ /* Request to change users realms */ + UDP_SRV_UPDATE_ACCESS_RIGHTS_REPLY, /* 26*/ /* Reply to request */ + TCP_CLI_CHANGE_PASSWORD, /* 27*/ /* send password change*/ + UDP_CLI_CHANGE_OWN_PASSWORD_REQUEST, /* 28*/ /* Change own password */ + UDP_SRV_CHANGE_OWN_PASSWORD_REPLY, /* 29*/ /* Reply to request */ + UDP_CLI_EMAIL_REQUEST, /* 2A*/ /* Request for email data */ + UDP_SRV_EMAIL_REPLY, /* 2B*/ /* Reply with email data */ + UDP_CLI_CHANGE_EMAIL_REQUEST, /* 2C*/ /* Request to set new email data */ + UDP_SRV_CHANGE_EMAIL_REPLY, /* 2D*/ /* Reply to setting new email data */ + UDP_CLI_CHANGE_SESSION_REQUEST, /* 2E*/ /* Request to logon to different unit*/ + TCP_CLI_IMG_DATA_REQUEST, /* 2F*/ /* request from remote to grab image data */ + TCP_SRV_DATA_DATA, /* 30*/ /* us sending requested images as data */ + TCP_SRV_NO_DATA, /* 31*/ /* Sent when finished sending data */ + UDP_CLI_ABORT_DATA_REQUEST, /* 32*/ /* Cancel data transfer */ + UDP_CLI_EVENT_DATA_REQUEST, /* 33*/ /* Request to obtain end time of an event */ + UDP_SRV_EVENT_DATA_REPLY, /* 34*/ /* reply */ + TCP_CLI_CONTROL_CONNECT, /* 35*/ /* Initial connection for TCP control link */ + TCP_SRV_CONTROL_CONNECT_REJECT, + TCP_SRV_CONTROL_CONNECT_REPLY, + UDP_CLI_SERVICE_CHECK, /* 38*/ /* Sent to check if UDP service working */ + UDP_SRV_SERVICE_CHECK_REPLY, + UDP_CLI_DRIVE_DETAIL_REQUEST, /* 3A*/ /* Request for hard drive details */ + UDP_SRV_DRIVE_DETAIL_REPLY, /* 3B*/ /* Reply with data */ + UDP_CLI_DRIVE_SMART_REQUEST, /* 3C*/ /* Request for hard drive S.M.A.R.T. details */ + UDP_SRV_DRIVE_SMART_REPLY, /* 3D*/ /* Reply with S.M.A.R.T. data */ + UDP_CLI_DRIVE_LOG_REQUEST, /* 3E*/ /* Request for hard drive log details */ + UDP_SRV_DRIVE_LOG_REPLY, /* 3F*/ /* Reply with log data */ + UDP_CLI_DRIVE_TEST_REQUEST, /* 40*/ /* Request for hard drive offline test */ + UDP_SRV_DRIVE_TEST_REPLY, /* 41*/ /* Reply with confirmation of test */ + TCP_SRV_FEATURE_CONNECT_REPLY, /* 42*/ /* Connect accepted - returns DSL configuration */ + TCP_CLI_ALM_CONNECT, /* 43*/ /* Initial alarm connection to PC */ + TCP_SRV_ALM_REJECT, /* 44*/ /* reject message with reasons not registered, auth required, invalid password, busy etc*/ + TCP_SRV_ALM_ACCEPT, /* 45*/ /* Client connection accepted - send and alarm msg */ + TCP_CLI_ALM_MSG, /* 46*/ /* Alarm details */ + TCP_SRV_ALM_ACK, /* 47*/ /* Server ack of an alarm message */ + UDP_CLI_CONFIG_REQUEST, /* 48*/ /* Request name/value pairs from unit Get/Send*/ + UDP_SRV_CONFIG_REJECT, /* 49*/ /* Server denied access to config */ + UDP_CLI_CONFIG_ITEM, /* 4A*/ /* has item x of y to determine missing and last item */ + UDP_SRV_CONFIG_ITEM, + UDP_CLI_RELAY_REQUEST, + UDP_SRV_RELAY_REPLY, + UDP_CLI_CHANGE_ACTIVE_FEATURES, + UDP_SRV_ACTIVE_FEATURES_REPLY, + UDP_CLI_POS_FILTER_REQUEST, /* 50*/ /* POS keyworldsearch filter */ + UDP_SRV_POS_TICK, /* 51*/ /* sends during search to say still searching */ + UDP_SRV_POS_FILTER_REPLY, /* 52*/ /* reply to filter */ + UDP_CLI_POS_LINES_REQUEST, /* 53*/ /* Request for POS lines */ + UDP_SRV_POS_LINES_REPLY, /* 54*/ /* Reply with POS matches */ + UDP_CLI_POS_DATA_REQUEST, /* 55*/ /* Request for info on a specific POS event */ + UDP_SRV_POS_DATA_REPLY, /* 56*/ /* Replies with start & end time etc */ + NET_NUMBER_OF_MESSAGE_TYPES /* 57*/ /* ALWAYS KEEP AS LAST ITEMS */ +} ControlMessageTypes; + +typedef struct _messageHeader { + unsigned long magicNumber; + unsigned long length; + long channelID; + long sequence; + unsigned long messageVersion; + long checksum; + long messageType; +} MessageHeader; +#define SIZEOF_MESSAGE_HEADER_IO 28 /* Size in bytes of the MessageHeader structure. Can't use sizeof to read/write one of these to network as structure packing may differ based on platform */ + +#endif /* __DS_H__ */ diff --git a/libavformat/ds_exports.h b/libavformat/ds_exports.h new file mode 100644 index 0000000000..7cb845898b --- /dev/null +++ b/libavformat/ds_exports.h @@ -0,0 +1,173 @@ +/* + * Data exportable to clients for AD-Holdings data types + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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_DS_EXPORTS_H +#define AVFORMAT_DS_EXPORTS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + + +struct NetVuPicture { + uint16_t src_pixels; ///< Input image size (horizontal) + uint16_t src_lines; ///< Input image size (vertical) + uint16_t target_pixels; ///< Output image size (horizontal) + uint16_t target_lines; ///< Output image size (vertical) + uint16_t pixel_offset; ///< Image start offset (horizontal) + uint16_t line_offset; ///< Image start offset (vertical) +}; + +struct NetVuImageData { + uint32_t version; ///< structure version number */ + + /** mode: in PIC_REVISION 0 this was the DFT style FULL_HI etc + * in PIC_REVISION 1 this is used to specify AD or JFIF format image + */ + int32_t mode; + int32_t cam; ///< camera number + int32_t vid_format; ///< 422 or 411 + uint32_t start_offset; ///< start of picture + int32_t size; ///< size of image + int32_t max_size; ///< maximum size allowed + int32_t target_size; ///< size wanted for compression + int32_t factor; ///< Q factor + uint32_t alm_bitmask_hi; ///< High 32 bits of the alarm bitmask + int32_t status; ///< status of last action performed on picture + uint32_t session_time; ///< playback time of image + uint32_t milliseconds; ///< sub-second count for playback speed control + char res[4]; ///< picture size + char title[31]; ///< camera title + char alarm[31]; ///< alarm text - title, comment etc + struct NetVuPicture format; ///< NOTE: Do not assign to a pointer due to CW-alignment + char locale[30]; ///< Timezone name + int32_t utc_offset; ///< Timezone difference in minutes + uint32_t alm_bitmask; +}; +#define NetVuImageDataHeaderSize 168 + +struct NetVuAudioData { + uint32_t version; + int32_t mode; + int32_t channel; + int32_t sizeOfAdditionalData; + int32_t sizeOfAudioData; + uint32_t seconds; + uint32_t msecs; + unsigned char * additionalData; +}; +#define NetVuAudioDataHeaderSize (28 + sizeof(unsigned char *)) + +#define ID_LENGTH 8 +#define NUM_ACTIVITIES 8 +#define CAM_TITLE_LENGTH 24 +#define ALARM_TEXT_LENGTH 24 + +struct DMImageData { + char identifier[ID_LENGTH]; + unsigned long jpegLength; + int64_t imgSeq; + int64_t imgTime; + unsigned char camera; + unsigned char status; + unsigned short activity[NUM_ACTIVITIES]; + unsigned short QFactor; + unsigned short height; + unsigned short width; + unsigned short resolution; + unsigned short interlace; + unsigned short subHeaderMask; + char camTitle[CAM_TITLE_LENGTH]; + char alarmText[ALARM_TEXT_LENGTH]; +}; + + +enum ADFrameType { + FrameTypeUnknown = 0, + NetVuVideo, + NetVuAudio, + DMVideo, + DMNudge, + NetVuDataInfo, + NetVuDataLayout, + RTPAudio +}; + +#define VSD_M0 0 +#define VSD_M1 1 +#define VSD_M2 2 +#define VSD_M3 3 +#define VSD_M4 4 +#define VSD_M5 5 +#define VSD_M6 6 +#define VSD_FM0 7 +#define VSD_F 8 +#define VSD_EM0 9 +#define VSD_COUNT 10 + +#define ACTMASKLEN 16 +#define VSDARRAYLEN 16 + +/** This is the data structure that the ffmpeg parser fills in as part of the + * parsing routines. It will be shared between adpic and dspic so that our + * clients can be compatible with either stream more easily + */ +struct ADFrameData { + /// Type of frame we have. See ADFrameType enum for supported types + enum ADFrameType frameType; + /// Pointer to structure holding the information for the frame. + void * frameData; + /// Pointer to text block + void * additionalData; + /// Data parsed out of text (if exists) + int activeZones; + /// Data parsed out of text (if exists) + uint32_t frameNum; + /// Data parsed out of text (if exists) + uint16_t activityMask[ACTMASKLEN]; + /// Data parsed out of text (if exists) + int vsd[VSD_COUNT][VSDARRAYLEN]; +}; + +#define RTP_PAYLOAD_TYPE_8000HZ_ADPCM 5 +#define RTP_PAYLOAD_TYPE_11025HZ_ADPCM 16 +#define RTP_PAYLOAD_TYPE_16000HZ_ADPCM 6 +#define RTP_PAYLOAD_TYPE_22050HZ_ADPCM 17 +#define RTP_PAYLOAD_TYPE_32000HZ_ADPCM 96 +#define RTP_PAYLOAD_TYPE_44100HZ_ADPCM 97 +#define RTP_PAYLOAD_TYPE_48000HZ_ADPCM 98 +#define RTP_PAYLOAD_TYPE_8000HZ_PCM 100 +#define RTP_PAYLOAD_TYPE_11025HZ_PCM 101 +#define RTP_PAYLOAD_TYPE_16000HZ_PCM 102 +#define RTP_PAYLOAD_TYPE_22050HZ_PCM 103 +#define RTP_PAYLOAD_TYPE_32000HZ_PCM 104 +#define RTP_PAYLOAD_TYPE_44100HZ_PCM 11 +#define RTP_PAYLOAD_TYPE_48000HZ_PCM 105 + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/libavformat/dsenc.c b/libavformat/dsenc.c new file mode 100644 index 0000000000..250d107c9f --- /dev/null +++ b/libavformat/dsenc.c @@ -0,0 +1,488 @@ +/* + * Protocol for AD-Holdings Digital Sprite stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 +#include +#include "avformat.h" +#include "libavutil/avstring.h" +#include "dsenc.h" + +/* Constants for MD5Transform routine. + */ +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + + +/* POINTER defines a generic pointer type */ +typedef unsigned char * POINTER; +typedef const unsigned char * CPOINTER; + +/* UINT2 defines a two byte word */ +typedef unsigned short int UINT2; + +/* UINT4 defines a four byte word */ +typedef unsigned long int UINT4; + + +/* MD5 context. */ +typedef struct { + UINT4 state[4]; /* state (ABCD) */ + UINT4 count[2]; /* number of bits, modulo 2^64 (lsb first) */ + unsigned char buffer[64]; /* input buffer */ +} MD5_CTX; + + +/* Length of test block, number of test blocks. + */ +#define TEST_BLOCK_LEN 1000 +#define TEST_BLOCK_COUNT 1000 + + +// Macro Definitions + +/* F, G, H and I are basic MD5 functions. + */ +#define F(x, y, z) (((x) & (y)) | ((~x) & (z))) +#define G(x, y, z) (((x) & (z)) | ((y) & (~z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) +#define I(x, y, z) ((y) ^ ((x) | (~z))) + +/* ROTATE_LEFT rotates x left n bits. + */ +#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) + +/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +Rotation is separate from addition to prevent recomputation. + */ +#define FF(a, b, c, d, x, s, ac) { \ + (a) += F ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define GG(a, b, c, d, x, s, ac) { \ + (a) += G ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define HH(a, b, c, d, x, s, ac) { \ + (a) += H ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } +#define II(a, b, c, d, x, s, ac) { \ + (a) += I ((b), (c), (d)) + (x) + (UINT4)(ac); \ + (a) = ROTATE_LEFT ((a), (s)); \ + (a) += (b); \ + } + +#define VIEWER_VERSION_104_001 0x00104001 + + +static void MD5Init(MD5_CTX * context, const unsigned char *pub_key); +static void MD5Update(MD5_CTX* context, const unsigned char* input, unsigned int inputLen); +static void MD5Final(unsigned char * digest, MD5_CTX * context); +static void MD5Transform(UINT4 * state, const unsigned char * block); +static void Encode(unsigned char * output, const UINT4 * input, unsigned int len); +static void Decode(UINT4 * output, const unsigned char * input, unsigned int len); +static unsigned char hex_val (const char *char2); +static void MD5Print(char* FingerPrint, int size, const unsigned char * digest); +static void GetFingerPrint(char* FingerPrint, const char* Source, unsigned int Length, const char *cpublic_key); + +#if !defined(_WIN32) +static char* strupr(char *theString) +{ + int ii; + + for(ii = 0; ii < strlen(theString); ii++) { + theString[ii] = toupper(theString[ii]); + } + return theString; +} +#endif + +//--------------------------------------------------------------------------- + +#pragma pack(1) +//#pragma package(smart_init) + +static unsigned char PADDING[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +/* MD5 initialization. Begins an MD5 operation, writing a new context. */ +static void MD5Init(MD5_CTX * context, const unsigned char *pub_key) +{ + context->count[0] = context->count[1] = 0; + /* Load magic initialization constants.*/ + context->state[0] = 0x67452301; + context->state[1] = 0xefcdab89; + context->state[2] = 0x98badcfe; + context->state[3] = 0x10325476; + if (pub_key != NULL) { + context->state[0] &= 0xFF0000FF ; + context->state[0] |= ((pub_key[4] << 8) | (pub_key[1] << 16)) ; + context->state[1] &= 0xFFFFFF00 ; + context->state[1] |= (pub_key[5]) ; + context->state[2] &= 0x00FF00FF ; + context->state[2] |= ((pub_key[0] << 8) | (pub_key[2] << 24)) ; + context->state[3] &= 0xFF00FFFF ; + context->state[3] |= (pub_key[3] << 16) ; + } +} + +/* MD5 block update operation. Continues an MD5 message-digest + operation, processing another message block, and updating the + context. + */ +static void MD5Update(MD5_CTX* context, const unsigned char* input, unsigned int inputLen) +{ + unsigned int i, index, partLen; + + /* Compute number of bytes mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3F); + + /* Update number of bits */ + if ((context->count[0] += ((UINT4)inputLen << 3)) < ((UINT4)inputLen << 3)) + context->count[1]++; + + context->count[1] += ((UINT4)inputLen >> 29); + + partLen = 64 - index; + + /* Transform as many times as possible */ + if (inputLen >= partLen) { + memcpy((POINTER)&context->buffer[index], (CPOINTER)input, partLen); + MD5Transform (context->state, context->buffer); + + for (i = partLen; i + 63 < inputLen; i += 64) + MD5Transform (context->state, &input[i]); + + index = 0; + } + else { + i = 0; + } + + /* Buffer remaining input */ + if (inputLen > i) + memcpy((POINTER)&context->buffer[index], (CPOINTER)&input[i], inputLen - i); +} + +/* MD5 finalization. Ends an MD5 message-digest operation, writing the + the message digest and zeroizing the context. + */ +static void MD5Final(unsigned char * digest, MD5_CTX * context) +{ + unsigned char bits[8]; + unsigned int index, padLen; + + /* Save number of bits */ + Encode (bits, context->count, 8); + + /* Pad out to 56 mod 64 */ + index = (unsigned int)((context->count[0] >> 3) & 0x3f); + padLen = (index < 56) ? (56 - index) : (120 - index); + MD5Update (context, PADDING, padLen); + + /* Append length (before padding) */ + MD5Update (context, bits, 8); + /* Store state in digest */ + Encode (digest, context->state, 16); + + /* Zeroize sensitive information */ + memset ((POINTER)context, 0, sizeof (*context)); +} + + + +/* MD5 basic transformation. Transforms state based on block */ +static void MD5Transform(UINT4 * state, const unsigned char * block) +{ + UINT4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + + Decode (x, block, 64); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + /* Zeroize sensitive information */ + memset ((POINTER)x, 0, sizeof (x)); +} + +/* Encodes input (UINT4) into output (unsigned char). Assumes len is a multiple of 4 */ +static void Encode(unsigned char * output, const UINT4 *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[j] = (unsigned char)(input[i] & 0xff); + output[j+1] = (unsigned char)((input[i] >> 8) & 0xff); + output[j+2] = (unsigned char)((input[i] >> 16) & 0xff); + output[j+3] = (unsigned char)((input[i] >> 24) & 0xff); + } +} + + +/* Decodes input (unsigned char) into output (UINT4). Assumes len is + a multiple of 4. + */ +static void Decode(UINT4 * output, const unsigned char *input, unsigned int len) +{ + unsigned int i, j; + + for (i = 0, j = 0; j < len; i++, j += 4) { + output[i] = ((UINT4)input[j]) | + (((UINT4)input[j+1]) << 8) | + (((UINT4)input[j+2]) << 16) | + (((UINT4)input[j+3]) << 24); + } +} + +static unsigned char hex_val (const char *char2) +{ + unsigned long ret_val ; + if (char2[0] > '9') + ret_val = (char2[0] - 'A' + 10) * 0x10 ; + else + ret_val = (char2[0] - '0') * 0x10 ; + + if (char2[1] > '9') + ret_val += (char2[1] - 'A' + 10) ; + else + ret_val += (char2[1] - '0') ; + + return (unsigned char)(ret_val & 0x000000FF) ; +} + +// Main Function to get a finger print +static void GetFingerPrint(char* FingerPrint, const char* Source, unsigned int Length, const char *cpublic_key) +{ + MD5_CTX context; + unsigned char digest[16]; + + short j ; + unsigned char public_key[6] ; + + char local_fp[32+1] ; + //unsigned int len = strlen(Source); + + if (cpublic_key != NULL) { + for (j = 0; j < 6; j++) + public_key[j] = hex_val (&(cpublic_key[j*2])) ; + MD5Init (&context, public_key); + } + else + MD5Init (&context, NULL); + + MD5Update (&context, (const unsigned char*)Source, Length); + MD5Final (digest, &context); + + + MD5Print (local_fp, 32 + 1, digest); + + av_strlcpy (FingerPrint, local_fp, 32) ; + +} + + +/* Prints a message digest in hexadecimal */ +static void MD5Print(char* FingerPrint, int size, const unsigned char * digest) +{ + unsigned int i; + + char temp[20]; + + strcpy(FingerPrint, ""); + + for (i = 0; i < 16; i++) { + snprintf (temp, 20, "%02x", digest[i]); + + av_strlcat(FingerPrint, temp, size); + } + +} + +static char *CreateUserPassword( const char *Username, const char *Password ) +{ + char * userPassword = NULL; + + if( Username != NULL && Password != NULL ) { + /* Allocate space for both strings */ + if( (userPassword = (char*) av_malloc( strlen( Username ) + strlen( Password ) + 1 )) != NULL ) { + /* Copy the username into the output string */ + strcpy( userPassword, Username ); + + userPassword[strlen(Username)] = '\0'; + + /* Now add the password */ + av_strlcat( userPassword, Password, strlen( Username ) + strlen( Password ) + 1 ); + + /* NULL terminate */ + userPassword[strlen(Username) + strlen(Password)] = '\0'; + + /* Now convert it to uppercase */ + strupr( userPassword ); + } + } + + return userPassword; +} + +char * EncryptPasswordString(const char * Username, + const char * Password, + long Timestamp, + const char * MacAddress, + long RemoteApplicationVersion ) +{ + // Encrypt Password + char EncPassword[33]; + char TransmittedData[33]; + char Source[128]; + char * UserPassword = NULL; + char * EncryptedPassword = NULL; + int canContinue = 1; + + // If connected to a new unit send concat username too + if( RemoteApplicationVersion >= VIEWER_VERSION_104_001 ) { // version 1.4(001) First version with password handling + if( (UserPassword = CreateUserPassword( Username, Password )) != NULL ) { + + GetFingerPrint( EncPassword, UserPassword, (unsigned int)strlen(UserPassword), MacAddress ); + EncPassword[32] = '\0'; + + av_free( UserPassword ); + } + else + canContinue = 0; + } + else { + GetFingerPrint( EncPassword, Password, (unsigned int)(strlen(Password)), MacAddress ); + EncPassword[32] = '\0'; + } + + if( canContinue ) { + snprintf(Source, 128, "%08X", (int)Timestamp); + av_strlcat(Source, strupr(EncPassword), 128); + + GetFingerPrint( TransmittedData, Source, (unsigned int)strlen(Source), MacAddress ); + TransmittedData[32] = '\0'; + + /* Take a copy of this and return it */ + if( (EncryptedPassword = (char *) av_malloc( strlen(TransmittedData) + 1 )) != NULL ) { + strcpy( EncryptedPassword, TransmittedData ); + EncryptedPassword[strlen(TransmittedData)] = '\0'; + } + } + + return EncryptedPassword; +} diff --git a/libavformat/dsenc.h b/libavformat/dsenc.h new file mode 100644 index 0000000000..fe6372efbe --- /dev/null +++ b/libavformat/dsenc.h @@ -0,0 +1,35 @@ +/* + * Protocol for AD-Holdings Digital Sprite stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 __DM_ENCRYPTION_ROUTINES_H__ +#define __DM_ENCRYPTION_ROUTINES_H__ +//--------------------------------------------------------------------------- + +char * EncryptPasswordString(const char * Username, + const char * Password, + long Timestamp, + const char * MacAddress, + long RemoteApplicationVersion ); + +#endif /* __DM_ENCRYPTION_ROUTINES_H__ */ diff --git a/libavformat/dspic.c b/libavformat/dspic.c new file mode 100644 index 0000000000..51c216da30 --- /dev/null +++ b/libavformat/dspic.c @@ -0,0 +1,317 @@ +/* + * Demuxer for AD-Holdings Digital Sprite stream format + * Copyright (c) 2006-2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 "avformat.h" +#include "libavcodec/avcodec.h" +#include "libavutil/bswap.h" +#include "ds.h" +#include "adpic.h" + +static int dspicProbe( AVProbeData *p ); +static int dspicReadHeader( AVFormatContext *s ); +static int dspicReadPacket( AVFormatContext *s, AVPacket *pkt ); +static int dspicReadClose( AVFormatContext *s ); +static int ReadNetworkMessageHeader( AVIOContext *context, MessageHeader *header ); +static struct DMImageData * parseDSJFIFHeader( uint8_t *data, int dataSize ); +static int ExtractDSFrameData( uint8_t * buffer, struct DMImageData *frameData ); + + +static const long DSPacketHeaderMagicNumber = DS_HEADER_MAGIC_NUMBER; +static const char * DSApp0Identifier = "DigiSpr"; + + +#define TRUE 1 +#define FALSE 0 + + +static int dspicProbe( AVProbeData *p ) +{ + long magicNumber = 0; + + if( p->buf_size <= sizeof(long) ) + return 0; + + /* Get what should be the magic number field of the first header */ + memcpy( &magicNumber, p->buf, sizeof(long) ); + /* Adjust the byte ordering */ + magicNumber = av_be2ne32(magicNumber); + + if( magicNumber == DSPacketHeaderMagicNumber ) + return AVPROBE_SCORE_MAX; + + return 0; +} + +static int dspicReadHeader( AVFormatContext *s ) +{ + return 0; +} + +static int dspicReadPacket( AVFormatContext *s, AVPacket *pkt ) +{ + AVIOContext * ioContext = s->pb; + int retVal = 0; + MessageHeader header; + int dataSize = 0; + struct DMImageData * videoFrameData = NULL; + AVStream * stream = NULL; + struct ADFrameData * frameData = NULL; + enum ADFrameType frameType = FrameTypeUnknown; + + /* Attempt to read in a network message header */ + if( (retVal = ReadNetworkMessageHeader( ioContext, &header )) != 0 ) + return retVal; + + /* Validate the header */ + if( header.magicNumber == DSPacketHeaderMagicNumber ) { + if( header.messageType == TCP_SRV_NUDGE ) { + frameType = DMNudge; + + /* Read any extra bytes then try again */ + dataSize = header.length - (SIZEOF_MESSAGE_HEADER_IO - sizeof(unsigned long)); + + if( (retVal = ad_new_packet( pkt, dataSize )) < 0 ) + return retVal; + + if( avio_read( ioContext, pkt->data, dataSize ) != dataSize ) + return AVERROR(EIO); + } + else if( header.messageType == TCP_SRV_IMG_DATA ) { + frameType = DMVideo; + + /* This should be followed by a jfif image */ + dataSize = header.length - (SIZEOF_MESSAGE_HEADER_IO - sizeof(unsigned long)); + + /* Allocate packet data large enough for what we have */ + if( (retVal = ad_new_packet( pkt, dataSize )) < 0 ) + return retVal; + + /* Read the jfif data out of the buffer */ + if( avio_read( ioContext, pkt->data, dataSize ) != dataSize ) + return AVERROR(EIO); + + /* Now extract the frame info that's in there */ + if( (videoFrameData = parseDSJFIFHeader( pkt->data, dataSize )) == NULL ) + return AVERROR(EIO); + + /* if( audioFrameData != NULL ) frameType |= DS1_PACKET_TYPE_AUDIO; */ + + if ( (stream = ad_get_vstream(s, 0, 0, 1, PIC_MODE_JPEG_422, NULL)) == NULL ) + return AVERROR(EIO); + } + } + else + return AVERROR(EIO); + + /* Now create a wrapper to hold this frame's data which we'll store in the packet's private member field */ + if( (frameData = av_malloc( sizeof(*frameData) )) != NULL ) { + frameData->frameType = frameType; + frameData->frameData = videoFrameData; + frameData->additionalData = NULL; + + pkt->data = frameData; + } + else + goto fail_mem; + + pkt->stream_index = ( stream != NULL ) ? stream->index : 0; + pkt->duration = ((int)(AV_TIME_BASE * 1.0)); + + return retVal; + +fail_mem: + /* Make sure everything that might have been allocated is released before we return... */ + av_free( frameData ); + av_free( videoFrameData ); + return AVERROR(ENOMEM); +} + +static struct DMImageData * parseDSJFIFHeader( uint8_t *data, int dataSize ) +{ + struct DMImageData * frameData = NULL; + int i; + unsigned short length, marker; + int sos = FALSE; + + i = 0; + while( ((unsigned char)data[i] != 0xff) && (i < dataSize) ) + i++; + + if ( (unsigned char) data[++i] != 0xd8) + return NULL; /* Bad SOI */ + + i++; + + while( !sos && (i < dataSize) ) { + memcpy(&marker, &data[i], 2 ); + i += 2; + memcpy(&length, &data[i], 2 ); + i += 2; + marker = av_be2ne16(marker); + length = av_be2ne16(length); + + switch (marker) { + case 0xffe0 : { // APP0 + /* Have a little look at the data in this block, see if it's what we're looking for */ + if( memcmp( &data[i], DSApp0Identifier, strlen(DSApp0Identifier) ) == 0 ) { + int offset = i; + + if( (frameData = av_mallocz( sizeof(struct DMImageData) )) != NULL ) { + /* Extract the values into a data structure */ + if( ExtractDSFrameData( &data[offset], frameData ) < 0 ) { + av_free( frameData ); + return NULL; + } + } + } + + i += length - 2; + } + break; + + case 0xffdb : // Q table + i += length - 2; + break; + + case 0xffc0 : // SOF + i += length - 2; + break; + + case 0xffc4 : // Huffman table + i += length - 2; + break; + + case 0xffda : // SOS + i += length - 2; + sos = TRUE; + break; + + case 0xffdd : // DRI + i += length - 2; + break; + + case 0xfffe : // Comment + i += length - 2; + break; + + default : + /* Unknown marker encountered, better just skip past it */ + i += length - 2; // JCB 026 skip past the unknown field + break; + } + } + + return frameData; +} + +static int ExtractDSFrameData( uint8_t * buffer, struct DMImageData *frameData ) +{ + int retVal = AVERROR(EIO); + int bufIdx = 0; + + if( buffer != NULL ) { + memcpy( frameData->identifier, &buffer[bufIdx], ID_LENGTH ); + bufIdx += ID_LENGTH; + + memcpy( &frameData->jpegLength, &buffer[bufIdx], sizeof(unsigned long) ); + bufIdx += sizeof(unsigned long); + frameData->jpegLength = av_be2ne32(frameData->jpegLength); + + memcpy( &frameData->imgSeq, &buffer[bufIdx], sizeof(int64_t) ); + bufIdx += sizeof(int64_t); + frameData->imgSeq = av_be2ne64(frameData->imgSeq); + + memcpy( &frameData->imgTime, &buffer[bufIdx], sizeof(int64_t) ); + bufIdx += sizeof(int64_t); + + memcpy( &frameData->camera, &buffer[bufIdx], sizeof(unsigned char) ); + bufIdx += sizeof(unsigned char); + + memcpy( &frameData->status, &buffer[bufIdx], sizeof(unsigned char) ); + bufIdx += sizeof(unsigned char); + + memcpy( &frameData->activity, &buffer[bufIdx], sizeof(unsigned short) * NUM_ACTIVITIES ); + bufIdx += sizeof(unsigned short) * NUM_ACTIVITIES; + + memcpy( &frameData->QFactor, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->QFactor = av_be2ne16(frameData->QFactor); + + memcpy( &frameData->height, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->height = av_be2ne16(frameData->height); + + memcpy( &frameData->width, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->width = av_be2ne16(frameData->width); + + memcpy( &frameData->resolution, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->resolution = av_be2ne16(frameData->resolution); + + memcpy( &frameData->interlace, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->interlace = av_be2ne16(frameData->interlace); + + memcpy( &frameData->subHeaderMask, &buffer[bufIdx], sizeof(unsigned short) ); + bufIdx += sizeof(unsigned short); + frameData->subHeaderMask = av_be2ne16(frameData->subHeaderMask); + + memcpy( frameData->camTitle, &buffer[bufIdx], sizeof(char) * CAM_TITLE_LENGTH ); + bufIdx += sizeof(char) * CAM_TITLE_LENGTH; + + memcpy( frameData->alarmText, &buffer[bufIdx], sizeof(char) * ALARM_TEXT_LENGTH ); + bufIdx += sizeof(char) * ALARM_TEXT_LENGTH; + + retVal = 0; + } + + return retVal; +} + +static int ReadNetworkMessageHeader( AVIOContext *context, MessageHeader *header ) +{ + // Read the header in a piece at a time... + header->magicNumber = avio_rb32(context); + header->length = avio_rb32(context); + header->channelID = avio_rb32(context); + header->sequence = avio_rb32(context); + header->messageVersion = avio_rb32(context); + header->checksum = avio_rb32(context); + header->messageType = avio_rb32(context); + return 0; +} + +static int dspicReadClose( AVFormatContext *s ) +{ + return 0; +} + + +AVInputFormat ff_dspic_demuxer = { + .name = "dspic", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings Digital-Sprite format"), + .read_probe = dspicProbe, + .read_header = dspicReadHeader, + .read_packet = dspicReadPacket, + .read_close = dspicReadClose, +}; diff --git a/libavformat/libpar.c b/libavformat/libpar.c new file mode 100644 index 0000000000..6393b3e011 --- /dev/null +++ b/libavformat/libpar.c @@ -0,0 +1,1030 @@ +/* + * AD-Holdings PAR file demuxer + * Copyright (c) 2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 + */ + +/** + * @file + * AD-Holdings PAR file demuxer + */ + +#include +#include + +#include "avformat.h" +#include "internal.h" +#include "libavutil/avstring.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/parseutils.h" +#include "libavutil/time.h" +#include "libpar.h" +#include "adpic.h" + + +typedef struct { + ParDisplaySettings dispSet; + ParFrameInfo frameInfo; + int fileChanged; + int frameCached; + unsigned long seqStartAdded; +} PARDecContext; + +struct PAREncStreamContext { + int index; + char name[64]; + int camera; + int64_t startTime; + int utc_offset; + //struct PAREncStreamContext *next; +}; + +typedef struct { + ParFrameInfo frameInfo; + struct PAREncStreamContext master; + struct PAREncStreamContext stream[20]; + //struct PAREncStreamContext *firstStream; + int picHeaderSize; +} PAREncContext; + +#ifdef AD_SIDEDATA_IN_PRIV +void libpar_packet_destroy(struct AVPacket *packet); +#endif + + +const unsigned int MAX_FRAMEBUFFER_SIZE = 512 * 1024; + + +static void importMetadata(const AVDictionaryEntry *tag, struct PAREncStreamContext *ps) +{ + if (av_strcasecmp(tag->key, "title") == 0) + av_strlcpy(ps->name, tag->value, sizeof(ps->name)); + else if (av_strcasecmp(tag->key, "date") == 0) { + av_parse_time(&ps->startTime, tag->value, 0); + ps->startTime *= 1000; + } + else if (av_strcasecmp(tag->key, "track") == 0) + sscanf(tag->value, "%d", &(ps->camera)); + else if (av_strcasecmp(tag->key, "timezone") == 0) + sscanf(tag->value, "%d", &(ps->utc_offset)); +} + +static void parreaderLogger(int level, const char *format, va_list args) +{ + int av_log_level = -1; + switch(level) { + case (PARREADER_LOG_CRITICAL): + av_log_level = AV_LOG_FATAL; + break; + case(PARREADER_LOG_ERROR): + av_log_level = AV_LOG_ERROR; + break; + case(PARREADER_LOG_WARNING): + av_log_level = AV_LOG_WARNING; + break; + case(PARREADER_LOG_INFO): + av_log_level = AV_LOG_INFO; + break; + case(PARREADER_LOG_DEBUG): + av_log_level = AV_LOG_DEBUG; + break; + } + av_vlog(NULL, av_log_level, format, args); +} + + +static int par_write_header(AVFormatContext *avf) +{ + PAREncContext *p = avf->priv_data; + AVDictionaryEntry *tag = NULL; + int ii, result; + char *fnameNoExt; + + p->picHeaderSize = parReader_getPicStructSize(); + do { + tag = av_dict_get(avf->metadata, "", tag, AV_DICT_IGNORE_SUFFIX); + if (tag) + importMetadata(tag, &(p->master)); + } + while (tag); + + for (ii = 0; ii < avf->nb_streams; ii++) { + AVStream *st = avf->streams[ii]; + + // Set timebase to 1 millisecond, and min frame rate to 1 / timebase + avpriv_set_pts_info(st, 32, 1, 1000); + st->r_frame_rate = (AVRational) { 1, 1 }; + } + + // parReader_initWritePartition will automatically append .par to filename + // so strip it off name passed in to prevent file being called file.par.par + ii = strlen(avf->filename); + fnameNoExt = av_malloc(ii+1); + strcpy(fnameNoExt, avf->filename); + if (av_strcasecmp(avf->filename + ii - 4, ".par") == 0) + fnameNoExt[ii - 4] = '\0'; + result = parReader_initWritePartition(&p->frameInfo, fnameNoExt, -1); + av_free(fnameNoExt); + + if (result == 1) + return 0; + else + return AVERROR(EIO); +} + +static int par_write_packet(AVFormatContext *avf, AVPacket * pkt) +{ + PAREncContext *p = avf->priv_data; + //struct PAREncStreamContext *ps = p->firstStream; + struct PAREncStreamContext *ps = &(p->stream[pkt->stream_index]); + AVDictionaryEntry *tag = NULL; + int64_t parTime; + AVStream *stream = avf->streams[pkt->stream_index]; + void *hdr; + uint8_t *ptr; + int parFrameFormat; + int64_t srcTime = pkt->pts; + //uint32_t pktTypeCheck; + int written = 0; + int isADformat = 0; + + // Metadata + if (ps->camera < 1) { + // Copy over the values from the file data first + *ps = p->master; + + // Now check if there are stream-specific values + do { + tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX); + if (tag) + importMetadata(tag, ps); + } + while (tag); + + if (ps->camera < 1) + ps->camera = pkt->stream_index + 1; + if (strlen(ps->name) == 0) + snprintf(ps->name, sizeof(ps->name), "Camera %d", ps->camera); + } + + isADformat = 0; +#ifdef AD_SIDEDATA + av_packet_split_side_data(pkt); + for (int ii = 0; ii < pkt->side_data_elems; ii++) { + if (pkt->side_data[ii].type == AV_PKT_DATA_AD_FRAME) + isADformat = 1; + } +#endif + + if (isADformat) { + uint8_t *combBuf = NULL, *combPtr = NULL; + int combSize = 0; + for (int ii = 0; ii < pkt->side_data_elems; ii++) { +#ifdef AD_SIDEDATA + if ( (pkt->side_data[ii].type == AV_PKT_DATA_AD_FRAME) || (pkt->side_data[ii].type == AV_PKT_DATA_AD_TEXT) ) { + combBuf = av_realloc(combBuf, combSize + pkt->side_data[ii].size); + combPtr = combBuf + combSize; + memcpy(combPtr, pkt->side_data[ii].data, pkt->side_data[ii].size); + combSize += pkt->side_data[ii].size; + } +#endif + } + + combBuf = av_realloc(combBuf, combSize + pkt->size); + combPtr = combBuf + combSize; + memcpy(combPtr, pkt->data, pkt->size); + combSize += pkt->size; + + p->frameInfo.frameBuffer = combBuf; + p->frameInfo.frameBufferSize = combSize; + written = parReader_writePartition(&p->frameInfo); + return written; + } + else { + // PAR files have timestamps that are in UTC, not elapsed time + // So if the pts we have been given is the latter we need to + // add an offset to convert it to the former + + if (srcTime < 0) + srcTime = pkt->dts; + if ( (srcTime == 0) && (ps->startTime == 0) ) { + AVDictionaryEntry *ffstarttime = av_dict_get(avf->metadata, "creation_time", NULL, 0); + int64_t ffstarttimeint = 0; + if (ffstarttime) + sscanf(ffstarttime->value, "%"PRId64"", &ffstarttimeint); + + if (avf->start_time_realtime > 0) + ps->startTime = avf->start_time_realtime / 1000; + else if (&ffstarttimeint > 0) + ps->startTime = ffstarttimeint * 1000; + else + ps->startTime = av_gettime() / 1000; + } + parTime = srcTime; + if (parTime < ps->startTime) + parTime = parTime + ps->startTime; + + if (stream->codec->codec_id == AV_CODEC_ID_MJPEG) { + //p->frameInfo.frameBuffer = parReader_jpegToIMAGE(pkt->data, parTime, pkt->stream_index); + if ((stream->codec->pix_fmt == AV_PIX_FMT_YUV422P) || (stream->codec->pix_fmt == AV_PIX_FMT_YUVJ422P) ) + parFrameFormat = FRAME_FORMAT_JPEG_422; + else + parFrameFormat = FRAME_FORMAT_JPEG_411; + } + else if (stream->codec->codec_id == AV_CODEC_ID_MPEG4) { + if (pkt->flags & AV_PKT_FLAG_KEY) + parFrameFormat = FRAME_FORMAT_MPEG4_411_GOV_I; + else + parFrameFormat = FRAME_FORMAT_MPEG4_411_GOV_P; + } + else { + if (pkt->flags & AV_PKT_FLAG_KEY) + parFrameFormat = FRAME_FORMAT_H264_I; + else + parFrameFormat = FRAME_FORMAT_H264_P; + } + + hdr = parReader_generatePicHeader(ps->camera, + parFrameFormat, + pkt->size, + parTime, + ps->name, + stream->codec->width, + stream->codec->height, + ps->utc_offset + ); + p->frameInfo.frameBufferSize = pkt->size + p->picHeaderSize; + ptr = av_malloc(p->frameInfo.frameBufferSize); + if (ptr == NULL) + return AVERROR(ENOMEM); + p->frameInfo.frameBuffer = ptr; + memcpy(ptr, hdr, p->picHeaderSize); + memcpy(ptr + p->picHeaderSize, pkt->data, pkt->size); + written = parReader_writePartition(&p->frameInfo); + av_free(ptr); + parReader_freePicHeader(hdr); + + return written; + } +} + +static int par_write_trailer(AVFormatContext *avf) +{ + PAREncContext *p = avf->priv_data; + parReader_closeWritePartition(&p->frameInfo); + return 0; +} + +#ifdef AD_SIDEDATA_IN_PRIV +void libpar_packet_destroy(struct AVPacket *packet) +{ + LibparFrameExtra *fed = (LibparFrameExtra*)packet->priv; + + if (packet->data == NULL) { + return; + } + + if (fed) { + if (fed->frameInfo->frameBuffer) + av_free(fed->frameInfo->frameBuffer); + if (fed->frameInfo) + av_free(fed->frameInfo); + + if (fed->indexInfo) + parReader_freeIndexInfo(fed->indexInfo); + + av_free(fed); + } + + av_destruct_packet(packet); +} +#endif + +static void endianSwapAudioData(uint8_t *data, int size) +{ + const uint8_t *dataEnd = data + size; + uint8_t upper, lower; + uint16_t predictor = AV_RB16(data); + + AV_WL16(data, predictor); + data += 4; + + for (;data < dataEnd; data++) { + upper = ((*data) & 0xF0) >> 4; + lower = ((*data) & 0x0F) << 4; + *data = upper | lower; + } +} + +static int64_t getLastFrameTime(int fc, ParFrameInfo *fi, ParDisplaySettings *disp) +{ + int64_t lastFrame = 0; + int isReliable; + + + lastFrame = parReader_getEndTime(fi, &isReliable) * 1000LL; + + if (isReliable == 0) { + long startFrame = fi->frameNumber; + int streamId = fi->channel; + int ii = 1; + + disp->cameraNum = fi->channel & 0xFFFF; + disp->fileSeqNo = -1; + disp->fileLock = 1; // Don't seek beyond the file + do { + disp->frameNumber = fc - ii++; + if (disp->frameNumber <= startFrame) + break; + parReader_loadFrame(fi, disp, NULL); + } while (streamId != fi->channel); + + lastFrame = fi->imageTime * 1000LL + fi->imageMS; + + // Now go back to where we were and reset the state + disp->playMode = RWND; + disp->frameNumber = startFrame; + parReader_loadFrame(fi, disp, NULL); + disp->playMode = PLAY; + disp->fileLock = 0; + } + + return lastFrame; +} + +static AVStream* createStream(AVFormatContext * avf) +{ + PARDecContext *p = avf->priv_data; + ParFrameInfo *fi = (ParFrameInfo *)&p->frameInfo; + char textbuf[128]; + int w, h; + int fc = 0; + + unsigned long startT, endT; + AVStream * st = NULL; + + if ((NULL==avf) || (NULL==fi) || (NULL==fi->frameBuffer)) + return NULL; + + st = avformat_new_stream(avf, NULL); + st->id = fi->channel; + + parReader_getIndexData(fi, NULL, &fc, &startT, &endT); + + if (parReader_frameIsVideo(fi)) { + st->codec->codec_type = AVMEDIA_TYPE_VIDEO; + + switch(parReader_getFrameSubType(fi)) { + case(FRAME_FORMAT_JPEG_422): + case(FRAME_FORMAT_JPEG_411): + st->codec->codec_id = AV_CODEC_ID_MJPEG; + st->codec->has_b_frames = 0; + break; + case(FRAME_FORMAT_MPEG4_411): + case(FRAME_FORMAT_MPEG4_411_I): + case(FRAME_FORMAT_MPEG4_411_GOV_P): + case(FRAME_FORMAT_MPEG4_411_GOV_I): + st->codec->codec_id = AV_CODEC_ID_MPEG4; + break; + case(FRAME_FORMAT_RAW_422I): + case(FRAME_FORMAT_H264_I): + case(FRAME_FORMAT_H264_P): + st->codec->codec_id = AV_CODEC_ID_H264; + break; + case(FRAME_FORMAT_PBM): + st->codec->codec_id = AV_CODEC_ID_PBM; + break; + default: + // Set unknown types to data so we don't try and play them + st->codec->codec_type = AVMEDIA_TYPE_DATA; + st->codec->codec_id = AV_CODEC_ID_NONE; + break; + } + + if (AVMEDIA_TYPE_VIDEO == st->codec->codec_type) { + if (AV_CODEC_ID_PBM != st->codec->codec_id) { + parReader_getFrameSize(fi, &w, &h); + st->codec->width = w; + st->codec->height = h; + + // Set pixel aspect ratio, display aspect is (sar * width / height) + /// \todo Could set better values here by checking resolutions and + /// assuming PAL/NTSC aspect + if( (w > 360) && (h < 480) ) + st->sample_aspect_ratio = (AVRational) { 1, 2 }; + else + st->sample_aspect_ratio = (AVRational) { 1, 1 }; + + parReader_getStreamName(fi->frameBuffer, + fi->frameBufferSize, + textbuf, + sizeof(textbuf)); + av_dict_set(&st->metadata, "title", textbuf, 0); + + parReader_getStreamDate(fi, textbuf, sizeof(textbuf)); + av_dict_set(&st->metadata, "date", textbuf, 0); + + snprintf(textbuf, sizeof(textbuf), "%d", fi->channel & 0xFFFF); + av_dict_set(&st->metadata, "track", textbuf, 0); + + av_dict_set(&st->metadata, "type", "camera", 0); + } + else { + av_dict_set(&st->metadata, "type", "mask", 0); + } + } + + // Set timebase to 1 millisecond, and min frame rate to 1 / timebase + avpriv_set_pts_info(st, 32, 1, 1000); + st->r_frame_rate = (AVRational) { 1, 1 }; + st->start_time = fi->imageTime * 1000LL + fi->imageMS; + st->duration = getLastFrameTime(fc, fi, &p->dispSet) - st->start_time; + } + else if (parReader_frameIsAudio(fi)) { + st->codec->codec_type = AVMEDIA_TYPE_AUDIO; + st->codec->channels = 1; + st->codec->block_align = 0; + st->start_time = fi->imageTime * 1000LL + fi->imageMS; + st->duration = getLastFrameTime(fc, fi, &p->dispSet) - st->start_time; + + switch(parReader_getFrameSubType(fi)) { + case(FRAME_FORMAT_AUD_ADPCM_8000): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 8000; + break; + case(FRAME_FORMAT_AUD_ADPCM_16000): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 16000; + break; + case(FRAME_FORMAT_AUD_L16_44100): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 441000; + break; + case(FRAME_FORMAT_AUD_ADPCM_11025): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 11025; + break; + case(FRAME_FORMAT_AUD_ADPCM_22050): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 22050; + break; + case(FRAME_FORMAT_AUD_ADPCM_32000): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 32000; + break; + case(FRAME_FORMAT_AUD_ADPCM_44100): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 44100; + break; + case(FRAME_FORMAT_AUD_ADPCM_48000): + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->bits_per_coded_sample = 4; + st->codec->sample_rate = 48000; + break; + case(FRAME_FORMAT_AUD_L16_8000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 8000; + break; + case(FRAME_FORMAT_AUD_L16_11025): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 11025; + break; + case(FRAME_FORMAT_AUD_L16_16000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 16000; + break; + case(FRAME_FORMAT_AUD_L16_22050): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 22050; + break; + case(FRAME_FORMAT_AUD_L16_32000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 32000; + break; + case(FRAME_FORMAT_AUD_L16_48000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 48000; + break; + case(FRAME_FORMAT_AUD_L16_12000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 12000; + break; + case(FRAME_FORMAT_AUD_L16_24000): + st->codec->codec_id = AV_CODEC_ID_PCM_S16LE; + st->codec->bits_per_coded_sample = 16; + st->codec->sample_rate = 24000; + break; + default: + st->codec->codec_id = AV_CODEC_ID_ADPCM_IMA_WAV; + st->codec->sample_rate = 8000; + break; + } + avpriv_set_pts_info(st, 64, 1, st->codec->sample_rate); + + if (fi->channel & 0x8000) { + // Camera associated audio + snprintf(textbuf, sizeof(textbuf), "%d", fi->channel & ~0x8000); + av_dict_set(&st->metadata, "track", textbuf, 0); + av_dict_set(&st->metadata, "associated", "1", 0); + } + else { + snprintf(textbuf, sizeof(textbuf), "%d", fi->channel); + av_dict_set(&st->metadata, "track", textbuf, 0); + av_dict_set(&st->metadata, "associated", "0", 0); + } + } + else { + st->codec->codec_type = AVMEDIA_TYPE_DATA; + + // Set timebase to 1 millisecond, and min frame rate to 1 / timebase + avpriv_set_pts_info(st, 32, 1, 1000); + st->r_frame_rate = (AVRational) { 1, 1 }; + + st->start_time = startT * 1000LL; + st->duration = getLastFrameTime(fc, fi, &p->dispSet) - st->start_time; + } + + return st; +} + +static int createPacket(AVFormatContext *avf, AVPacket *pkt, int siz) +{ + PARDecContext *ctxt = avf->priv_data; + ParFrameInfo *fi = &ctxt->frameInfo; + int id = fi->channel; + int ii; + AVStream *st = NULL; + +#if defined(AD_SIDEDATA_IN_PRIV) + LibparFrameExtra *pktExt = NULL; + ParFrameInfo *pktFI = NULL; +#elif defined(AD_SIDEDATA) + int adDataSize = 0; + uint8_t *sideData = NULL; + int textBufSize = 0; +#endif + + for(ii = 0; ii < avf->nb_streams; ii++) { + if ( (NULL != avf->streams[ii]) && (avf->streams[ii]->id == id) ) { + st = avf->streams[ii]; + break; + } + } + if (NULL == st) { + st = createStream(avf); + if (st == NULL) + return -1; + } + + + // If frame is MPEG4 video and is the first sent then add a VOL header to it + if (st->codec->codec_id == AV_CODEC_ID_MPEG4) { + if ((ctxt->seqStartAdded & (1<index)) == 0) { + if (parReader_isIFrame(fi)) { + if (parReader_add_start_of_sequence(fi->frameBuffer)) + ctxt->seqStartAdded |= 1 << st->index; + } + } + } + + if (st->codec->codec_id == AV_CODEC_ID_PBM) { + char *comment = NULL; + int w, h; + uint8_t *pbm = av_malloc(fi->size); + memcpy(pbm, fi->frameData, fi->size); + pkt->size = ad_pbmDecompress(&comment, &pbm, fi->size, pkt, &w, &h); + if (pkt->size > 0) { + st->codec->width = w; + st->codec->height = h; + } + if (comment) { + int camera = parReader_getCamera(fi, NULL, 0); + char name[128]; + snprintf(name, sizeof(name), "Camera %u: %s", camera, comment); + av_dict_set(&st->metadata, "title", name, 0); + av_free(comment); + } + } + else { + if ( ((uint8_t*)fi->frameData + siz) < ((uint8_t*)fi->frameBuffer + MAX_FRAMEBUFFER_SIZE) ) { + av_new_packet(pkt, siz); + + if (NULL == pkt->data) { + pkt->size = 0; + return AVERROR(ENOMEM); + } + memcpy(pkt->data, fi->frameData, siz); + } + else { + av_log(avf, AV_LOG_ERROR, "Copying %d bytes would read beyond framebuffer end", siz); + return AVERROR(ENOMEM); + } + } + pkt->stream_index = st->index; + + if (parReader_frameIsAudio(fi)) { + if (st->codec->codec_id == AV_CODEC_ID_ADPCM_IMA_WAV) + endianSwapAudioData(pkt->data, siz); + } + else if (parReader_frameIsVideo(fi)) { + if (parReader_isIFrame(fi)) + pkt->flags |= AV_PKT_FLAG_KEY; + } + + if (fi->imageTime > 0) { + pkt->pts = fi->imageTime; + pkt->pts *= 1000ULL; + pkt->pts += fi->imageMS; + } + else if (fi->indexTime > 0) { + pkt->pts = fi->indexTime; + pkt->pts *= 1000ULL; + pkt->pts += fi->indexMS; + } + else { + pkt->pts = AV_NOPTS_VALUE; + } + pkt->dts = pkt->pts; + pkt->duration = 1; + +#if defined(AD_SIDEDATA_IN_PRIV) + pkt->destruct = libpar_packet_destroy; + pktExt = av_malloc(sizeof(LibparFrameExtra)); + if (NULL == pktExt) { + pkt->size = 0; + return AVERROR(ENOMEM); + } + pktExt->fileChanged = ctxt->fileChanged; + pktExt->indexInfoCount = parReader_getIndexInfo(fi, &pktExt->indexInfo); + + pktExt->frameInfo = av_mallocz(sizeof(ParFrameInfo)); + if (NULL == pktExt->frameInfo) { + pkt->size = 0; + return AVERROR(ENOMEM); + } + pktFI = pktExt->frameInfo; + if (parReader_frameIsVideo(fi)) + pktFI->frameBufferSize = parReader_getPicStructSize(); + else if (parReader_frameIsAudio(fi)) + pktFI->frameBufferSize = parReader_getAudStructSize(); + + if (pktFI->frameBufferSize > 0) { + // Make a copy of the ParFrameInfo struct and the frame header + // for use by client code that knows this is here + + // Save frameBufferSize as it's about to be overwritten by memcpy + int fbs = pktFI->frameBufferSize; + memcpy(pktFI, fi, sizeof(ParFrameInfo)); + pktFI->frameBufferSize = fbs; + pktFI->frameBuffer = av_malloc(fbs); + if (NULL == pktFI->frameBuffer) { + pkt->size = 0; + return AVERROR(ENOMEM); + } + memcpy(pktFI->frameBuffer, fi->frameBuffer, fbs); + pktFI->frameData = NULL; + } + + pkt->priv = pktExt; +#elif defined(AD_SIDEDATA) + if (parReader_frameIsVideo(fi)) + adDataSize = parReader_getPicStructSize(); + else if (parReader_frameIsAudio(fi)) + adDataSize = parReader_getAudStructSize(); + if (adDataSize > 0) { + sideData = av_packet_new_side_data(pkt, AV_PKT_DATA_AD_FRAME, adDataSize); + if (sideData) + memcpy(sideData, fi->frameBuffer, adDataSize); + } + + if (fi->frameText) { + textBufSize = strlen(fi->frameText) + 1; + sideData = av_packet_new_side_data(pkt, AV_PKT_DATA_AD_TEXT, textBufSize); + if (sideData) + memcpy(sideData, fi->frameText, textBufSize); + } + + if (ctxt->fileChanged) { + int fc = 0; + unsigned long startT, endT, lastFT; + + parReader_getIndexData(fi, NULL, &fc, &startT, &endT); + lastFT = getLastFrameTime(fc, fi, &ctxt->dispSet); + for (ii = 0; ii < avf->nb_streams; ii++) { + st->start_time = fi->imageTime * 1000LL + fi->imageMS; + st->duration = lastFT - st->start_time; + } + + sideData = av_packet_new_side_data(pkt, AV_PKT_DATA_AD_PARINF, sizeof(ctxt->frameInfo)); + if (sideData) + memcpy(sideData, &(ctxt->frameInfo), sizeof(ctxt->frameInfo)); + ctxt->fileChanged = 0; + } +#endif + + ctxt->frameCached = 0; + + return 0; +} + + +static int par_probe(AVProbeData *p) +{ + unsigned long first4; + if (p->buf_size < 4) + return 0; + + first4 = *((unsigned long *)p->buf); + first4 = av_le2ne32(first4); + if (first4 == 0x00524150) + return AVPROBE_SCORE_MAX; + else + return 0; +} + +static int par_read_header(AVFormatContext * avf) +{ + int res, siz; + PARDecContext *p = avf->priv_data; + char **filelist; + int seqLen; + int64_t seconds = 0; + AVStream *strm = NULL; + char textbuf[128]; + + + if (parReader_version(textbuf, sizeof(textbuf)) > 0) { + av_log(avf, AV_LOG_INFO, "ParReader library version: %s\n", textbuf); + av_dict_set(&avf->metadata, "ParReader", textbuf, 0); + } + + parReader_initFrameInfo(&p->frameInfo, MAX_FRAMEBUFFER_SIZE, av_malloc(MAX_FRAMEBUFFER_SIZE)); + if (p->frameInfo.frameBuffer == NULL) + return AVERROR(ENOMEM); + + switch(av_log_get_level()) { + case(AV_LOG_QUIET): + parReader_setLogLevel(-1); + break; + case(AV_LOG_PANIC): + parReader_setLogLevel(PARREADER_LOG_CRITICAL); + break; + case(AV_LOG_FATAL): + parReader_setLogLevel(PARREADER_LOG_CRITICAL); + break; + case(AV_LOG_ERROR): + parReader_setLogLevel(PARREADER_LOG_ERROR); + break; + case(AV_LOG_WARNING): + parReader_setLogLevel(PARREADER_LOG_WARNING); + break; + case(AV_LOG_INFO): + parReader_setLogLevel(PARREADER_LOG_INFO); + break; + case(AV_LOG_VERBOSE): + parReader_setLogLevel(PARREADER_LOG_INFO); + break; + case(AV_LOG_DEBUG): + parReader_setLogLevel(PARREADER_LOG_DEBUG); + break; + } + parReader_setLogCallback(parreaderLogger); + + parReader_setDisplaySettingsDefaults(&p->dispSet); + + res = parReader_loadParFile(NULL, avf->filename, -1, &p->frameInfo, 0); + if (0 == res) + return AVERROR(EIO); + + seqLen = parReader_getFilelist(&p->frameInfo, &filelist); + if (1 == seqLen) { + int frameNumber, frameCount; + unsigned long start, end; + if (parReader_getIndexData(&p->frameInfo, &frameNumber, &frameCount, &start, &end)) + seconds = end - start; + } + else { + int res, frameNumber, frameCount; + unsigned long start, end, realStart, realEnd; + if (parReader_getIndexData(&p->frameInfo, &frameNumber, &frameCount, &start, &end)) { + realStart = start; + av_log(avf, AV_LOG_DEBUG, "par_read_header: %s (%d)\n", filelist[0], seqLen - 1); + res = parReader_loadParFile(NULL, filelist[0], seqLen - 1, &p->frameInfo, 0); + if (res && parReader_getIndexData(&p->frameInfo, &frameNumber, &frameCount, &start, &end)) { + realEnd = end; + seconds = realEnd - realStart; + } + av_log(avf, AV_LOG_DEBUG, "par_read_header: %s (%d)\n", filelist[0], res); + res = parReader_loadParFile(NULL, filelist[0], -1, &p->frameInfo, 0); + } + } + + siz = parReader_loadFrame(&p->frameInfo, &p->dispSet, &p->fileChanged); + + p->frameCached = siz; + p->fileChanged = 1; + p->seqStartAdded = 0; + + snprintf(textbuf, sizeof(textbuf), "%d", parReader_getUTCOffset(&p->frameInfo)); + av_dict_set(&avf->metadata, "timezone", textbuf, 0); + + strm = createStream(avf); +// if (strm) { +// // Note: Do not set avf->start_time, ffmpeg computes it from AVStream values +// avf->duration = av_rescale_q(seconds, secondsTB, strm->time_base); +// } + + avf->ctx_flags |= AVFMTCTX_NOHEADER; + + return 0; +} + +static int par_read_packet(AVFormatContext * avf, AVPacket * pkt) +{ + PARDecContext *p = avf->priv_data; + int siz = 0; + + if (p->frameCached) { + siz = p->frameCached; + } + else + siz = parReader_loadFrame(&p->frameInfo, &p->dispSet, &p->fileChanged); + + if (siz < 0) { + p->frameCached = 0; + p->fileChanged = 0; + pkt->size = 0; + return AVERROR_EOF; + } + + if (p->fileChanged) + parReader_getFilename(&p->frameInfo, avf->filename, sizeof(avf->filename)); + + if ( (siz == 0) || (NULL == p->frameInfo.frameData) ) { + p->frameCached = 0; + return AVERROR(EAGAIN); + } + + return createPacket(avf, pkt, siz); +} + +static int par_read_seek(AVFormatContext *avf, int stream, + int64_t target, int flags) +{ + PARDecContext *p = avf->priv_data; + int siz = 0; + int streamId = 0; + int isKeyFrame = 0; + int step; + int anyStreamWillDo = 0; + int prevPlayMode, prevLock; + + av_log(avf, AV_LOG_DEBUG, "par_read_seek target = %"PRId64"\n", target); + + if ((stream < 0) || (stream >= avf->nb_streams)) { + anyStreamWillDo = 1; + streamId = avf->streams[0]->id; + } + else + streamId = avf->streams[stream]->id; + + prevPlayMode = p->dispSet.playMode; + prevLock = p->dispSet.fileLock; + + p->seqStartAdded = 0; + p->dispSet.cameraNum = streamId; + if (flags & AVSEEK_FLAG_BACKWARD) + p->dispSet.playMode = RWND; + + if ( (flags & AVSEEK_FLAG_FRAME) && (target < 0) ) { + p->dispSet.fileSeqNo = (-target) - 1; + p->dispSet.frameNumber = 0; + } + else { + p->dispSet.fileSeqNo = -1; + + if (flags & AVSEEK_FLAG_FRAME) { + // Don't seek beyond the file + p->dispSet.fileLock = 1; + p->dispSet.frameNumber = target; + } + else { + p->dispSet.timestamp = target / 1000LL; + p->dispSet.millisecs = target % 1000; + } + } + + do { + siz = parReader_loadFrame(&p->frameInfo, &p->dispSet, &p->fileChanged); + + // If this frame is not acceptable we want to just iterate through + // until we find one that is, not seek again, so reset targets + p->dispSet.frameNumber = -1; + p->dispSet.timestamp = 0; + p->dispSet.millisecs = 0; + + if (siz < 0) + break; + + if (parReader_frameIsVideo(&p->frameInfo)) { + if (flags & AVSEEK_FLAG_ANY) + isKeyFrame = 1; + else + isKeyFrame = parReader_isIFrame(&p->frameInfo); + } + else { + // Always seek to a video frame + isKeyFrame = 0; + } + + // If we don't care which stream then force the streamId to match + if (anyStreamWillDo) + streamId = p->frameInfo.channel; + } + while ( (streamId != p->frameInfo.channel) || (0 == isKeyFrame) ); + + p->dispSet.fileLock = prevLock; + p->dispSet.playMode = prevPlayMode; + + if (siz > 0) { + p->frameCached = siz; + for(step = 0; step < avf->nb_streams; step++) { + if ( (NULL != avf->streams[step]) && (avf->streams[step]->id == streamId) ) { + avf->streams[step]->codec->frame_number = p->frameInfo.frameNumber; + break; + } + } + av_log(avf, AV_LOG_DEBUG, "par_read_seek seek done = %lu\n", p->frameInfo.imageTime); + return p->frameInfo.imageTime; + } + else { + av_log(avf, AV_LOG_DEBUG, "par_read_seek seek failed\n"); + return -1; + } +} + +static int par_read_close(AVFormatContext * avf) +{ + PARDecContext *p = avf->priv_data; + av_log(avf, AV_LOG_DEBUG, "par_read_close"); + av_free(p->frameInfo.frameBuffer); + parReader_closeParFile(&p->frameInfo); + return 0; +} + + +AVOutputFormat ff_libparreader_muxer = { + .name = "libpar", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings PAR format"), + .mime_type = "video/adhbinary", + .extensions = "par", + .priv_data_size = sizeof(PAREncContext), + .audio_codec = AV_CODEC_ID_ADPCM_IMA_WAV, + .video_codec = AV_CODEC_ID_MJPEG, + .write_header = par_write_header, + .write_packet = par_write_packet, + .write_trailer = par_write_trailer, + .flags = AVFMT_GLOBALHEADER, +}; + +AVInputFormat ff_libparreader_demuxer = { + .name = "libpar", + .long_name = NULL_IF_CONFIG_SMALL("AD-Holdings PAR format"), + .priv_data_size = sizeof(PARDecContext), + .read_probe = par_probe, + .read_header = par_read_header, + .read_packet = par_read_packet, + .read_close = par_read_close, + .read_seek = par_read_seek, + .flags = AVFMT_TS_DISCONT | AVFMT_VARIABLE_FPS | AVFMT_NO_BYTE_SEEK, +}; diff --git a/libavformat/libpar.h b/libavformat/libpar.h new file mode 100644 index 0000000000..2d958727ba --- /dev/null +++ b/libavformat/libpar.h @@ -0,0 +1,40 @@ +/* + * copyright (c) 2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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_LIBPAR_H +#define AVFORMAT_LIBPAR_H + +#include "avformat.h" + + +#ifdef AD_SIDEDATA_IN_PRIV +#include + +typedef struct { + int indexInfoCount; + ParFrameInfo *frameInfo; + ParKeyValuePair *indexInfo; + int fileChanged; +} LibparFrameExtra; + +#endif // AD_SIDEDATA_IN_PRIV + +#endif /* AVFORMAT_LIBPAR_H */ diff --git a/libavformat/netvu.c b/libavformat/netvu.c new file mode 100644 index 0000000000..fdac338f21 --- /dev/null +++ b/libavformat/netvu.c @@ -0,0 +1,214 @@ +/* + * Netvu protocol for ffmpeg client + * Copyright (c) 2010 AD-Holdings plc + * Modified for FFmpeg by Tom Needham <06needhamt@gmail.com> (2018) + * + * 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 +#include + +#include "avformat.h" +#include "internal.h" +#include "http.h" +#include "netvu.h" +#include "libavutil/avstring.h" + + +static void copy_value_to_field(const char *value, char **dest) +{ + int len = strlen(value) + 1; + if (*dest != NULL) + av_free(*dest); + + *dest = av_malloc(len); + if (*dest) { + av_strlcpy(*dest, value, len); + } +} + +static void netvu_parse_content_type_header(char * p, NetvuContext *nv) +{ + int finishedContentHeader = 0; + char * name = NULL; + char * value = NULL; + + //strip the content-type from the headder + value = p; + while((*p != ';') && (*p != '\0')) + p++; + + if(*p == '\0') + finishedContentHeader = 1; + + *p = '\0'; + p++; + copy_value_to_field( value, &nv->hdrs[NETVU_CONTENT] ); + + while( *p != '\0' && finishedContentHeader != 1) { + while(isspace(*p)) + p++; // Skip whitespace + name = p; + + // Now we get attributes in = pairs + while (*p != '\0' && *p != '=') + p++; + + if (*p != '=') + return; + + *p = '\0'; + p++; + + value = p; + + while (*p != '\0' && *p != ';') + p++; + + if (*p == ';') { + *p = '\0'; + p++; + } + + // Strip any "s off + if( strlen(value) > 0 ) { + int ii; + + if( *value == '"' && *(value + strlen(value) - 1) == '"' ) { + *(value + strlen(value) - 1) = '\0'; + value += 1; + } + + // Copy the attribute into the relevant field + for(ii = 0; ii < NETVU_MAX_HEADERS; ii++) { + if( av_strcasecmp(name, nv->hdrNames[ii] ) == 0 ) + copy_value_to_field(value, &nv->hdrs[ii]); + } + if(av_strcasecmp(name, "utc_offset") == 0) + nv->utc_offset = atoi(value); + } + } +} + +static void processLine(char *line, NetvuContext *nv) +{ + char *p = line; + char *tag; + + while (*p != '\0' && *p != ':') + p++; + if (*p != ':') + return; + + *p = '\0'; + tag = line; + p++; + while (isspace(*p)) + p++; + + if (!av_strcasecmp (tag, nv->hdrNames[NETVU_CONTENT])) + netvu_parse_content_type_header(p, nv); + else if(!av_strcasecmp(tag, nv->hdrNames[NETVU_SERVER])) + copy_value_to_field( p, &nv->hdrs[NETVU_SERVER]); +} + +static int netvu_open(URLContext *h, const char *uri, int flags) +{ + char hostname[1024], auth[1024], path[1024], http[1024]; + int port, err; + NetvuContext *nv = h->priv_data; + + nv->hdrNames[NETVU_SERVER] = "Server"; + nv->hdrNames[NETVU_CONTENT] = "Content-type"; + nv->hdrNames[NETVU_RESOLUTION] = "resolution"; + nv->hdrNames[NETVU_COMPRESSION] = "compression"; + nv->hdrNames[NETVU_RATE] = "rate"; + nv->hdrNames[NETVU_PPS] = "pps"; + nv->hdrNames[NETVU_SITE_ID] = "site_id"; + nv->hdrNames[NETVU_BOUNDARY] = "boundary"; + // Set utc_offset an invalid value so if server doesn't set it we can ignore it + nv->utc_offset = 1441; + + h->is_streamed = 1; + + av_url_split(NULL, 0, auth, sizeof(auth), hostname, sizeof(hostname), &port, + path, sizeof(path), uri); + if (port < 0) + port = 80; + ff_url_join(http, sizeof(http), "http", auth, hostname, port, "%s", path); + + err = ffurl_open(&nv->hd, http, AVIO_FLAG_READ, &h->interrupt_callback, NULL); + if (err >= 0) { + char headers[1024]; + char *startOfLine = &headers[0]; + int ii; + + size_t hdrSize = ff_http_get_headers(nv->hd, headers, sizeof(headers)); + if (hdrSize > 0) { + for (ii = 0; ii < hdrSize; ii++) { + if (headers[ii] == '\n') { + headers[ii] = '\0'; + processLine(startOfLine, nv); + startOfLine = &headers[ii+1]; + } + } + } + return 0; + } + else { + if (nv->hd) + ffurl_close(nv->hd); + nv->hd = NULL; + return AVERROR(EIO); + } +} + +static int netvu_read(URLContext *h, uint8_t *buf, int size) +{ + NetvuContext *nv = h->priv_data; + if (nv->hd) + return ffurl_read(nv->hd, buf, size); + return AVERROR_PROTOCOL_NOT_FOUND; +} + +static int netvu_close(URLContext *h) +{ + NetvuContext *nv = h->priv_data; + int i, ret = 0; + + if (nv->hd) + ret = ffurl_close(nv->hd); + + for (i = 0; i < NETVU_MAX_HEADERS; i++) { + if (nv->hdrs[i]) + av_free(nv->hdrs[i]); + } + + return ret; +} + + +URLProtocol ff_netvu_protocol = { + .name = "netvu", + .url_open = netvu_open, + .url_read = netvu_read, + .url_close = netvu_close, + .priv_data_size = sizeof(NetvuContext), + .flags = URL_PROTOCOL_FLAG_NETWORK, +}; diff --git a/libavformat/netvu.h b/libavformat/netvu.h new file mode 100644 index 0000000000..4dd72a6e19 --- /dev/null +++ b/libavformat/netvu.h @@ -0,0 +1,21 @@ +#include "avformat.h" + +enum NetvuHeaders { NETVU_SERVER = 0, + NETVU_CONTENT, + NETVU_RESOLUTION, + NETVU_COMPRESSION, + NETVU_RATE, + NETVU_PPS, + NETVU_SITE_ID, + NETVU_BOUNDARY, + NETVU_MAX_HEADERS + }; + +typedef struct { + const AVClass *class; + URLContext *hd; + + char* hdrs[NETVU_MAX_HEADERS]; + const char* hdrNames[NETVU_MAX_HEADERS]; + int utc_offset; +} NetvuContext; diff --git a/libavformat/version.h b/libavformat/version.h index 39b00f62ab..22ed534bfb 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -32,8 +32,8 @@ // 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 28 -#define LIBAVFORMAT_VERSION_MICRO 101 +#define LIBAVFORMAT_VERSION_MINOR 29 +#define LIBAVFORMAT_VERSION_MICRO 100