diff options
Diffstat (limited to 'libavformat/avienc.c')
-rw-r--r-- | libavformat/avienc.c | 369 |
1 files changed, 296 insertions, 73 deletions
diff --git a/libavformat/avienc.c b/libavformat/avienc.c index 9c2936af60..ca505f407a 100644 --- a/libavformat/avienc.c +++ b/libavformat/avienc.c @@ -2,29 +2,41 @@ * AVI muxer * Copyright (c) 2000 Fabrice Bellard * - * This file is part of Libav. + * This file is part of FFmpeg. * - * Libav is free software; you can redistribute it and/or + * 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. * - * Libav is distributed in the hope that it will be useful, + * 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 Libav; if not, write to the Free Software + * License along with FFmpeg; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +//#define DEBUG + #include "avformat.h" #include "internal.h" #include "avi.h" #include "avio_internal.h" #include "riff.h" +#include "mpegts.h" +#include "libavformat/avlanguage.h" +#include "libavutil/avstring.h" +#include "libavutil/internal.h" #include "libavutil/intreadwrite.h" #include "libavutil/dict.h" +#include "libavutil/avassert.h" +#include "libavutil/timestamp.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "libavcodec/raw.h" /* * TODO: @@ -39,27 +51,40 @@ typedef struct AVIIentry { typedef struct AVIIndex { int64_t indx_start; + int64_t audio_strm_offset; int entry; int ents_allocated; + int master_odml_riff_id_base; AVIIentry** cluster; } AVIIndex; typedef struct AVIContext { + const AVClass *class; int64_t riff_start, movi_list, odml_list; int64_t frames_hdr_all; int riff_id; + int write_channel_mask; } AVIContext; typedef struct AVIStream { int64_t frames_hdr_strm; - int audio_strm_length; + int64_t audio_strm_length; int packet_count; int entry; + int max_size; + int sample_requested; + + int64_t pal_offset; + int hdr_pal_done; + + int64_t last_dts; AVIIndex indexes; } AVIStream; -static inline AVIIentry *avi_get_ientry(AVIIndex *idx, int ent_id) +static int avi_write_packet_internal(AVFormatContext *s, AVPacket *pkt); + +static inline AVIIentry *avi_get_ientry(const AVIIndex *idx, int ent_id) { int cl = ent_id / AVI_INDEX_CLUSTER_SIZE; int id = ent_id % AVI_INDEX_CLUSTER_SIZE; @@ -76,6 +101,7 @@ static int64_t avi_start_new_riff(AVFormatContext *s, AVIOContext *pb, avi->riff_id++; for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; + avist->indexes.audio_strm_offset = avist->audio_strm_length; avist->indexes.entry = 0; } @@ -117,7 +143,7 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) for (n = 0; n < s->nb_streams; n++) { AVIStream *avist = s->streams[n]->priv_data; - assert(avist->frames_hdr_strm); + av_assert0(avist->frames_hdr_strm); stream = s->streams[n]->codec; avio_seek(pb, avist->frames_hdr_strm, SEEK_SET); ff_parse_specific_params(s->streams[n], &au_byterate, &au_ssize, &au_scale); @@ -129,7 +155,7 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) nb_frames = FFMAX(nb_frames, avist->packet_count); } if (riff_id == 1) { - assert(avi->frames_hdr_all); + av_assert0(avi->frames_hdr_all); avio_seek(pb, avi->frames_hdr_all, SEEK_SET); avio_wl32(pb, nb_frames); } @@ -138,6 +164,33 @@ static int avi_write_counters(AVFormatContext *s, int riff_id) return 0; } +static void write_odml_master(AVFormatContext *s, int stream_index) +{ + AVIOContext *pb = s->pb; + AVStream *st = s->streams[stream_index]; + AVCodecContext *enc = st->codec; + AVIStream *avist = st->priv_data; + unsigned char tag[5]; + int j; + + /* Starting to lay out AVI OpenDML master index. + * We want to make it JUNK entry for now, since we'd + * like to get away without making AVI an OpenDML one + * for compatibility reasons. */ + avist->indexes.indx_start = ff_start_tag(pb, "JUNK"); + avio_wl16(pb, 4); /* wLongsPerEntry */ + avio_w8(pb, 0); /* bIndexSubType (0 == frame index) */ + avio_w8(pb, 0); /* bIndexType (0 == AVI_INDEX_OF_INDEXES) */ + avio_wl32(pb, 0); /* nEntriesInUse (will fill out later on) */ + ffio_wfourcc(pb, avi_stream2fourcc(tag, stream_index, enc->codec_type)); + /* dwChunkId */ + avio_wl64(pb, 0); /* dwReserved[3] */ + avio_wl32(pb, 0); /* Must be 0. */ + for (j = 0; j < AVI_MASTER_INDEX_SIZE * 2; j++) + avio_wl64(pb, 0); + ff_end_tag(pb, avist->indexes.indx_start); +} + static int avi_write_header(AVFormatContext *s) { AVIContext *avi = s->priv_data; @@ -147,11 +200,12 @@ static int avi_write_header(AVFormatContext *s) AVStream *video_st = NULL; int64_t list1, list2, strh, strf; AVDictionaryEntry *t = NULL; + int padding; if (s->nb_streams > AVI_MAX_STREAM_COUNT) { av_log(s, AV_LOG_ERROR, "AVI does not support >%d streams\n", AVI_MAX_STREAM_COUNT); - return -1; + return AVERROR(EINVAL); } for (n = 0; n < s->nb_streams; n++) { @@ -254,9 +308,18 @@ static int avi_write_header(AVFormatContext *s) ff_parse_specific_params(st, &au_byterate, &au_ssize, &au_scale); + if ( enc->codec_type == AVMEDIA_TYPE_VIDEO + && enc->codec_id != AV_CODEC_ID_XSUB + && au_byterate > 1000LL*au_scale) { + au_byterate = 600; + au_scale = 1; + } + avpriv_set_pts_info(st, 64, au_scale, au_byterate); + if (enc->codec_id == AV_CODEC_ID_XSUB) + au_scale = au_byterate = 0; + avio_wl32(pb, au_scale); /* scale */ avio_wl32(pb, au_byterate); /* rate */ - avpriv_set_pts_info(st, 64, au_scale, au_byterate); avio_wl32(pb, 0); /* start */ /* remember this offset to fill later */ @@ -267,7 +330,7 @@ static int avi_write_header(AVFormatContext *s) else avio_wl32(pb, 0); /* length, XXX: filled later */ - /* suggested buffer size */ //FIXME set at the end to largest chunk + /* suggested buffer size, is set to largest chunk size in avi_write_trailer */ if (enc->codec_type == AVMEDIA_TYPE_VIDEO) avio_wl32(pb, 1024 * 1024); else if (enc->codec_type == AVMEDIA_TYPE_AUDIO) @@ -282,6 +345,9 @@ static int avi_write_header(AVFormatContext *s) ff_end_tag(pb, strh); if (enc->codec_type != AVMEDIA_TYPE_DATA) { + int ret, flags; + enum AVPixelFormat pix_fmt; + strf = ff_start_tag(pb, "strf"); switch (enc->codec_type) { case AVMEDIA_TYPE_SUBTITLE: @@ -290,43 +356,55 @@ static int avi_write_header(AVFormatContext *s) if (enc->codec_id != AV_CODEC_ID_XSUB) break; case AVMEDIA_TYPE_VIDEO: - ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 0); + /* WMP expects RGB 5:5:5 rawvideo in avi to have bpp set to 16. */ + if ( !enc->codec_tag + && enc->codec_id == AV_CODEC_ID_RAWVIDEO + && enc->pix_fmt == AV_PIX_FMT_RGB555LE + && enc->bits_per_coded_sample == 15) + enc->bits_per_coded_sample = 16; + avist->pal_offset = avio_tell(pb) + 40; + ff_put_bmp_header(pb, enc, ff_codec_bmp_tags, 0, 0); + pix_fmt = avpriv_find_pix_fmt(avpriv_pix_fmt_bps_avi, + enc->bits_per_coded_sample); + if ( !enc->codec_tag + && enc->codec_id == AV_CODEC_ID_RAWVIDEO + && enc->pix_fmt != pix_fmt + && enc->pix_fmt != AV_PIX_FMT_NONE) + av_log(s, AV_LOG_ERROR, "%s rawvideo cannot be written to avi, output file will be unreadable\n", + av_get_pix_fmt_name(enc->pix_fmt)); break; case AVMEDIA_TYPE_AUDIO: - if (ff_put_wav_header(pb, enc) < 0) - return -1; + flags = (avi->write_channel_mask == 0) ? FF_PUT_WAV_HEADER_SKIP_CHANNELMASK : 0; + if ((ret = ff_put_wav_header(pb, enc, flags)) < 0) + return ret; break; default: - return -1; + av_log(s, AV_LOG_ERROR, + "Invalid or not supported codec type '%s' found in the input\n", + (char *)av_x_if_null(av_get_media_type_string(enc->codec_type), "?")); + return AVERROR(EINVAL); } ff_end_tag(pb, strf); if ((t = av_dict_get(st->metadata, "title", NULL, 0))) { ff_riff_write_info_tag(s->pb, "strn", t->value); t = NULL; } + if (enc->codec_id == AV_CODEC_ID_XSUB + && (t = av_dict_get(s->streams[i]->metadata, "language", NULL, 0))) { + const char* langstr = av_convert_lang_to(t->value, AV_LANG_ISO639_1); + t = NULL; + if (langstr) { + char* str = av_asprintf("Subtitle - %s-xx;02", langstr); + if (!str) + return AVERROR(ENOMEM); + ff_riff_write_info_tag(s->pb, "strn", str); + av_free(str); + } + } } if (pb->seekable) { - unsigned char tag[5]; - int j; - - /* Starting to lay out AVI OpenDML master index. - * We want to make it JUNK entry for now, since we'd - * like to get away without making AVI an OpenDML one - * for compatibility reasons. */ - avist->indexes.entry = avist->indexes.ents_allocated = 0; - avist->indexes.indx_start = ff_start_tag(pb, "JUNK"); - avio_wl16(pb, 4); /* wLongsPerEntry */ - avio_w8(pb, 0); /* bIndexSubType (0 == frame index) */ - avio_w8(pb, 0); /* bIndexType (0 == AVI_INDEX_OF_INDEXES) */ - avio_wl32(pb, 0); /* nEntriesInUse (will fill out later on) */ - ffio_wfourcc(pb, avi_stream2fourcc(tag, i, enc->codec_type)); - /* dwChunkId */ - avio_wl64(pb, 0); /* dwReserved[3] */ - // avio_wl32(pb, 0); /* Must be 0. */ - for (j = 0; j < AVI_MASTER_INDEX_SIZE * 2; j++) - avio_wl64(pb, 0); - ff_end_tag(pb, avist->indexes.indx_start); + write_odml_master(s, i); } if (enc->codec_type == AVMEDIA_TYPE_VIDEO && @@ -342,7 +420,7 @@ static int avi_write_header(AVFormatContext *s) avio_wl32(pb, 0); // video format = unknown avio_wl32(pb, 0); // video standard = unknown // TODO: should be avg_frame_rate - avio_wl32(pb, lrintf(1.0 / av_q2d(st->time_base))); + avio_wl32(pb, (2LL*st->time_base.den + st->time_base.num - 1) / (2LL * st->time_base.num)); avio_wl32(pb, enc->width); avio_wl32(pb, enc->height); avio_wl16(pb, den); @@ -381,11 +459,18 @@ static int avi_write_header(AVFormatContext *s) ff_riff_write_info(s); + + padding = s->metadata_header_padding; + if (padding < 0) + padding = 1016; + /* some padding for easier tag editing */ - list2 = ff_start_tag(pb, "JUNK"); - for (i = 0; i < 1016; i += 4) - avio_wl32(pb, 0); - ff_end_tag(pb, list2); + if (padding) { + list2 = ff_start_tag(pb, "JUNK"); + for (i = padding; i > 0; i -= 4) + avio_wl32(pb, 0); + ff_end_tag(pb, list2); + } avi->movi_list = ff_start_tag(pb, "LIST"); ffio_wfourcc(pb, "movi"); @@ -395,6 +480,39 @@ static int avi_write_header(AVFormatContext *s) return 0; } +static void update_odml_entry(AVFormatContext *s, int stream_index, int64_t ix, int size) +{ + AVIOContext *pb = s->pb; + AVIContext *avi = s->priv_data; + AVIStream *avist = s->streams[stream_index]->priv_data; + int64_t pos; + int au_byterate, au_ssize, au_scale; + + avio_flush(pb); + pos = avio_tell(pb); + + /* Updating one entry in the AVI OpenDML master index */ + avio_seek(pb, avist->indexes.indx_start - 8, SEEK_SET); + ffio_wfourcc(pb, "indx"); /* enabling this entry */ + avio_skip(pb, 8); + avio_wl32(pb, avi->riff_id - avist->indexes.master_odml_riff_id_base); /* nEntriesInUse */ + avio_skip(pb, 16 * (avi->riff_id - avist->indexes.master_odml_riff_id_base)); + avio_wl64(pb, ix); /* qwOffset */ + avio_wl32(pb, size); /* dwSize */ + ff_parse_specific_params(s->streams[stream_index], &au_byterate, &au_ssize, &au_scale); + if (s->streams[stream_index]->codec->codec_type == AVMEDIA_TYPE_AUDIO && au_ssize > 0) { + uint32_t audio_segm_size = (avist->audio_strm_length - avist->indexes.audio_strm_offset); + if ((audio_segm_size % au_ssize > 0) && !avist->sample_requested) { + avpriv_request_sample(s, "OpenDML index duration for audio packets with partial frames"); + avist->sample_requested = 1; + } + avio_wl32(pb, audio_segm_size / au_ssize); /* dwDuration (sample count) */ + } else + avio_wl32(pb, avist->indexes.entry); /* dwDuration (packet count) */ + + avio_seek(pb, pos, SEEK_SET); +} + static int avi_write_ix(AVFormatContext *s) { AVIOContext *pb = s->pb; @@ -403,14 +521,26 @@ static int avi_write_ix(AVFormatContext *s) char ix_tag[] = "ix00"; int i, j; - assert(pb->seekable); + av_assert0(pb->seekable); - if (avi->riff_id > AVI_MASTER_INDEX_SIZE) - return -1; + for (i = 0; i < s->nb_streams; i++) { + AVIStream *avist = s->streams[i]->priv_data; + if (avi->riff_id - avist->indexes.master_odml_riff_id_base == AVI_MASTER_INDEX_SIZE) { + int64_t pos; + int size = 8+2+1+1+4+8+4+4+16*AVI_MASTER_INDEX_SIZE; + + pos = avio_tell(pb); + update_odml_entry(s, i, pos, size); + write_odml_master(s, i); + av_assert1(avio_tell(pb) - pos == size); + avist->indexes.master_odml_riff_id_base = avi->riff_id - 1; + } + av_assert0(avi->riff_id - avist->indexes.master_odml_riff_id_base < AVI_MASTER_INDEX_SIZE); + } for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; - int64_t ix, pos; + int64_t ix; avi_stream2fourcc(tag, i, s->streams[i]->codec->codec_type); ix_tag[3] = '0' + i; @@ -435,20 +565,8 @@ static int avi_write_ix(AVFormatContext *s) avio_wl32(pb, ((uint32_t) ie->len & ~0x80000000) | (ie->flags & 0x10 ? 0 : 0x80000000)); } - avio_flush(pb); - pos = avio_tell(pb); - - /* Updating one entry in the AVI OpenDML master index */ - avio_seek(pb, avist->indexes.indx_start - 8, SEEK_SET); - ffio_wfourcc(pb, "indx"); /* enabling this entry */ - avio_skip(pb, 8); - avio_wl32(pb, avi->riff_id); /* nEntriesInUse */ - avio_skip(pb, 16 * avi->riff_id); - avio_wl64(pb, ix); /* qwOffset */ - avio_wl32(pb, pos - ix); /* dwSize */ - avio_wl32(pb, avist->indexes.entry); /* dwDuration */ - - avio_seek(pb, pos, SEEK_SET); + + update_odml_entry(s, i, ix, avio_tell(pb) - ix); } return 0; } @@ -504,9 +622,97 @@ static int avi_write_idx1(AVFormatContext *s) return 0; } +static int write_skip_frames(AVFormatContext *s, int stream_index, int64_t dts) +{ + AVIStream *avist = s->streams[stream_index]->priv_data; + AVCodecContext *enc = s->streams[stream_index]->codec; + + ff_dlog(s, "dts:%s packet_count:%d stream_index:%d\n", av_ts2str(dts), avist->packet_count, stream_index); + while (enc->block_align == 0 && dts != AV_NOPTS_VALUE && + dts > avist->packet_count && enc->codec_id != AV_CODEC_ID_XSUB && avist->packet_count) { + AVPacket empty_packet; + + if (dts - avist->packet_count > 60000) { + av_log(s, AV_LOG_ERROR, "Too large number of skipped frames %"PRId64" > 60000\n", dts - avist->packet_count); + return AVERROR(EINVAL); + } + + av_init_packet(&empty_packet); + empty_packet.size = 0; + empty_packet.data = NULL; + empty_packet.stream_index = stream_index; + avi_write_packet_internal(s, &empty_packet); + ff_dlog(s, "dup dts:%s packet_count:%d\n", av_ts2str(dts), avist->packet_count); + } + + return 0; +} + static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) { unsigned char tag[5]; + const int stream_index = pkt->stream_index; + const uint8_t *data = pkt->data; + int size = pkt->size; + AVIOContext *pb = s->pb; + AVCodecContext *enc = s->streams[stream_index]->codec; + AVIStream *avist = s->streams[stream_index]->priv_data; + int ret; + + if (enc->codec_id == AV_CODEC_ID_H264 && enc->codec_tag == MKTAG('H','2','6','4') && pkt->size) { + ret = ff_check_h264_startcode(s, s->streams[stream_index], pkt); + if (ret < 0) + return ret; + } + + if ((ret = write_skip_frames(s, stream_index, pkt->dts)) < 0) + return ret; + + if (enc->codec_id == AV_CODEC_ID_RAWVIDEO && enc->codec_tag == 0) { + int64_t bpc = enc->bits_per_coded_sample != 15 ? enc->bits_per_coded_sample : 16; + int expected_stride = ((enc->width * bpc + 31) >> 5)*4; + + ret = ff_reshuffle_raw_rgb(s, &pkt, enc, expected_stride); + if (ret < 0) + return ret; + if (ret) { + if (ret == CONTAINS_PAL) { + int pc_tag, i; + int pal_size = 1 << enc->bits_per_coded_sample; + if (!avist->hdr_pal_done) { + int64_t cur_offset = avio_tell(pb); + avio_seek(pb, avist->pal_offset, SEEK_SET); + for (i = 0; i < pal_size; i++) { + uint32_t v = AV_RL32(data + size - 4*pal_size + 4*i); + avio_wl32(pb, v & 0xffffff); + } + avio_seek(pb, cur_offset, SEEK_SET); + avist->hdr_pal_done++; + } + avi_stream2fourcc(tag, stream_index, enc->codec_type); + tag[2] = 'p'; tag[3] = 'c'; + pc_tag = ff_start_tag(pb, tag); + avio_w8(pb, 0); + avio_w8(pb, pal_size & 0xFF); + avio_wl16(pb, 0); // reserved + for (i = 0; i < pal_size; i++) { + uint32_t v = AV_RL32(data + size - 4*pal_size + 4*i); + avio_wb32(pb, v<<8); + } + ff_end_tag(pb, pc_tag); + } + ret = avi_write_packet_internal(s, pkt); + av_packet_free(&pkt); + return ret; + } + } + + return avi_write_packet_internal(s, pkt); +} + +static int avi_write_packet_internal(AVFormatContext *s, AVPacket *pkt) +{ + unsigned char tag[5]; unsigned int flags = 0; const int stream_index = pkt->stream_index; int size = pkt->size; @@ -515,16 +721,9 @@ static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) AVIStream *avist = s->streams[stream_index]->priv_data; AVCodecContext *enc = s->streams[stream_index]->codec; - while (enc->block_align == 0 && pkt->dts != AV_NOPTS_VALUE && - pkt->dts > avist->packet_count) { - AVPacket empty_packet; + if (pkt->dts != AV_NOPTS_VALUE) + avist->last_dts = pkt->dts + pkt->duration; - av_init_packet(&empty_packet); - empty_packet.size = 0; - empty_packet.data = NULL; - empty_packet.stream_index = stream_index; - avi_write_packet(s, &empty_packet); - } avist->packet_count++; // Make sure to put an OpenDML chunk when the file size exceeds the limits @@ -547,27 +746,27 @@ static int avi_write_packet(AVFormatContext *s, AVPacket *pkt) avist->audio_strm_length += size; if (s->pb->seekable) { - int err; AVIIndex *idx = &avist->indexes; int cl = idx->entry / AVI_INDEX_CLUSTER_SIZE; int id = idx->entry % AVI_INDEX_CLUSTER_SIZE; if (idx->ents_allocated <= idx->entry) { - if ((err = av_reallocp(&idx->cluster, - (cl + 1) * sizeof(*idx->cluster))) < 0) { + idx->cluster = av_realloc_f(idx->cluster, sizeof(void*), cl+1); + if (!idx->cluster) { idx->ents_allocated = 0; idx->entry = 0; - return err; + return AVERROR(ENOMEM); } idx->cluster[cl] = av_malloc(AVI_INDEX_CLUSTER_SIZE * sizeof(AVIIentry)); if (!idx->cluster[cl]) - return -1; + return AVERROR(ENOMEM); idx->ents_allocated += AVI_INDEX_CLUSTER_SIZE; } idx->cluster[cl][id].flags = flags; idx->cluster[cl][id].pos = avio_tell(pb) - avi->movi_list; idx->cluster[cl][id].len = size; + avist->max_size = FFMAX(avist->max_size, size); idx->entry++; } @@ -588,6 +787,11 @@ static int avi_write_trailer(AVFormatContext *s) int i, j, n, nb_frames; int64_t file_size; + for (i = 0; i < s->nb_streams; i++) { + AVIStream *avist = s->streams[i]->priv_data; + write_skip_frames(s, i, avist->last_dts); + } + if (pb->seekable) { if (avi->riff_id == 1) { ff_end_tag(pb, avi->movi_list); @@ -626,14 +830,32 @@ static int avi_write_trailer(AVFormatContext *s) for (i = 0; i < s->nb_streams; i++) { AVIStream *avist = s->streams[i]->priv_data; for (j = 0; j < avist->indexes.ents_allocated / AVI_INDEX_CLUSTER_SIZE; j++) - av_free(avist->indexes.cluster[j]); + av_freep(&avist->indexes.cluster[j]); av_freep(&avist->indexes.cluster); avist->indexes.ents_allocated = avist->indexes.entry = 0; + if (pb->seekable) { + avio_seek(pb, avist->frames_hdr_strm + 4, SEEK_SET); + avio_wl32(pb, avist->max_size); + } } return res; } +#define OFFSET(x) offsetof(AVIContext, x) +#define ENC AV_OPT_FLAG_ENCODING_PARAM +static const AVOption options[] = { + { "write_channel_mask", "write channel mask into wave format header", OFFSET(write_channel_mask), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, ENC }, + { NULL }, +}; + +static const AVClass avi_muxer_class = { + .class_name = "AVI muxer", + .item_name = av_default_item_name, + .option = options, + .version = LIBAVUTIL_VERSION_INT, +}; + AVOutputFormat ff_avi_muxer = { .name = "avi", .long_name = NULL_IF_CONFIG_SMALL("AVI (Audio Video Interleaved)"), @@ -648,4 +870,5 @@ AVOutputFormat ff_avi_muxer = { .codec_tag = (const AVCodecTag * const []) { ff_codec_bmp_tags, ff_codec_wav_tags, 0 }, + .priv_class = &avi_muxer_class, }; |