From 6fb40779cd3457a819e20d6db91a142c47cad3c2 Mon Sep 17 00:00:00 2001 From: Peter Ross Date: Mon, 17 Dec 2012 20:37:54 +1100 Subject: Silicon Graphics Movie (.mv) demuxer Signed-off-by: Peter Ross --- Changelog | 1 + doc/general.texi | 1 + libavformat/Makefile | 1 + libavformat/allformats.c | 1 + libavformat/mvdec.c | 424 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 428 insertions(+) create mode 100644 libavformat/mvdec.c diff --git a/Changelog b/Changelog index 5843675e39..0fd46c6038 100644 --- a/Changelog +++ b/Changelog @@ -44,6 +44,7 @@ version : - aselect filter - 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 22988b20fa..921bdcd160 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. +@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 eab94e6b1d..54e8864608 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 8e8a799c7d..6456e8c3ef 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 0000000000..15e1151163 --- /dev/null +++ b/libavformat/mvdec.c @@ -0,0 +1,424 @@ +/* + * 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/eval.h" +#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; + +#define AUDIO_FORMAT_SIGNED 401 + +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 char * var_read_string(AVIOContext *pb, int size) +{ + char *str = av_malloc(size + 1); + int n; + if (!str) + return NULL; + n = avio_get_str(pb, size, str, size + 1); + if (n < size) + avio_skip(pb, size - n); + return str; +} + +static int var_read_int(AVIOContext *pb, int size) +{ + int v; + char * s = var_read_string(pb, size); + if (!s || sscanf(s, "%d", &v) != 1) + v = 0; + av_free(s); + return v; +} + +static AVRational var_read_float(AVIOContext *pb, int size) +{ + AVRational v; + char * s = var_read_string(pb, size); + if (!s) + return (AVRational){0, 0}; + v = av_d2q(av_strtod(s, NULL), INT_MAX); + av_free(s); + return v; +} + +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 %s\n", str); + } + 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; + int v; + avio_skip(pb, 22); + + /* allocate audio track first to prevent unnecessary seeking + (audio packet always precede video packet for a given frame) */ + ast = avformat_new_stream(avctx, NULL); + if (!ast) + return AVERROR(ENOMEM); + + 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); + v = avio_rb32(pb); + switch (v) { + 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 %i\n", v); + break; + } + vst->codec->codec_tag = 0; + vst->codec->width = avio_rb32(pb); + vst->codec->height = avio_rb32(pb); + avio_skip(pb, 12); + + 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; + v = avio_rb32(pb); + if (v == AUDIO_FORMAT_SIGNED) { + ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE; + } else { + av_log_ask_for_sample(avctx, "unknown audio compression (format %i)\n", v); + } + + avio_skip(pb, 12); + var_read_metadata(avctx, "title", 0x80); + var_read_metadata(avctx, "comment", 0x100); + avio_skip(pb, 0x80); + + 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); + 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); + + 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; + /* temporarily store compression value in codec_tag; format value in codec_id */ + read_table(avctx, ast, parse_audio_var); + if (ast->codec->codec_tag == 100 && ast->codec->codec_id == AUDIO_FORMAT_SIGNED && 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 %i (format %i, width %i)\n", + ast->codec->codec_tag, ast->codec->codec_id, ast->codec->bits_per_coded_sample); + ast->codec->codec_id = AV_CODEC_ID_NONE; + } + ast->codec->codec_tag = 0; + } + + 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 %i\n", version); + 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 frame = mv->frame[mv->stream_index]; + int ret; + uint64_t pos; + + if (frame < st->nb_frames) { + index = &st->index_entries[frame]; + pos = avio_tell(pb); + if (index->pos > pos) + avio_skip(pb, index->pos - pos); + else if (index->pos < pos) { + if (!pb->seekable) + return AVERROR(EIO); + ret = avio_seek(pb, index->pos, SEEK_SET); + if (ret < 0) + return ret; + } + 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); + + if (!avctx->pb->seekable) + return AVERROR(EIO); + + 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, +}; -- cgit v1.2.3