[FFmpeg-devel] [PATCH] WIP Vivo demuxer

Daniel Verkamp daniel
Sun Mar 22 00:33:57 CET 2009


On Sat, Mar 21, 2009 at 11:14 AM, Reimar D?ffinger
<Reimar.Doeffinger at gmx.de> wrote:
> On Sat, Mar 21, 2009 at 10:32:52AM -0500, Daniel Verkamp wrote:
>> NB: This patch is still a huge mess and not intended for review per se yet.
>
> Well, but since I have to read it anyway.
>
>> + ? ?c = *buf++;
>> +
>> + ? ?// stream must start with packet of type 0 and sequence number 0
>> + ? ?if (c != 0)
>> + ? ? ? ?return 0;
>
> Using that c variable here is rather pointless
>

Fixed.

>> + ? ?// read at most 2 bytes of coded length
>> + ? ?for (i = 0; i < 2; i++) {
>> + ? ? ? ?c = *buf++;
>> + ? ? ? ?len = (len << 7) | (c & 0x7F);
>> + ? ? ? ?if (~c & 0x80)
>> + ? ? ? ? ? ?break;
>> + ? ?}
>> +
>> + ? ?if (i == 2 || len > 1024 || len < 21)
>> + ? ? ? ?return 0;
>
> I think this would be a good deal simpler without the loop.
>

Ok, changed this, as well as the other place where length is read, so
that it only reads two bytes max without the loop.  This allows for
lengths up to 16383 at most.

>> +static int vivo_read_text_header(AVFormatContext *s, VivoPacket *vpkt,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? AVStream *vst, AVStream *ast)
>> +{
>> + ? ?VivoContext *vivo = s->priv_data;
>> + ? ?ByteIOContext *pb = s->pb;
>> + ? ?unsigned char text[vpkt->len + 1];
>
> Uh, vpkt->len can be arbitrarily long, you certainly don't want to
> 1) just add 1 to it
> 2) allocate it on the stack
>

Since I changed the length reading above, vpkt->len will be at most
16383; is that good enough to ensure nothing bad happens here?

>> + ? ?text[vpkt->len] = '\0';
>
> I think we generally prefer 0 over '\0' around here.
>

Changed.

>> + ? ? ? ?value = strchr(key, ':');
>> + ? ? ? ?if (!value) {
>> + ? ? ? ? ? ?av_log(s, AV_LOG_ERROR, "missing colon in key:value pair\n");
>> + ? ? ? ? ? ?return -1;
>> + ? ? ? ?}
>
> Skipping to the next line seems more reasonable than failing completely.
>

Changed.

>> + ? ? ? ?value_int = atoi(value);
>
> strtol to actually check for errors might make more sense.
>
>> + ? ? ? ?return AVERROR(ENOMEM); // FIXME - memleak etc.
>> + ? ? ? ? ? ?return -1; // FIXME - memleak etc.
>> + ? ? ? ? ? ?return -1; // FIXME - memleak etc.
>
> I don't see any memleaks etc. you'd have to handle here.
>

Do I not need to free the earlier streams from av_new_stream()?

>> + ? ?av_set_pts_info(vst, 64, vivo->timebase.num, vivo->timebase.den);
>> + ? ?av_set_pts_info(ast, 64, 1, ast->codec->sample_rate);
>
> Your audio_pts/video_pts is int, but here you claim to have 64 bit of
> pts information...
>

Too much copy and paste, I suppose; how much PTS do I really have?
It's not coded in the file, as far as I know...

>> + ? ?pkt->stream_index = stream_index;
>> + ? ?if (stream_index == 0)
>> + ? ? ? ?pkt->pts = vivo->video_pts++;
>> + ? ?else
>> + ? ? ? ?pkt->pts = vivo->audio_pts++;
>
> This means that the audio packet comes 1/ast->codec->sample_rate seconds
> after the previous, or to put it differently that the packet only
> contains one audio sample...
> No wonder you have A-V sync issues.

Still not sure how this is all supposed to work, but hopefully a
little closer... some samples have weirdness with the H.263 stream
framerate different than the container framerate, and the
TimeUnitNumerator/Denominator fields in the header also don't make
sense when compared to FPS, so some investigation is still needed.
The mplayer demuxer seems to be confused about audio bitrate as well;
it says that 24-byte audio packets indicate 24 kbps audio, but that
doesn't work out to the sizes I see in actual streams.

