[FFmpeg-devel] [PATCH 3/4] Silicon Graphics Movie (.mv) demuxer

Peter Ross pross at xvid.org
Wed Dec 12 13:06:08 CET 2012


On Tue, Dec 11, 2012 at 11:32:06AM +0000, Paul B Mahol wrote:

Thanks for taking the time to review this.

> On 12/11/12, Peter Ross <pross at xvid.org> wrote:
> > Signed-off-by: Peter Ross <pross at xvid.org>
> > ---
> >  Changelog                |   1 +
> >  doc/general.texi         |   1 +
> >  libavformat/Makefile     |   1 +
> >  libavformat/allformats.c |   1 +
> >  libavformat/mvdec.c      | 410
> > +++++++++++++++++++++++++++++++++++++++++++++++
> >  5 files changed, 414 insertions(+)
> >  create mode 100644 libavformat/mvdec.c
> >
> > diff --git a/Changelog b/Changelog
> > index 93f34e3..f2fd229 100644
> > --- a/Changelog
> > +++ b/Changelog
> > @@ -42,6 +42,7 @@ version <next>:
> >  - JSON captions for TED talks decoding support
> >  - SGI RLE 8-bit decoder
> >  - Silicon Graphics Motion Video Compressor 1 & 2 decoder
> > +- Silicon Graphics Movie demuxer
> >
> >
> >  version 1.0:
> > diff --git a/doc/general.texi b/doc/general.texi
> > index 22988b2..921bdcd 100644
> > --- a/doc/general.texi
> > +++ b/doc/general.texi
> > @@ -359,6 +359,7 @@ library:
> >  @item SDP                       @tab   @tab X
> >  @item Sega FILM/CPK             @tab   @tab X
> >      @tab Used in many Sega Saturn console games.
> > + at item Silicon Graphics Movie    @tab   @tab X
> >  @item Sierra SOL                @tab   @tab X
> >      @tab .sol files used in Sierra Online games.
> >  @item Sierra VMD                @tab   @tab X
> > diff --git a/libavformat/Makefile b/libavformat/Makefile
> > index 87fdc11..b187697 100644
> > --- a/libavformat/Makefile
> > +++ b/libavformat/Makefile
> > @@ -222,6 +222,7 @@ OBJS-$(CONFIG_MPJPEG_MUXER)              += mpjpeg.o
> >  OBJS-$(CONFIG_MSNWC_TCP_DEMUXER)         += msnwc_tcp.o
> >  OBJS-$(CONFIG_MTV_DEMUXER)               += mtv.o
> >  OBJS-$(CONFIG_MVI_DEMUXER)               += mvi.o
> > +OBJS-$(CONFIG_MV_DEMUXER)                += mvdec.o
> >  OBJS-$(CONFIG_MXF_DEMUXER)               += mxfdec.o mxf.o
> >  OBJS-$(CONFIG_MXF_MUXER)                 += mxfenc.o mxf.o
> > audiointerleave.o
> >  OBJS-$(CONFIG_MXG_DEMUXER)               += mxg.o
> > diff --git a/libavformat/allformats.c b/libavformat/allformats.c
> > index 8e8a799..6456e8c 100644
> > --- a/libavformat/allformats.c
> > +++ b/libavformat/allformats.c
> > @@ -172,6 +172,7 @@ void av_register_all(void)
> >      REGISTER_MUXER    (MPJPEG, mpjpeg);
> >      REGISTER_DEMUXER  (MSNWC_TCP, msnwc_tcp);
> >      REGISTER_DEMUXER  (MTV, mtv);
> > +    REGISTER_DEMUXER  (MV, mv);
> >      REGISTER_DEMUXER  (MVI, mvi);
> >      REGISTER_MUXDEMUX (MXF, mxf);
> >      REGISTER_MUXER    (MXF_D10, mxf_d10);
> > diff --git a/libavformat/mvdec.c b/libavformat/mvdec.c
> > new file mode 100644
> > index 0000000..3f7816d
> > --- /dev/null
> > +++ b/libavformat/mvdec.c
> > @@ -0,0 +1,410 @@
> > +/*
> > + * Silicon Graphics Movie demuxer
> > + * Copyright (c) 2012 Peter Ross
> > + *
> > + * 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
> > + * Silicon Graphics Movie demuxer
> > + */
> > +
> > +#include "libavutil/intreadwrite.h"
> > +#include "libavutil/rational.h"
> > +#include "avformat.h"
> > +#include "internal.h"
> > +
> > +typedef struct {
> > +    int nb_video_tracks;
> > +    int nb_audio_tracks;
> > +
> > +    int eof_count;        /**< number of streams that have finished */
> > +    int stream_index;     /**< current stream index */
> > +    int frame[2];         /**< frame nb for current stream */
> > +} MvContext;
> > +
> > +static int mv_probe(AVProbeData *p)
> > +{
> > +    if (AV_RB32(p->buf) == MKBETAG('M','O','V','I') && AV_RB16(p->buf + 4)
> > < 3)
> > +        return AVPROBE_SCORE_MAX;
> > +    return 0;
> > +}
> > +
> > +static int var_read_int(AVIOContext *pb, int size)
> > +{
> > +    int value = 0;
> > +    while (size--) {
> > +        int c = avio_r8(pb);
> > +        if (c < '0' || c > '9')
> > +            break;
> > +        value = value * 10 + c - '0';
> > +    }
> > +    avio_skip(pb, size);
> > +    return value;
> > +}
> 
> isn't there already something like lavu/parseutils and eval?

