* Matroska muxer
* Copyright (c) 2007 David Conrad
*
- * This file is part of FFmpeg.
+ * This file is part of Libav.
*
- * FFmpeg is free software; you can redistribute it and/or
+ * 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.
*
- * FFmpeg is distributed in the hope that it will be useful,
+ * 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 FFmpeg; if not, write to the Free Software
+ * License along with Libav; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "avformat.h"
+#include "internal.h"
#include "riff.h"
#include "isom.h"
#include "matroska.h"
#include "avlanguage.h"
#include "libavutil/samplefmt.h"
#include "libavutil/intreadwrite.h"
+#include "libavutil/intfloat.h"
+#include "libavutil/mathematics.h"
#include "libavutil/random_seed.h"
#include "libavutil/lfg.h"
+#include "libavutil/dict.h"
+#include "libavutil/avstring.h"
#include "libavcodec/xiph.h"
#include "libavcodec/mpeg4audio.h"
-#include <strings.h>
typedef struct ebml_master {
int64_t pos; ///< absolute offset in the file where the master's elements start
unsigned int audio_buffer_size;
AVPacket cur_audio_pkt;
+
+ int have_attachments;
} MatroskaMuxContext;
{
put_ebml_id(pb, elementid);
put_ebml_num(pb, 8, 0);
- avio_wb64(pb, av_dbl2int(val));
+ avio_wb64(pb, av_double2int(val));
}
static void put_ebml_binary(AVIOContext *pb, unsigned int elementid,
currentpos = avio_tell(pb);
- if (seekhead->reserved_size > 0)
- if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0)
- return -1;
+ if (seekhead->reserved_size > 0) {
+ if (avio_seek(pb, seekhead->filepos, SEEK_SET) < 0) {
+ currentpos = -1;
+ goto fail;
+ }
+ }
metaseek = start_ebml_master(pb, MATROSKA_ID_SEEKHEAD, seekhead->reserved_size);
for (i = 0; i < seekhead->num_entries; i++) {
currentpos = seekhead->filepos;
}
+fail:
av_free(seekhead->entries);
av_free(seekhead);
}
end_ebml_master(pb, cues_element);
- av_free(cues->entries);
- av_free(cues);
return currentpos;
}
else
first_header_size = 42;
- if (ff_split_xiph_headers(codec->extradata, codec->extradata_size,
+ if (avpriv_split_xiph_headers(codec->extradata, codec->extradata_size,
first_header_size, header_start, header_len) < 0) {
av_log(s, AV_LOG_ERROR, "Extradata corrupt.\n");
return -1;
static void get_aac_sample_rates(AVFormatContext *s, AVCodecContext *codec, int *sample_rate, int *output_sample_rate)
{
- int sri;
+ MPEG4AudioConfig mp4ac;
- if (codec->extradata_size < 2) {
- av_log(s, AV_LOG_WARNING, "No AAC extradata, unable to determine samplerate.\n");
+ if (avpriv_mpeg4audio_get_config(&mp4ac, codec->extradata,
+ codec->extradata_size * 8, 1) < 0) {
+ av_log(s, AV_LOG_WARNING, "Error parsing AAC extradata, unable to determine samplerate.\n");
return;
}
- sri = ((codec->extradata[0] << 1) & 0xE) | (codec->extradata[1] >> 7);
- if (sri > 12) {
- av_log(s, AV_LOG_WARNING, "AAC samplerate index out of bounds\n");
- return;
- }
- *sample_rate = ff_mpeg4audio_sample_rates[sri];
-
- // if sbr, get output sample rate as well
- if (codec->extradata_size == 5) {
- sri = (codec->extradata[4] >> 3) & 0xF;
- if (sri > 12) {
- av_log(s, AV_LOG_WARNING, "AAC output samplerate index out of bounds\n");
- return;
- }
- *output_sample_rate = ff_mpeg4audio_sample_rates[sri];
- }
+ *sample_rate = mp4ac.sample_rate;
+ *output_sample_rate = mp4ac.ext_sample_rate;
}
static int mkv_write_codecprivate(AVFormatContext *s, AVIOContext *pb, AVCodecContext *codec, int native_id, int qt_id)
uint8_t *codecpriv;
int ret, codecpriv_size;
- ret = url_open_dyn_buf(&dyn_cp);
+ ret = avio_open_dyn_buf(&dyn_cp);
if(ret < 0)
return ret;
ff_put_wav_header(dyn_cp, codec);
}
- codecpriv_size = url_close_dyn_buf(dyn_cp, &codecpriv);
+ codecpriv_size = avio_close_dyn_buf(dyn_cp, &codecpriv);
if (codecpriv_size)
put_ebml_binary(pb, MATROSKA_ID_CODECPRIVATE, codecpriv, codecpriv_size);
av_free(codecpriv);
int bit_depth = av_get_bits_per_sample(codec->codec_id);
int sample_rate = codec->sample_rate;
int output_sample_rate = 0;
- AVMetadataTag *tag;
+ AVDictionaryEntry *tag;
+
+ if (codec->codec_type == AVMEDIA_TYPE_ATTACHMENT) {
+ mkv->have_attachments = 1;
+ continue;
+ }
if (!bit_depth)
- bit_depth = av_get_bits_per_sample_fmt(codec->sample_fmt);
+ bit_depth = av_get_bytes_per_sample(codec->sample_fmt) << 3;
if (codec->codec_id == CODEC_ID_AAC)
get_aac_sample_rates(s, codec, &sample_rate, &output_sample_rate);
put_ebml_uint (pb, MATROSKA_ID_TRACKUID , i + 1);
put_ebml_uint (pb, MATROSKA_ID_TRACKFLAGLACING , 0); // no lacing (yet)
- if ((tag = av_metadata_get(st->metadata, "title", NULL, 0)))
+ if ((tag = av_dict_get(st->metadata, "title", NULL, 0)))
put_ebml_string(pb, MATROSKA_ID_TRACKNAME, tag->value);
- tag = av_metadata_get(st->metadata, "language", NULL, 0);
+ tag = av_dict_get(st->metadata, "language", NULL, 0);
put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag ? tag->value:"und");
if (st->disposition)
// XXX: interlace flag?
put_ebml_uint (pb, MATROSKA_ID_VIDEOPIXELWIDTH , codec->width);
put_ebml_uint (pb, MATROSKA_ID_VIDEOPIXELHEIGHT, codec->height);
+ if ((tag = av_dict_get(s->metadata, "stereo_mode", NULL, 0))) {
+ uint8_t stereo_fmt = atoi(tag->value);
+ int valid_fmt = 0;
+
+ switch (mkv->mode) {
+ case MODE_WEBM:
+ if (stereo_fmt <= MATROSKA_VIDEO_STEREOMODE_TYPE_TOP_BOTTOM
+ || stereo_fmt == MATROSKA_VIDEO_STEREOMODE_TYPE_RIGHT_LEFT)
+ valid_fmt = 1;
+ break;
+ case MODE_MATROSKAv2:
+ if (stereo_fmt <= MATROSKA_VIDEO_STEREOMODE_TYPE_BOTH_EYES_BLOCK_RL)
+ valid_fmt = 1;
+ break;
+ }
+
+ if (valid_fmt)
+ put_ebml_uint (pb, MATROSKA_ID_VIDEOSTEREOMODE, stereo_fmt);
+ }
if (st->sample_aspect_ratio.num) {
int d_width = codec->width*av_q2d(st->sample_aspect_ratio);
put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , d_width);
case AVMEDIA_TYPE_SUBTITLE:
put_ebml_uint(pb, MATROSKA_ID_TRACKTYPE, MATROSKA_TRACK_TYPE_SUBTITLE);
+ if (!native_id) {
+ av_log(s, AV_LOG_ERROR, "Subtitle codec %d is not supported.\n", codec->codec_id);
+ return AVERROR(ENOSYS);
+ }
break;
default:
- av_log(s, AV_LOG_ERROR, "Only audio, video, and subtitles are supported for Matroska.");
+ av_log(s, AV_LOG_ERROR, "Only audio, video, and subtitles are supported for Matroska.\n");
break;
}
ret = mkv_write_codecprivate(s, pb, codec, native_id, qt_id);
end_ebml_master(pb, track);
// ms precision is the de-facto standard timescale for mkv files
- av_set_pts_info(st, 64, 1, 1000);
+ avpriv_set_pts_info(st, 64, 1, 1000);
}
end_ebml_master(pb, tracks);
return 0;
for (i = 0; i < s->nb_chapters; i++) {
ebml_master chapteratom, chapterdisplay;
AVChapter *c = s->chapters[i];
- AVMetadataTag *t = NULL;
+ AVDictionaryEntry *t = NULL;
chapteratom = start_ebml_master(pb, MATROSKA_ID_CHAPTERATOM, 0);
put_ebml_uint(pb, MATROSKA_ID_CHAPTERUID, c->id);
av_rescale_q(c->end, c->time_base, scale));
put_ebml_uint(pb, MATROSKA_ID_CHAPTERFLAGHIDDEN , 0);
put_ebml_uint(pb, MATROSKA_ID_CHAPTERFLAGENABLED, 1);
- if ((t = av_metadata_get(c->metadata, "title", NULL, 0))) {
+ if ((t = av_dict_get(c->metadata, "title", NULL, 0))) {
chapterdisplay = start_ebml_master(pb, MATROSKA_ID_CHAPTERDISPLAY, 0);
put_ebml_string(pb, MATROSKA_ID_CHAPSTRING, t->value);
put_ebml_string(pb, MATROSKA_ID_CHAPLANG , "und");
return 0;
}
-static void mkv_write_simpletag(AVIOContext *pb, AVMetadataTag *t)
+static void mkv_write_simpletag(AVIOContext *pb, AVDictionaryEntry *t)
{
uint8_t *key = av_strdup(t->key);
uint8_t *p = key;
av_freep(&key);
}
-static int mkv_write_tag(AVFormatContext *s, AVMetadata *m, unsigned int elementid,
+static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int elementid,
unsigned int uid, ebml_master *tags)
{
MatroskaMuxContext *mkv = s->priv_data;
ebml_master tag, targets;
- AVMetadataTag *t = NULL;
+ AVDictionaryEntry *t = NULL;
int ret;
if (!tags->pos) {
put_ebml_uint(s->pb, elementid, uid);
end_ebml_master(s->pb, targets);
- while ((t = av_metadata_get(m, "", t, AV_METADATA_IGNORE_SUFFIX)))
- if (strcasecmp(t->key, "title"))
+ while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX)))
+ if (av_strcasecmp(t->key, "title"))
mkv_write_simpletag(s->pb, t);
end_ebml_master(s->pb, tag);
ff_metadata_conv_ctx(s, ff_mkv_metadata_conv, NULL);
- if (av_metadata_get(s->metadata, "", NULL, AV_METADATA_IGNORE_SUFFIX)) {
+ if (av_dict_get(s->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX)) {
ret = mkv_write_tag(s, s->metadata, 0, 0, &tags);
if (ret < 0) return ret;
}
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
- if (!av_metadata_get(st->metadata, "", 0, AV_METADATA_IGNORE_SUFFIX))
+ if (!av_dict_get(st->metadata, "", 0, AV_DICT_IGNORE_SUFFIX))
continue;
ret = mkv_write_tag(s, st->metadata, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &tags);
for (i = 0; i < s->nb_chapters; i++) {
AVChapter *ch = s->chapters[i];
- if (!av_metadata_get(ch->metadata, "", NULL, AV_METADATA_IGNORE_SUFFIX))
+ if (!av_dict_get(ch->metadata, "", NULL, AV_DICT_IGNORE_SUFFIX))
continue;
ret = mkv_write_tag(s, ch->metadata, MATROSKA_ID_TAGTARGETS_CHAPTERUID, ch->id, &tags);
return 0;
}
+static int mkv_write_attachments(AVFormatContext *s)
+{
+ MatroskaMuxContext *mkv = s->priv_data;
+ AVIOContext *pb = s->pb;
+ ebml_master attachments;
+ AVLFG c;
+ int i, ret;
+
+ if (!mkv->have_attachments)
+ return 0;
+
+ av_lfg_init(&c, av_get_random_seed());
+
+ ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_ATTACHMENTS, avio_tell(pb));
+ if (ret < 0) return ret;
+
+ attachments = start_ebml_master(pb, MATROSKA_ID_ATTACHMENTS, 0);
+
+ for (i = 0; i < s->nb_streams; i++) {
+ AVStream *st = s->streams[i];
+ ebml_master attached_file;
+ AVDictionaryEntry *t;
+ const char *mimetype = NULL;
+
+ if (st->codec->codec_type != AVMEDIA_TYPE_ATTACHMENT)
+ continue;
+
+ attached_file = start_ebml_master(pb, MATROSKA_ID_ATTACHEDFILE, 0);
+
+ if (t = av_dict_get(st->metadata, "title", NULL, 0))
+ put_ebml_string(pb, MATROSKA_ID_FILEDESC, t->value);
+ if (!(t = av_dict_get(st->metadata, "filename", NULL, 0))) {
+ av_log(s, AV_LOG_ERROR, "Attachment stream %d has no filename tag.\n", i);
+ return AVERROR(EINVAL);
+ }
+ put_ebml_string(pb, MATROSKA_ID_FILENAME, t->value);
+ if (t = av_dict_get(st->metadata, "mimetype", NULL, 0))
+ mimetype = t->value;
+ else if (st->codec->codec_id != CODEC_ID_NONE ) {
+ int i;
+ for (i = 0; ff_mkv_mime_tags[i].id != CODEC_ID_NONE; i++)
+ if (ff_mkv_mime_tags[i].id == st->codec->codec_id) {
+ mimetype = ff_mkv_mime_tags[i].str;
+ break;
+ }
+ }
+ if (!mimetype) {
+ av_log(s, AV_LOG_ERROR, "Attachment stream %d has no mimetype tag and "
+ "it cannot be deduced from the codec id.\n", i);
+ return AVERROR(EINVAL);
+ }
+
+ put_ebml_string(pb, MATROSKA_ID_FILEMIMETYPE, mimetype);
+ put_ebml_binary(pb, MATROSKA_ID_FILEDATA, st->codec->extradata, st->codec->extradata_size);
+ put_ebml_uint(pb, MATROSKA_ID_FILEUID, av_lfg_get(&c));
+ end_ebml_master(pb, attached_file);
+ }
+ end_ebml_master(pb, attachments);
+
+ return 0;
+}
+
static int mkv_write_header(AVFormatContext *s)
{
MatroskaMuxContext *mkv = s->priv_data;
AVIOContext *pb = s->pb;
ebml_master ebml_header, segment_info;
- AVMetadataTag *tag;
+ AVDictionaryEntry *tag;
int ret, i;
if (!strcmp(s->oformat->name, "webm")) mkv->mode = MODE_WEBM;
segment_info = start_ebml_master(pb, MATROSKA_ID_INFO, 0);
put_ebml_uint(pb, MATROSKA_ID_TIMECODESCALE, 1000000);
- if ((tag = av_metadata_get(s->metadata, "title", NULL, 0)))
+ if ((tag = av_dict_get(s->metadata, "title", NULL, 0)))
put_ebml_string(pb, MATROSKA_ID_TITLE, tag->value);
if (!(s->streams[0]->codec->flags & CODEC_FLAG_BITEXACT)) {
uint32_t segment_uid[4];
ret = mkv_write_tags(s);
if (ret < 0) return ret;
+
+ ret = mkv_write_attachments(s);
+ if (ret < 0) return ret;
}
- if (url_is_streamed(s->pb))
+ if (!s->pb->seekable)
mkv_write_seekhead(pb, mkv->main_seekhead);
mkv->cues = mkv_start_cues(mkv->segment_offset);
mkv->cur_audio_pkt.size = 0;
mkv->audio_buffer_size = 0;
- put_flush_packet(pb);
+ avio_flush(pb);
return 0;
}
size -= start - data;
sscanf(data, "Dialogue: %d,", &layer);
i = snprintf(buffer, sizeof(buffer), "%"PRId64",%d,",
- s->streams[pkt->stream_index]->nb_frames++, layer);
+ s->streams[pkt->stream_index]->nb_frames, layer);
size = FFMIN(i+size, sizeof(buffer));
memcpy(buffer+i, start, size-i);
if (!mkv->dyn_bc)
return;
- bufsize = url_close_dyn_buf(mkv->dyn_bc, &dyn_buf);
+ bufsize = avio_close_dyn_buf(mkv->dyn_bc, &dyn_buf);
avio_write(s->pb, dyn_buf, bufsize);
av_free(dyn_buf);
mkv->dyn_bc = NULL;
return AVERROR(EINVAL);
}
- if (url_is_streamed(s->pb)) {
+ if (!s->pb->seekable) {
if (!mkv->dyn_bc)
- url_open_dyn_buf(&mkv->dyn_bc);
+ avio_open_dyn_buf(&mkv->dyn_bc);
pb = mkv->dyn_bc;
}
static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt)
{
MatroskaMuxContext *mkv = s->priv_data;
- AVIOContext *pb = url_is_streamed(s->pb) ? mkv->dyn_bc : s->pb;
+ AVIOContext *pb = s->pb->seekable ? s->pb : mkv->dyn_bc;
AVCodecContext *codec = s->streams[pkt->stream_index]->codec;
int ret, keyframe = !!(pkt->flags & AV_PKT_FLAG_KEY);
int64_t ts = mkv->tracks[pkt->stream_index].write_dts ? pkt->dts : pkt->pts;
- int cluster_size = avio_tell(pb) - (url_is_streamed(s->pb) ? 0 : mkv->cluster_pos);
+ int cluster_size = avio_tell(pb) - (s->pb->seekable ? mkv->cluster_pos : 0);
// start a new cluster every 5 MB or 5 sec, or 32k / 1 sec for streaming or
// after 4k and on a keyframe
if (mkv->cluster_pos &&
- ((url_is_streamed(s->pb) && (cluster_size > 32*1024 || ts > mkv->cluster_pts + 1000))
+ ((!s->pb->seekable && (cluster_size > 32*1024 || ts > mkv->cluster_pts + 1000))
|| cluster_size > 5*1024*1024 || ts > mkv->cluster_pts + 5000
|| (codec->codec_type == AVMEDIA_TYPE_VIDEO && keyframe && cluster_size > 4*1024))) {
av_log(s, AV_LOG_DEBUG, "Starting new cluster at offset %" PRIu64
end_ebml_master(pb, mkv->cluster);
}
- if (!url_is_streamed(pb)) {
- cuespos = mkv_write_cues(pb, mkv->cues, s->nb_streams);
+ if (pb->seekable) {
+ if (mkv->cues->num_entries) {
+ cuespos = mkv_write_cues(pb, mkv->cues, s->nb_streams);
+
+ ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_CUES, cuespos);
+ if (ret < 0) return ret;
+ }
- ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_CUES , cuespos);
- if (ret < 0) return ret;
mkv_write_seekhead(pb, mkv->main_seekhead);
// update the duration
end_ebml_master(pb, mkv->segment);
av_free(mkv->tracks);
+ av_freep(&mkv->cues->entries);
+ av_freep(&mkv->cues);
av_destruct_packet(&mkv->cur_audio_pkt);
- put_flush_packet(pb);
+ avio_flush(pb);
+ return 0;
+}
+
+static int mkv_query_codec(enum CodecID codec_id, int std_compliance)
+{
+ int i;
+ for (i = 0; ff_mkv_codec_tags[i].id != CODEC_ID_NONE; i++)
+ if (ff_mkv_codec_tags[i].id == codec_id)
+ return 1;
+
+ if (std_compliance < FF_COMPLIANCE_NORMAL) { // mkv theoretically supports any
+ enum AVMediaType type = avcodec_get_type(codec_id); // video/audio through VFW/ACM
+ if (type == AVMEDIA_TYPE_VIDEO || type == AVMEDIA_TYPE_AUDIO)
+ return 1;
+ }
+
return 0;
}
#if CONFIG_MATROSKA_MUXER
AVOutputFormat ff_matroska_muxer = {
- "matroska",
- NULL_IF_CONFIG_SMALL("Matroska file format"),
- "video/x-matroska",
- "mkv",
- sizeof(MatroskaMuxContext),
- CODEC_ID_MP2,
- CODEC_ID_MPEG4,
- mkv_write_header,
- mkv_write_packet,
- mkv_write_trailer,
- .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS,
- .codec_tag = (const AVCodecTag* const []){ff_codec_bmp_tags, ff_codec_wav_tags, 0},
- .subtitle_codec = CODEC_ID_TEXT,
+ .name = "matroska",
+ .long_name = NULL_IF_CONFIG_SMALL("Matroska file format"),
+ .mime_type = "video/x-matroska",
+ .extensions = "mkv",
+ .priv_data_size = sizeof(MatroskaMuxContext),
+#if CONFIG_LIBVORBIS_ENCODER
+ .audio_codec = CODEC_ID_VORBIS,
+#else
+ .audio_codec = CODEC_ID_AC3,
+#endif
+#if CONFIG_LIBX264_ENCODER
+ .video_codec = CODEC_ID_H264,
+#else
+ .video_codec = CODEC_ID_MPEG4,
+#endif
+ .write_header = mkv_write_header,
+ .write_packet = mkv_write_packet,
+ .write_trailer = mkv_write_trailer,
+ .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS,
+ .codec_tag = (const AVCodecTag* const []){ff_codec_bmp_tags, ff_codec_wav_tags, 0},
+ .subtitle_codec = CODEC_ID_SSA,
+ .query_codec = mkv_query_codec,
};
#endif
#if CONFIG_WEBM_MUXER
AVOutputFormat ff_webm_muxer = {
- "webm",
- NULL_IF_CONFIG_SMALL("WebM file format"),
- "video/webm",
- "webm",
- sizeof(MatroskaMuxContext),
- CODEC_ID_VORBIS,
- CODEC_ID_VP8,
- mkv_write_header,
- mkv_write_packet,
- mkv_write_trailer,
+ .name = "webm",
+ .long_name = NULL_IF_CONFIG_SMALL("WebM file format"),
+ .mime_type = "video/webm",
+ .extensions = "webm",
+ .priv_data_size = sizeof(MatroskaMuxContext),
+ .audio_codec = CODEC_ID_VORBIS,
+ .video_codec = CODEC_ID_VP8,
+ .write_header = mkv_write_header,
+ .write_packet = mkv_write_packet,
+ .write_trailer = mkv_write_trailer,
.flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS,
};
#endif
#if CONFIG_MATROSKA_AUDIO_MUXER
AVOutputFormat ff_matroska_audio_muxer = {
- "matroska",
- NULL_IF_CONFIG_SMALL("Matroska file format"),
- "audio/x-matroska",
- "mka",
- sizeof(MatroskaMuxContext),
- CODEC_ID_MP2,
- CODEC_ID_NONE,
- mkv_write_header,
- mkv_write_packet,
- mkv_write_trailer,
+ .name = "matroska",
+ .long_name = NULL_IF_CONFIG_SMALL("Matroska file format"),
+ .mime_type = "audio/x-matroska",
+ .extensions = "mka",
+ .priv_data_size = sizeof(MatroskaMuxContext),
+#if CONFIG_LIBVORBIS_ENCODER
+ .audio_codec = CODEC_ID_VORBIS,
+#else
+ .audio_codec = CODEC_ID_AC3,
+#endif
+ .video_codec = CODEC_ID_NONE,
+ .write_header = mkv_write_header,
+ .write_packet = mkv_write_packet,
+ .write_trailer = mkv_write_trailer,
.flags = AVFMT_GLOBALHEADER,
.codec_tag = (const AVCodecTag* const []){ff_codec_wav_tags, 0},
};