The mplayer demuxer also might have a hint to the problem H.263
samples - h263_decode_picture_header() has some handling of a "custom
source format" for H.263v2 which seems to match up to the UFEP = 6
messages.

Thanks,
-- Daniel Verkamp
-------------- next part --------------
>From 27444a512ffadccc1c45fabcd82e7422fece4170 Mon Sep 17 00:00:00 2001
From: Daniel Verkamp <daniel at drv.nu>
Date: Fri, 20 Mar 2009 20:21:49 -0500
Subject: [PATCH] work in progress vivo demuxer

---
 libavformat/Makefile     |    1 +
 libavformat/allformats.c |    1 +
 libavformat/vivodec.c    |  346 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 348 insertions(+), 0 deletions(-)
 create mode 100644 libavformat/vivodec.c

diff --git a/libavformat/Makefile b/libavformat/Makefile
index f2285d7..5f64edd 100644
--- a/libavformat/Makefile
+++ b/libavformat/Makefile
@@ -214,6 +214,7 @@ OBJS-$(CONFIG_TXD_DEMUXER)               += txd.o
 OBJS-$(CONFIG_VC1_DEMUXER)               += raw.o
 OBJS-$(CONFIG_VC1T_DEMUXER)              += vc1test.o
 OBJS-$(CONFIG_VC1T_MUXER)                += vc1testenc.o
+OBJS-$(CONFIG_VIVO_DEMUXER)              += vivodec.o
 OBJS-$(CONFIG_VMD_DEMUXER)               += sierravmd.o
 OBJS-$(CONFIG_VOC_DEMUXER)               += vocdec.o voc.o
 OBJS-$(CONFIG_VOC_MUXER)                 += vocenc.o voc.o
diff --git a/libavformat/allformats.c b/libavformat/allformats.c
index 39ac3b8..2e7593e 100644
--- a/libavformat/allformats.c
+++ b/libavformat/allformats.c
@@ -187,6 +187,7 @@ void av_register_all(void)
     REGISTER_DEMUXER  (TXD, txd);
     REGISTER_DEMUXER  (VC1, vc1);
     REGISTER_MUXDEMUX (VC1T, vc1t);
+    REGISTER_DEMUXER  (VIVO, vivo);
     REGISTER_DEMUXER  (VMD, vmd);
     REGISTER_MUXDEMUX (VOC, voc);
     REGISTER_DEMUXER  (VQF, vqf);
