diff --git a/Changelog b/Changelog
index 86167b76a1..41d12c092e 100644
--- a/Changelog
+++ b/Changelog
@@ -35,6 +35,7 @@ version <next>:
 - 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 <strings.h>
+
+#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 = "<infoList>";
+                    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 <strings.h>
+
+#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 <time.h>
+#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 <strings.h>
+
+#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=<mode>
+                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 <strings.h>
+
+#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 <stdint.h>
+
+
+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 <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#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 <strings.h>
+#include <parreader.h>
+
+#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<<st->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 <parreader_types.h>
+
+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 <ctype.h>
+#include <string.h>
+#include <strings.h>
+
+#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 <name>=<value> 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