yes, sscanf. this was done to avoid "malloc, get_str, sscanf, free" sequence

> > +
> > +static AVRational var_read_float(AVIOContext *pb, int size)
> > +{
> > +    AVRational v = { 0, 1 };
> > +    int fractional = 0;
> > +    while (size--) {
> > +        int c = avio_r8(pb);
> > +        if (c == '.') {
> > +            if (fractional)
> > +                break;
> > +            fractional = 1;
> > +            continue;
> > +        }
> > +        if (c < '0' || c > '9')
> > +            break;
> > +        v.num = v.num * 10 + c - '0';
> > +        if (fractional)
> > +            v.den *= 10;
> > +    }
> > +    avio_skip(pb, size);
> > +    return v;
> > +}
> 
> ditto

there is av_strtod. But is it 'okay' to use doubles here?
(Ideally need an av_str_to_rational function).

> > +
> > +static char * var_read_string(AVIOContext *pb, int size)
> > +{
> > +    char *str = av_malloc(size + 1);
> > +    if (!str)
> > +        return NULL;
> > +    avio_read(pb, str, size);
> > +    str[size] = 0;
> > +    return str;
> > +}
> 
> avio_get_str() + avio_skip() (if strings have extra padding)
> > +
> > +static void var_read_metadata(AVFormatContext *avctx, const char *tag, int
> > size)
> > +{
> > +    char *value = var_read_string(avctx->pb, size);
> > +    if (value)
> > +        av_dict_set(&avctx->metadata, tag, value,
> > AV_DICT_DONT_STRDUP_VAL);
> > +}
> > +
> > +/**
> > + * Parse global variable
> > + * @return < 0 if unknown
> > + */
> > +static int parse_global_var(AVFormatContext *avctx, AVStream *st, const
> > char *name, int size)
> > +{
> > +    MvContext *mv = avctx->priv_data;
> > +    AVIOContext *pb = avctx->pb;
> > +    if (!strcmp(name, "__NUM_I_TRACKS")) {
> > +        mv->nb_video_tracks = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "__NUM_A_TRACKS")) {
> > +        mv->nb_audio_tracks = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "COMMENT") || !strcmp(name, "TITLE")) {
> > +        var_read_metadata(avctx, name, size);
> > +    } else if (!strcmp(name, "LOOP_MODE") || !strcmp(name, "NUM_LOOPS") ||
> > !strcmp(name, "OPTIMIZED")) {
> > +        avio_skip(pb, size); // ignore
> > +    } else
> > +        return -1;
> > +
> > +    return 0;
> > +}
> > +
> > +/**
> > + * Parse audio variable
> > + * @return < 0 if unknown
> > + */
> > +static int parse_audio_var(AVFormatContext *avctx, AVStream *st, const char
> > *name, int size)
> > +{
> > +    AVIOContext *pb = avctx->pb;
> > +    if (!strcmp(name, "__DIR_COUNT")) {
> > +        st->nb_frames = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "AUDIO_FORMAT")) {
> > +        st->codec->codec_id = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "COMPRESSION")) {
> > +        st->codec->codec_tag = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "DEFAULT_VOL")) {
> > +        var_read_metadata(avctx, name, size);
> > +    } else if (!strcmp(name, "NUM_CHANNELS")) {
> > +        st->codec->channels = var_read_int(pb, size);
> > +        st->codec->channel_layout = (st->codec->channels == 1) ?
> > AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
> > +    } else if (!strcmp(name, "SAMPLE_RATE")) {
> > +        st->codec->sample_rate = var_read_int(pb, size);
> > +        avpriv_set_pts_info(st, 33, 1, st->codec->sample_rate);
> > +    } else if (!strcmp(name, "SAMPLE_WIDTH")) {
> > +        st->codec->bits_per_coded_sample = var_read_int(pb, size) * 8;
> > +    } else
> > +        return -1;
> > +    return 0;
> > +}
> > +
> > +/**
> > + * Parse video variable
> > + * @return < 0 if unknown
> > + */
> > +static int parse_video_var(AVFormatContext *avctx, AVStream *st, const char
> > *name, int size)
> > +{
> > +    AVIOContext *pb = avctx->pb;
> > +    if (!strcmp(name, "__DIR_COUNT")) {
> > +        st->nb_frames = st->duration = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "COMPRESSION")) {
> > +        char * str = var_read_string(pb, size);
> > +        if (!strcmp(str, "1")) {
> > +            st->codec->codec_id = AV_CODEC_ID_MVC1;
> > +        } else if (!strcmp(str, "2")) {
> > +            st->codec->pix_fmt  = AV_PIX_FMT_ABGR;
> > +            st->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
> > +        } else if (!strcmp(str, "3")) {
> > +            st->codec->codec_id = AV_CODEC_ID_SGIRLE;
> > +        } else if (!strcmp(str, "10")) {
> > +            st->codec->codec_id = AV_CODEC_ID_MJPEG;
> > +        } else if (!strcmp(str, "MVC2")) {
> > +            st->codec->codec_id = AV_CODEC_ID_MVC2;
> > +        } else {
> > +            av_log_ask_for_sample(avctx, "unknown video compression\n");
> > +        }
> > +        av_free(str);
> > +    } else if (!strcmp(name, "FPS")) {
> > +        st->time_base = av_inv_q(var_read_float(pb, size));
> > +    } else if (!strcmp(name, "HEIGHT")) {
> > +        st->codec->height = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "PIXEL_ASPECT")) {
> > +        st->sample_aspect_ratio = var_read_float(pb, size);
> > +        av_reduce(&st->sample_aspect_ratio.num,
> > &st->sample_aspect_ratio.den,
> > +                  st->sample_aspect_ratio.num, st->sample_aspect_ratio.den,
> > INT_MAX);
> > +    } else if (!strcmp(name, "WIDTH")) {
> > +        st->codec->width = var_read_int(pb, size);
> > +    } else if (!strcmp(name, "ORIENTATION")) {
> > +        if (var_read_int(pb, size) == 1101) {
> > +            st->codec->extradata       = av_strdup("BottomUp");
> > +            st->codec->extradata_size  = 9;
> > +        }
> > +    } else if (!strcmp(name, "Q_SPATIAL") || !strcmp(name, "Q_TEMPORAL"))
> > {
> > +        var_read_metadata(avctx, name, size);
> > +    } else if (!strcmp(name, "INTERLACING") || !strcmp(name, "PACKING")) {
> > +        avio_skip(pb, size); // ignore
> > +    } else
> > +        return -1;
> > +    return 0;
> > +}
> > +
> > +static void read_table(AVFormatContext *avctx, AVStream *st, int
> > (*parse)(AVFormatContext *avctx, AVStream *st, const char *name, int size))
> > +{
> > +    int count, i;
> > +    AVIOContext *pb = avctx->pb;
> > +    avio_skip(pb, 4);
> > +    count = avio_rb32(pb);
> > +    avio_skip(pb, 4);
> > +    for (i = 0; i < count; i++) {
> > +        char name[17];
> > +        int size;
> > +        avio_read(pb, name, 16);
> > +        name[sizeof(name) - 1] = 0;
> > +        size = avio_rb32(pb);
> > +        if (parse(avctx, st, name, size) < 0) {
> > +            av_log_ask_for_sample(avctx, "unknown variable %s\n", name);
> > +            avio_skip(pb, size);
> > +        }
> > +    }
> > +}
> > +
> > +static void read_index(AVIOContext *pb, AVStream *st)
> > +{
> > +    uint64_t timestamp = 0;
> > +    int i;
> > +    for (i = 0; i < st->nb_frames; i++) {
> > +        uint32_t pos  = avio_rb32(pb);
> > +        uint32_t size = avio_rb32(pb);
> > +        avio_skip(pb, 8);
> > +        av_add_index_entry(st, pos, timestamp, size, 0, AVINDEX_KEYFRAME);
> > +        if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
> > +            timestamp += size / (st->codec->channels * 2);
> > +        } else {
> > +            timestamp++;
> > +        }
> > +    }
> > +}
> > +
> > +static int mv_read_header(AVFormatContext *avctx)
> > +{
> > +    MvContext *mv = avctx->priv_data;
> > +    AVIOContext *pb = avctx->pb;
> > +    AVStream *ast, *vst;
> > +    int version, i;
> > +
> > +    avio_skip(pb, 4);
> > +
> > +    version = avio_rb16(pb);
> > +    if (version == 2) {
> > +        uint64_t timestamp;
> > +        avio_skip(pb, 22);
> > +        vst = avformat_new_stream(avctx, NULL);
> > +        if (!vst)
> > +            return AVERROR(ENOMEM);
> > +        vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
> > +        vst->time_base = (AVRational){1, 15};
> > +
> > +        vst->nb_frames = avio_rb32(pb);
> > +        vst->codec->codec_tag = avio_rb32(pb);
> 
> not really codec->codec_tag to be exported this way.
> > +        switch (vst->codec->codec_tag) {
> > +        case 1:
> > +            vst->codec->codec_id = AV_CODEC_ID_MVC1;
> > +            break;
> > +        case 2:
> > +            vst->codec->pix_fmt  = AV_PIX_FMT_ARGB;
> > +            vst->codec->codec_id = AV_CODEC_ID_RAWVIDEO;
> > +            break;
> > +        default:
> > +            av_log_ask_for_sample(avctx, "unknown video compression\n");
> > +            break;
> > +        }
> > +        vst->codec->width  = avio_rb32(pb);
> > +        vst->codec->height = avio_rb32(pb);
> > +        avio_skip(pb, 12);
> > +
> > +        ast = avformat_new_stream(avctx, NULL);
> > +        if (!ast)
> > +            return AVERROR(ENOMEM);
> > +        ast->codec->codec_type     = AVMEDIA_TYPE_AUDIO;
> > +        ast->nb_frames             = vst->nb_frames;
> > +        ast->codec->sample_rate    = avio_rb32(pb);
> > +        avpriv_set_pts_info(ast, 33, 1, ast->codec->sample_rate);
> > +        ast->codec->channels       = avio_rb32(pb);
> > +        ast->codec->channel_layout = (ast->codec->channels == 1) ?
> > AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO;
> > +        if (avio_rb32(pb) == 401) {
> > +            ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE;
> > +        } else {
> > +            av_log_ask_for_sample(avctx, "unknown audio compression\n");
> > +        }
> > +
> > +        avio_skip(pb, 12);
> 
> what is that? padding or something that tells where other data is located?

Those bytes are unknown. They are likely audio parameters for bits per samples,
and format. Don't have enough samples to determine what-is-what, nor do I have
ready access to a working box to perform sensitivity tests on libdmedia.so.

> > +        var_read_metadata(avctx, "title", 0x80);
> > +        var_read_metadata(avctx, "comment", 0x100);
> > +        avio_skip(pb, 0x80);
> 
> ditto

Those 0x80 bytes are filled with (what looks like) junk in my samples, so
not clear if it forms part of the comment, or something else.

> > +
> > +        timestamp = 0;
> > +        for (i = 0; i < vst->nb_frames; i++) {
> > +            uint32_t pos   = avio_rb32(pb);
> > +            uint32_t asize = avio_rb32(pb);
> > +            uint32_t vsize = avio_rb32(pb);
> > +            avio_skip(pb, 8);
> 
> padding or?

No, unknown.

> > +            av_add_index_entry(ast, pos,         timestamp, asize, 0,
> > AVINDEX_KEYFRAME);
> > +            av_add_index_entry(vst, pos + asize, i,         vsize, 0,
> > AVINDEX_KEYFRAME);
> > +            timestamp += asize / (ast->codec->channels * 2);
> > +        }
> > +    } else if (!version && avio_rb16(pb) == 3) {
> > +        avio_skip(pb, 4);
> padding or?

Is duplicate of the image dimensions (width, height).

> > +
> > +        read_table(avctx, NULL, parse_global_var);
> > +
> > +        if (mv->nb_audio_tracks > 1) {
> > +            av_log_ask_for_sample(avctx, "multiple audio streams\n");
> > +            return AVERROR_PATCHWELCOME;
> > +        } else if (mv->nb_audio_tracks) {
> > +            ast = avformat_new_stream(avctx, NULL);
> > +            if (!ast)
> > +                return AVERROR(ENOMEM);
> > +            ast->codec->codec_type = AVMEDIA_TYPE_AUDIO;
> > +            read_table(avctx, ast, parse_audio_var);
> > +            if (ast->codec->codec_tag == 100 && ast->codec->codec_id == 401
> > && ast->codec->bits_per_coded_sample == 16) {
> > +                ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE;
> > +            } else {
> > +                av_log_ask_for_sample(avctx, "unknown audio
> > compression\n");
> > +            }
> > +        }
> > +
> > +        if (mv->nb_video_tracks > 1) {
> > +            av_log_ask_for_sample(avctx, "multiple video streams\n");
> > +            return AVERROR_PATCHWELCOME;
> > +        } else if (mv->nb_video_tracks) {
> > +            vst = avformat_new_stream(avctx, NULL);
> > +            if (!vst)
> > +                return AVERROR(ENOMEM);
> > +            vst->codec->codec_type = AVMEDIA_TYPE_VIDEO;
> > +            read_table(avctx, vst, parse_video_var);
> > +        }
> > +
> > +        if (mv->nb_audio_tracks)
> > +            read_index(pb, ast);
> > +
> > +        if (mv->nb_video_tracks)
> > +            read_index(pb, vst);
> > +    } else {
> > +        av_log_ask_for_sample(avctx, "unknown version\n");
> 
> Could you please for all unknown cases actually print unknown value.
> > +        return AVERROR_PATCHWELCOME;
> > +    }
> > +
> > +    return 0;
> > +}
> > +
> > +static int mv_read_packet(AVFormatContext *avctx, AVPacket *pkt)
> > +{
> > +    MvContext *mv = avctx->priv_data;
> > +    AVIOContext *pb = avctx->pb;
> > +    AVStream *st = avctx->streams[mv->stream_index];
> > +    const AVIndexEntry *index;
> > +    int ret;
> > +    int frame = mv->frame[mv->stream_index];
> > +
> > +    if (frame  < st->nb_frames) {
> > +        index = &st->index_entries[frame];
> > +        avio_seek(pb, index->pos, SEEK_SET);
> 
> ??? is this really required? Demuxing via pipe protocol (or any that
> do not support seeking)
> still works?

Nah, laziness.

While the audio/video packets are not guaranteed to be stored sequentially
in the file, most cases it is. I will make it work with not-seeking-capable
protocols.

> > +        ret = av_get_packet(pb, pkt, index->size);
> > +        if (ret < 0)
> > +            return ret;
> > +
> > +        pkt->stream_index = mv->stream_index;
> > +        pkt->pts = index->timestamp;
> > +        pkt->flags |= AV_PKT_FLAG_KEY;
> > +
> > +        mv->frame[mv->stream_index]++;
> > +        mv->eof_count = 0;
> > +    } else {
> > +        mv->eof_count++;
> > +        if (mv->eof_count >= avctx->nb_streams)
> > +            return AVERROR_EOF;
> > +    }
> > +
> > +    mv->stream_index++;
> > +    if (mv->stream_index >= avctx->nb_streams)
> > +        mv->stream_index = 0;
> > +
> > +    return 0;
> > +}
> > +
> > +static int mv_read_seek(AVFormatContext *avctx, int stream_index, int64_t
> > timestamp, int flags)
> > +{
> > +    MvContext *mv = avctx->priv_data;
> > +    AVStream *st = avctx->streams[stream_index];
> > +    int frame, i;
> > +
> > +    if ((flags & AVSEEK_FLAG_FRAME) || (flags & AVSEEK_FLAG_BYTE))
> > +        return AVERROR(ENOSYS);
> > +
> > +    frame = av_index_search_timestamp(st, timestamp, flags);
> > +    if (frame < 0)
> > +        return -1;
> > +
> > +    for (i = 0; i < avctx->nb_streams; i++)
> > +        mv->frame[i] = frame;
> > +    return 0;
> > +}
> > +
> > +AVInputFormat ff_mv_demuxer = {
> > +    .name           = "mv",
> > +    .long_name      = NULL_IF_CONFIG_SMALL("Silicon Graphics Movie"),
> > +    .priv_data_size = sizeof(MvContext),
> > +    .read_probe     = mv_probe,
> > +    .read_header    = mv_read_header,
> > +    .read_packet    = mv_read_packet,
> > +    .read_seek      = mv_read_seek,
> > +};
> > --
> > 1.8.0
> >
> > -- Peter
> > (A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)
> >
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel at ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
-- Peter
(A907 E02F A6E5 0CD2 34CD 20D2 6760 79C5 AC40 DD6B)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 198 bytes
Desc: Digital signature
URL: <http://ffmpeg.org/pipermail/ffmpeg-devel/attachments/20121212/01bd5fac/attachment.asc>


More information about the ffmpeg-devel mailing list