*/
#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);
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;
{
MPEG4AudioConfig mp4ac;
- if (ff_mpeg4audio_get_config(&mp4ac, codec->extradata, codec->extradata_size) < 0) {
+ 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;
}
} else if (codec->codec_type == AVMEDIA_TYPE_VIDEO) {
if (qt_id) {
if (!codec->codec_tag)
- codec->codec_tag = ff_codec_get_tag(codec_movvideo_tags, codec->codec_id);
+ codec->codec_tag = ff_codec_get_tag(ff_codec_movvideo_tags, codec->codec_id);
if (codec->extradata_size)
avio_write(dyn_cp, codec->extradata, codec->extradata_size);
} else {
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)
put_ebml_uint(pb, MATROSKA_ID_TRACKDEFAULTDURATION, av_q2d(codec->time_base)*1E9);
if (!native_id &&
- ff_codec_get_tag(codec_movvideo_tags, codec->codec_id) &&
+ ff_codec_get_tag(ff_codec_movvideo_tags, codec->codec_id) &&
(!ff_codec_get_tag(ff_codec_bmp_tags, codec->codec_id)
|| codec->codec_id == CODEC_ID_SVQ1
|| codec->codec_id == CODEC_ID_SVQ3
// 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 (!s->pb->seekable)
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);
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,
- .flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS,
+ .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,
- .flags = AVFMT_GLOBALHEADER,
- .codec_tag = (const AVCodecTag* const []){ff_codec_wav_tags, 0},
+ .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 },
};
#endif