diff --git a/libavformat/vivodec.c b/libavformat/vivodec.c
new file mode 100644
index 0000000..24bafb8
--- /dev/null
+++ b/libavformat/vivodec.c
@@ -0,0 +1,346 @@
+/*
+ * Vivo stream demuxer
+ * Copyright (c) 2009 Daniel Verkamp <daniel at drv.nu>
+ *
+ * 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 libavformat/vivodec.c
+ * @brief Vivo stream demuxer
+ * @author Daniel Verkamp <daniel at drv.nu>
+ * @sa http://wiki.multimedia.cx/index.php?title=Vivo
+ */
+
+#include "libavutil/intreadwrite.h"
+#include "avformat.h"
+
+typedef struct VivoPacket {
+    unsigned type;
+    unsigned sequence;
+    unsigned len;
+} VivoPacket;
+
+typedef struct VivoContext {
+    VivoPacket pkt;
+    double fps;
+    AVRational timebase;
+    int64_t video_pts;
+    int64_t audio_pts;
+    int nominal_bitrate; // audio bitrate
+} VivoContext;
+
+
+static int vivo_probe(AVProbeData *p)
+{
+    const unsigned char *buf = p->buf;
+    unsigned c, len = 0;
+
+    // stream must start with packet of type 0 and sequence number 0
+    if (*buf++ != 0)
+        return 0;
+
+    // read at most 2 bytes of coded length
+    c = *buf++;
+    len = c & 0x7F;
+    if (c & 0x80) {
+        c = *buf++;
+        len = (len << 7) | (c & 0x7F);
+    }
+    if (c & 0x80 || len > 1024 || len < 21)
+        return 0;
+
+    if (memcmp(buf, "\r\nVersion:Vivo/", 15))
+        return 0;
+    buf += 15;
+
+    if (*buf != '0' && *buf != '1' && *buf != '2')
+        return 0;
+
+    return AVPROBE_SCORE_MAX;
+}
+
+
+static int vivo_get_packet_header(AVFormatContext *s, VivoPacket *vpkt)
+{
+    ByteIOContext *pb = s->pb;
+    unsigned c, get_len = 0;
+
+    // TODO: check for eof
+
+    c = get_byte(pb);
+
+    if (c == 0x82) {
+        get_len = 1;
+        c = get_byte(pb);
+    }
+
+    vpkt->type     = c >> 4;
+    vpkt->sequence = c & 0xF;
+
+    switch (vpkt->type) {
+    case 0:   get_len =   1; break;
+    case 1: vpkt->len = 128; break;
+    case 2:   get_len =   1; break;
+    case 3: vpkt->len =  40; break;
+    case 4: vpkt->len =  24; break;
+    default:
+        av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vpkt->type);
+        return -1;
+    }
+
+    if (get_len) {
+        c = get_byte(pb);
+        vpkt->len = c & 0x7F;
+        if (c & 0x80) {
+            c = get_byte(pb);
+            vpkt->len = (vpkt->len << 7) | (c & 0x7F);
+
+            if (c & 0x80) {
+                av_log(s, AV_LOG_ERROR, "coded length is more than two bytes\n");
+                return -1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+
+static int vivo_read_text_header(AVFormatContext *s, VivoPacket *vpkt,
+                                 AVStream *vst, AVStream *ast)
+{
+    VivoContext *vivo = s->priv_data;
+    ByteIOContext *pb = s->pb;
+    unsigned char text[vpkt->len + 1];
+    unsigned char *line, *line_end, *key, *value;
+    char *end_value;
+    long value_int, value_used;
+
+    get_buffer(pb, text, vpkt->len);
+    text[vpkt->len] = 0;
+
+    line = text;
+    while (*line) {
+        line_end = strstr(line, "\r\n");
+        if (!line_end)
+            break;
+
+        *line_end = '\0';
+        key = line;
+        line = line_end + 2; // skip \r\n
+
+        if (line_end == key) // skip blank lines
+            continue;
+
+        value = strchr(key, ':');
+        if (!value) {
+            av_log(s, AV_LOG_WARNING, "missing colon in key:value pair '%s'\n",
+                   value);
+            continue;
+        }
+
+        *value++ = 0;
+
+        av_log(s, AV_LOG_INFO, "header: '%s' = '%s'\n", key, value);
+
+        value_int = strtol(value, &end_value, 10);
+        value_used = 0;
+        if (*end_value == 0) { // valid integer
+            av_log(s, AV_LOG_INFO, "got a valid integer (%d)\n", value_int);
+            value_used = 1;
+            if (!strcmp(key, "Width")) {
+                vst->codec->width = value_int;
+            } else if (!strcmp(key, "Height")) {
+                vst->codec->height = value_int;
+            } else if (!strcmp(key, "TimeUnitNumerator")) {
+                vivo->timebase.num = value_int;
+            } else if (!strcmp(key, "TimeUnitDenominator")) {
+                vivo->timebase.den = value_int;
+            } else if (!strcmp(key, "SamplingFrequency")) {
+                ast->codec->sample_rate = value_int;
+                if (value_int == 16000) {
+                    //ast->codec->codec_id = CODEC_ID_SIREN;
+                } else if (value_int == 8000) {
+                    //ast->codec->codec_id = CODEC_ID_G723;
+                }
+            } else if (!strcmp(key, "NominalBitrate")) {
+                vivo->nominal_bitrate = value_int;
+            } else {
+                value_used = 0;
+            }
+        }
+
+        if (!strcmp(key, "FPS")) {
+            vivo->fps = atof(value);
+        }
+
+        if (!value_used)
+            av_metadata_set(&s->metadata, key, value);
+    }
+
+    return 0;
+}
+
+
+static int vivo_read_header(AVFormatContext *s, AVFormatParameters *ap)
+{
+    VivoContext *vivo = s->priv_data;
+    AVStream *vst;
+    AVStream *ast;
+    int ret;
+
+    vst = av_new_stream(s, 0);
+    if (!vst)
+        return AVERROR(ENOMEM);
+    vst->codec->codec_type = CODEC_TYPE_VIDEO;
+    vst->codec->codec_id   = CODEC_ID_H263;
+
+    ast = av_new_stream(s, 1);
+    if (!ast)
+        return AVERROR(ENOMEM);
+    ast->codec->codec_type = CODEC_TYPE_AUDIO;
+    ast->codec->channels = 1;
+    ast->codec->bits_per_coded_sample = 8;
+    ast->codec->sample_fmt = SAMPLE_FMT_U8;
+    ast->codec->sample_rate = 0;
+    vivo->nominal_bitrate = 0;
+
+    while (1) {
+        ret = vivo_get_packet_header(s, &vivo->pkt);
+        if (ret < 0)
+            return -1;
+
+        // done reading all text header packets?
+        if (vivo->pkt.sequence || vivo->pkt.type)
+            break;
+
+        ret = vivo_read_text_header(s, &vivo->pkt, vst, ast);
+        if (ret < 0)
+            return -1;
+    }
+
+    if (ast->codec->sample_rate == 0) {
+        ast->codec->sample_rate = 8000;
+        //ast->codec->codec_id = CODEC_ID_G723;
+    }
+
+    if (vivo->nominal_bitrate == 0) {
+        vivo->nominal_bitrate = 800;
+    }
+
+    if (vivo->fps) {
+        vivo->timebase = av_d2q(1.0 / vivo->fps, INT_MAX);
+    }
+
+    av_set_pts_info(vst, 64, vivo->timebase.num, vivo->timebase.den);
+    av_set_pts_info(ast, 64, 1, ast->codec->sample_rate * vivo->nominal_bitrate >> 3);
+
+    return 0;
+}
+
+
+static int vivo_read_packet(AVFormatContext *s, AVPacket *pkt)
+{
+    VivoContext *vivo = s->priv_data;
+    ByteIOContext *pb = s->pb;
+    int ret, stream_index;
+    unsigned old_sequence = vivo->pkt.sequence, old_type = vivo->pkt.type;
+    unsigned len = 0;
+    uint8_t *buf = NULL, *buf_new;
+
+restart:
+
+    if (url_feof(pb))
+        return AVERROR_EOF;
+
+    switch (vivo->pkt.type) {
+    case 0:
+        url_fskip(pb, vivo->pkt.len);
+        ret = vivo_get_packet_header(s, &vivo->pkt);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "couldn't get packet header\n");
+            return AVERROR(EIO);
+        }
+        goto restart;
+    case 1: case 2: // video
+        stream_index = 0;
+        break;
+    case 3: case 4: // audio
+        stream_index = 1;
+        break;
+    default:
+        av_log(s, AV_LOG_ERROR, "unknown packet type %d\n", vivo->pkt.type);
+        return -1;
+    }
+
+    do {
+        // read data into buffer
+        buf_new = av_realloc(buf, len + vivo->pkt.len);
+        if (!buf_new) {
+            av_log(s, AV_LOG_ERROR, "couldn't allocate buffer\n");
+            av_free(buf);
+            return AVERROR(ENOMEM);
+        }
+        buf = buf_new;
+        get_buffer(pb, buf + len, vivo->pkt.len);
+        len += vivo->pkt.len;
+
+        if (url_feof(pb)) {
+            av_free(buf);
+            return AVERROR_EOF;
+        }
+
+        // get next packet header
+        ret = vivo_get_packet_header(s, &vivo->pkt);
+        if (ret < 0) {
+            av_log(s, AV_LOG_ERROR, "couldn't get packet header\n");
+            av_free(buf);
+            return AVERROR(EIO);
+        }
+    } while (vivo->pkt.sequence == old_sequence &&
+          (((vivo->pkt.type - 1) >> 1) == ((old_type - 1) >> 1)));
+
+    // copy data into packet
+    if (av_new_packet(pkt, len) < 0) {
+        av_log(s, AV_LOG_ERROR, "couldn't create packet\n");
+        av_free(buf);
+        return AVERROR(ENOMEM);
+    }
+
+    pkt->stream_index = stream_index;
+    if (stream_index == 0) {
+        pkt->pts = vivo->video_pts++;
+    } else {
+        pkt->pts = vivo->audio_pts;
+        vivo->audio_pts += len;
+    }
+
+    memcpy(pkt->data, buf, len);
+    av_free(buf);
+
+    return len;
+}
+
+AVInputFormat vivo_demuxer = {
+    "vivo",
+    NULL_IF_CONFIG_SMALL("Vivo stream"),
+    sizeof(VivoContext),
+    vivo_probe,
+    vivo_read_header,
+    vivo_read_packet,
+};
-- 
1.6.2



More information about the ffmpeg-devel mailing list