diff --git a/Changelog b/Changelog index 7d02d6ffca..183e8dc5a9 100644 --- a/Changelog +++ b/Changelog @@ -21,6 +21,7 @@ version : - asettb filter - Silicon Graphics RLE 8-bit video decoder - Silicon Graphics Motion Video Compressor 1 & 2 decoder +- Silicon Graphics Movie demuxer version 10: diff --git a/doc/general.texi b/doc/general.texi index 569bbe0584..2daa6ae928 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -383,6 +383,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 a7f03f973a..0ef6d83aba 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -201,6 +201,7 @@ OBJS-$(CONFIG_MPEGVIDEO_DEMUXER) += mpegvideodec.o rawdec.o OBJS-$(CONFIG_MPJPEG_MUXER) += mpjpeg.o OBJS-$(CONFIG_MSNWC_TCP_DEMUXER) += msnwc_tcp.o OBJS-$(CONFIG_MTV_DEMUXER) += mtv.o +OBJS-$(CONFIG_MV_DEMUXER) += mvdec.o OBJS-$(CONFIG_MVI_DEMUXER) += mvi.o OBJS-$(CONFIG_MXF_DEMUXER) += mxfdec.o mxf.o OBJS-$(CONFIG_MXF_MUXER) += mxfenc.o mxf.o audiointerleave.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index 4f3acf56a3..40962f8971 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -165,6 +165,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..e21ec06b74 --- /dev/null +++ b/libavformat/mvdec.c @@ -0,0 +1,468 @@ +/* + * Silicon Graphics Movie demuxer + * Copyright (c) 2012 Peter Ross + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; 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/channel_layout.h" +#include "libavutil/eval.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/rational.h" + +#include "avformat.h" +#include "internal.h" + +typedef struct MvContext { + 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 + + int acompression; ///< compression level for audio stream + int aformat; ///< audio format +} 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) +{ + int n; + char *str = av_malloc(size + 1); + 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) + return 0; + v = strtol(s, NULL, 10); + 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); +} + +static int set_channels(AVFormatContext *avctx, AVStream *st, int channels) +{ + if (channels <= 0) { + av_log(avctx, AV_LOG_ERROR, "Channel count %d invalid.\n", channels); + return AVERROR_INVALIDDATA; + } + st->codec->channels = channels; + st->codec->channel_layout = (st->codec->channels == 1) ? AV_CH_LAYOUT_MONO + : AV_CH_LAYOUT_STEREO; + return 0; +} + +/** + * 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 AVERROR_INVALIDDATA; + + return 0; +} + +/** + * Parse audio variable + * @return < 0 if unknown + */ +static int parse_audio_var(AVFormatContext *avctx, AVStream *st, + const char *name, int size) +{ + MvContext *mv = avctx->priv_data; + AVIOContext *pb = avctx->pb; + if (!strcmp(name, "__DIR_COUNT")) { + st->nb_frames = var_read_int(pb, size); + } else if (!strcmp(name, "AUDIO_FORMAT")) { + mv->aformat = var_read_int(pb, size); + } else if (!strcmp(name, "COMPRESSION")) { + mv->acompression = var_read_int(pb, size); + } else if (!strcmp(name, "DEFAULT_VOL")) { + var_read_metadata(avctx, name, size); + } else if (!strcmp(name, "NUM_CHANNELS")) { + return set_channels(avctx, st, var_read_int(pb, size)); + } 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 AVERROR_INVALIDDATA; + + 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 (!str) + return AVERROR_INVALIDDATA; + 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 { + avpriv_request_sample(avctx, "Video compression %s", str); + } + av_free(str); + } else if (!strcmp(name, "FPS")) { + AVRational fps = var_read_float(pb, size); + avpriv_set_pts_info(st, 64, fps.den, fps.num); + st->avg_frame_rate = fps; + } 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 AVERROR_INVALIDDATA; + + 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) { + avpriv_request_sample(avctx, "Variable %s", 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 = NULL, *vst = NULL; + 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); + avpriv_set_pts_info(vst, 64, 1, 15); + vst->codec->codec_type = AVMEDIA_TYPE_VIDEO; + vst->avg_frame_rate = av_inv_q(vst->time_base); + 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: + avpriv_request_sample(avctx, "Video compression %i", 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); + if (set_channels(avctx, ast, avio_rb32(pb)) < 0) + return AVERROR_INVALIDDATA; + + v = avio_rb32(pb); + if (v == AUDIO_FORMAT_SIGNED) { + ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE; + } else { + avpriv_request_sample(avctx, "Audio compression (format %i)", 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) { + avpriv_request_sample(avctx, "Multiple audio streams support"); + 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 (mv->acompression == 100 && + mv->aformat == AUDIO_FORMAT_SIGNED && + ast->codec->bits_per_coded_sample == 16) { + ast->codec->codec_id = AV_CODEC_ID_PCM_S16BE; + } else { + avpriv_request_sample(avctx, + "Audio compression %i (format %i, sr %i)", + mv->acompression, mv->aformat, + ast->codec->bits_per_coded_sample); + ast->codec->codec_id = AV_CODEC_ID_NONE; + } + if (ast->codec->channels <= 0) { + av_log(avctx, AV_LOG_ERROR, "No valid channel count found.\n"); + return AVERROR_INVALIDDATA; + } + } + + if (mv->nb_video_tracks > 1) { + avpriv_request_sample(avctx, "Multiple video streams support"); + 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 { + avpriv_request_sample(avctx, "Version %i", 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_index_entries) { + 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; + + // avoid returning 0 without a packet + return AVERROR(EAGAIN); + } + + 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 AVERROR_INVALIDDATA; + + 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, +}; diff --git a/libavformat/version.h b/libavformat/version.h index ba531205c5..b09d49c818 100644 --- a/libavformat/version.h +++ b/libavformat/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFORMAT_VERSION_MAJOR 55 -#define LIBAVFORMAT_VERSION_MINOR 15 +#define LIBAVFORMAT_VERSION_MINOR 16 #define LIBAVFORMAT_VERSION_MICRO 0 #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \