typedef struct mkv_track {
int write_dts;
int has_cue;
+ int64_t codecpriv_offset;
int64_t ts_offset;
} mkv_track;
+typedef struct mkv_attachment {
+ int stream_idx;
+ uint32_t fileuid;
+} mkv_attachment;
+
+typedef struct mkv_attachments {
+ mkv_attachment *entries;
+ int num_entries;
+} mkv_attachments;
+
#define MODE_MATROSKAv2 0x01
#define MODE_WEBM 0x02
ebml_master tags;
AVIOContext *info_bc;
ebml_master info;
+ AVIOContext *tracks_bc;
+ ebml_master tracks_master;
ebml_master segment;
int64_t segment_offset;
ebml_master cluster;
mkv_seekhead *main_seekhead;
mkv_cues *cues;
mkv_track *tracks;
+ mkv_attachments *attachments;
AVPacket cur_audio_pkt;
avio_seek(pb, pos, SEEK_SET);
}
-static int start_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, ebml_master *master,
- unsigned int elementid, uint64_t expectedsize)
+static int start_ebml_master_crc32(AVIOContext *pb, AVIOContext **dyn_cp, MatroskaMuxContext *mkv,
+ ebml_master *master, unsigned int elementid, uint64_t expectedsize)
{
int ret;
- if (ret = avio_open_dyn_buf(dyn_cp) < 0)
+ if ((ret = avio_open_dyn_buf(dyn_cp)) < 0)
return ret;
- if (pb->seekable)
+ if (pb->seekable) {
*master = start_ebml_master(pb, elementid, expectedsize);
- else
+ if (mkv->write_crc && mkv->mode != MODE_WEBM)
+ put_ebml_void(*dyn_cp, 6); /* Reserve space for CRC32 so position/size calculations using avio_tell() take it into account */
+ } else
*master = start_ebml_master(*dyn_cp, elementid, expectedsize);
return 0;
ebml_master master)
{
uint8_t *buf, crc[4];
- int size;
+ int size, skip = 0;
if (pb->seekable) {
size = avio_close_dyn_buf(*dyn_cp, &buf);
if (mkv->write_crc && mkv->mode != MODE_WEBM) {
- AV_WL32(crc, av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), UINT32_MAX, buf, size) ^ UINT32_MAX);
+ skip = 6; /* Skip reserved 6-byte long void element from the dynamic buffer. */
+ AV_WL32(crc, av_crc(av_crc_get_table(AV_CRC_32_IEEE_LE), UINT32_MAX, buf + skip, size - skip) ^ UINT32_MAX);
put_ebml_binary(pb, EBML_ID_CRC32, crc, sizeof(crc));
}
- avio_write(pb, buf, size);
+ avio_write(pb, buf + skip, size - skip);
end_ebml_master(pb, master);
} else {
end_ebml_master(*dyn_cp, master);
av_freep(&mkv->cues->entries);
av_freep(&mkv->cues);
}
+ if (mkv->attachments) {
+ av_freep(&mkv->attachments->entries);
+ av_freep(&mkv->attachments);
+ }
av_freep(&mkv->tracks);
av_freep(&mkv->stream_durations);
av_freep(&mkv->stream_duration_offsets);
}
}
- if (start_ebml_master_crc32(pb, &dyn_cp, &metaseek, MATROSKA_ID_SEEKHEAD,
+ if (start_ebml_master_crc32(pb, &dyn_cp, mkv, &metaseek, MATROSKA_ID_SEEKHEAD,
seekhead->reserved_size) < 0) {
currentpos = -1;
goto fail;
int i, j, ret;
currentpos = avio_tell(pb);
- ret = start_ebml_master_crc32(pb, &dyn_cp, &cues_element, MATROSKA_ID_CUES, 0);
+ ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, &cues_element, MATROSKA_ID_CUES, 0);
if (ret < 0)
return ret;
if (!par->codec_tag)
par->codec_tag = ff_codec_get_tag(ff_codec_movvideo_tags,
par->codec_id);
- if (par->extradata_size) {
- if ( ff_codec_get_id(ff_codec_movvideo_tags, par->codec_tag) == par->codec_id
- && ff_codec_get_id(ff_codec_movvideo_tags, AV_RL32(par->extradata + 4)) != par->codec_id
- ) {
- int i;
- avio_wb32(dyn_cp, 0x5a + par->extradata_size);
- avio_wl32(dyn_cp, par->codec_tag);
- for(i = 0; i < 0x5a - 8; i++)
- avio_w8(dyn_cp, 0);
- }
- avio_write(dyn_cp, par->extradata, par->extradata_size);
+ if ( ff_codec_get_id(ff_codec_movvideo_tags, par->codec_tag) == par->codec_id
+ && (!par->extradata_size || ff_codec_get_id(ff_codec_movvideo_tags, AV_RL32(par->extradata + 4)) != par->codec_id)
+ ) {
+ int i;
+ avio_wb32(dyn_cp, 0x5a + par->extradata_size);
+ avio_wl32(dyn_cp, par->codec_tag);
+ for(i = 0; i < 0x5a - 8; i++)
+ avio_w8(dyn_cp, 0);
}
+ avio_write(dyn_cp, par->extradata, par->extradata_size);
} else {
if (!ff_codec_get_tag(ff_codec_bmp_tags, par->codec_id))
av_log(s, AV_LOG_WARNING, "codec %s is not supported by this format\n",
par->color_range < AVCOL_RANGE_NB) {
put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOCOLORRANGE, par->color_range);
}
+ if (par->chroma_location != AVCHROMA_LOC_UNSPECIFIED &&
+ par->chroma_location <= AVCHROMA_LOC_TOP) {
+ int xpos, ypos;
+
+ avcodec_enum_to_chroma_pos(&xpos, &ypos, par->chroma_location);
+ put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOCOLORCHROMASITINGHORZ, (xpos >> 7) + 1);
+ put_ebml_uint(dyn_cp, MATROSKA_ID_VIDEOCOLORCHROMASITINGVERT, (ypos >> 7) + 1);
+ }
if (side_data_size == sizeof(AVMasteringDisplayMetadata)) {
ebml_master meta_element = start_ebml_master(
dyn_cp, MATROSKA_ID_VIDEOCOLORMASTERINGMETA, 0);
{
switch (field_order) {
case AV_FIELD_UNKNOWN:
- put_ebml_uint(pb, MATROSKA_ID_VIDEOFLAGINTERLACED,
- MATROSKA_VIDEO_INTERLACE_FLAG_UNDETERMINED);
break;
case AV_FIELD_PROGRESSIVE:
put_ebml_uint(pb, MATROSKA_ID_VIDEOFLAGINTERLACED,
av_log(s, AV_LOG_ERROR, "Overflow in display width\n");
return AVERROR(EINVAL);
}
- put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , d_width / display_width_div);
- put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, par->height / display_height_div);
+ if (d_width != par->width || display_width_div != 1 || display_height_div != 1) {
+ if (mkv->mode == MODE_WEBM || display_width_div != 1 || display_height_div != 1) {
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , d_width / display_width_div);
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, par->height / display_height_div);
+ } else {
+ AVRational display_aspect_ratio;
+ av_reduce(&display_aspect_ratio.num, &display_aspect_ratio.den,
+ par->width * (int64_t)st->sample_aspect_ratio.num,
+ par->height * (int64_t)st->sample_aspect_ratio.den,
+ 1024 * 1024);
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH, display_aspect_ratio.num);
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, display_aspect_ratio.den);
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYUNIT, MATROSKA_VIDEO_DISPLAYUNIT_DAR);
+ }
+ }
} else if (display_width_div != 1 || display_height_div != 1) {
put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYWIDTH , par->width / display_width_div);
put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYHEIGHT, par->height / display_height_div);
- }
+ } else
+ put_ebml_uint(pb, MATROSKA_ID_VIDEODISPLAYUNIT, MATROSKA_VIDEO_DISPLAYUNIT_UNKNOWN);
if (par->codec_id == AV_CODEC_ID_RAWVIDEO) {
uint32_t color_space = av_le2ne32(par->codec_tag);
}
if (mkv->mode != MODE_WEBM || par->codec_id != AV_CODEC_ID_WEBVTT) {
+ mkv->tracks[i].codecpriv_offset = avio_tell(pb);
ret = mkv_write_codecprivate(s, pb, par, native_id, qt_id);
if (ret < 0)
return ret;
static int mkv_write_tracks(AVFormatContext *s)
{
MatroskaMuxContext *mkv = s->priv_data;
- AVIOContext *dyn_cp, *pb = s->pb;
- ebml_master tracks;
+ AVIOContext *pb = s->pb;
int i, ret, default_stream_exists = 0;
ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_TRACKS, avio_tell(pb));
if (ret < 0)
return ret;
- ret = start_ebml_master_crc32(pb, &dyn_cp, &tracks, MATROSKA_ID_TRACKS, 0);
+ ret = start_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, &mkv->tracks_master, MATROSKA_ID_TRACKS, 0);
if (ret < 0)
return ret;
default_stream_exists |= st->disposition & AV_DISPOSITION_DEFAULT;
}
for (i = 0; i < s->nb_streams; i++) {
- ret = mkv_write_track(s, mkv, i, dyn_cp, default_stream_exists);
+ ret = mkv_write_track(s, mkv, i, mkv->tracks_bc, default_stream_exists);
if (ret < 0)
return ret;
}
- end_ebml_master_crc32(pb, &dyn_cp, mkv, tracks);
+
+ if (pb->seekable && !mkv->is_live)
+ put_ebml_void(pb, avio_tell(mkv->tracks_bc));
+ else
+ end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, mkv->tracks_master);
+
return 0;
}
ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_CHAPTERS, avio_tell(pb));
if (ret < 0) return ret;
- ret = start_ebml_master_crc32(pb, &dyn_cp, &chapters, MATROSKA_ID_CHAPTERS, 0);
+ ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, &chapters, MATROSKA_ID_CHAPTERS, 0);
if (ret < 0) return ret;
editionentry = start_ebml_master(dyn_cp, MATROSKA_ID_EDITIONENTRY, 0);
ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_TAGS, avio_tell(s->pb));
if (ret < 0) return ret;
- start_ebml_master_crc32(s->pb, &mkv->tags_bc, tags, MATROSKA_ID_TAGS, 0);
+ start_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, tags, MATROSKA_ID_TAGS, 0);
}
pb = mkv->tags_bc;
av_strcasecmp(name, "encoding_tool") &&
av_strcasecmp(name, "duration") &&
(elementid != MATROSKA_ID_TAGTARGETS_TRACKUID ||
- av_strcasecmp(name, "language"));
+ av_strcasecmp(name, "language")) &&
+ (elementid != MATROSKA_ID_TAGTARGETS_ATTACHUID ||
+ (av_strcasecmp(name, "filename") &&
+ av_strcasecmp(name, "mimetype")));
}
static int mkv_write_tag(AVFormatContext *s, AVDictionary *m, unsigned int elementid,
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT)
+ continue;
+
if (!mkv_check_tag(st->metadata, MATROSKA_ID_TAGTARGETS_TRACKUID))
continue;
if (s->pb->seekable && !mkv->is_live) {
for (i = 0; i < s->nb_streams; i++) {
AVIOContext *pb;
+ AVStream *st = s->streams[i];
ebml_master tag_target;
ebml_master tag;
+ if (st->codecpar->codec_type == AVMEDIA_TYPE_ATTACHMENT)
+ continue;
+
mkv_write_tag_targets(s, MATROSKA_ID_TAGTARGETS_TRACKUID, i + 1, &mkv->tags, &tag_target);
pb = mkv->tags_bc;
if (ret < 0) return ret;
}
+ if (mkv->have_attachments) {
+ for (i = 0; i < mkv->attachments->num_entries; i++) {
+ mkv_attachment *attachment = &mkv->attachments->entries[i];
+ AVStream *st = s->streams[attachment->stream_idx];
+
+ if (!mkv_check_tag(st->metadata, MATROSKA_ID_TAGTARGETS_ATTACHUID))
+ continue;
+
+ ret = mkv_write_tag(s, st->metadata, MATROSKA_ID_TAGTARGETS_ATTACHUID, attachment->fileuid, &mkv->tags);
+ if (ret < 0)
+ return ret;
+ }
+ }
+
if (mkv->tags.pos) {
if (s->pb->seekable && !mkv->is_live)
- put_ebml_void(s->pb, avio_tell(mkv->tags_bc) + ((mkv->write_crc && mkv->mode != MODE_WEBM) ? 2 /* ebml id + data size */ + 4 /* CRC32 */ : 0));
+ put_ebml_void(s->pb, avio_tell(mkv->tags_bc));
else
end_ebml_master_crc32(s->pb, &mkv->tags_bc, mkv, mkv->tags);
}
if (!mkv->have_attachments)
return 0;
+ mkv->attachments = av_mallocz(sizeof(*mkv->attachments));
+ if (!mkv->attachments)
+ return AVERROR(ENOMEM);
+
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;
- ret = start_ebml_master_crc32(pb, &dyn_cp, &attachments, MATROSKA_ID_ATTACHMENTS, 0);
+ ret = start_ebml_master_crc32(pb, &dyn_cp, mkv, &attachments, MATROSKA_ID_ATTACHMENTS, 0);
if (ret < 0) return ret;
for (i = 0; i < s->nb_streams; i++) {
AVStream *st = s->streams[i];
ebml_master attached_file;
+ mkv_attachment *attachment = mkv->attachments->entries;
AVDictionaryEntry *t;
const char *mimetype = NULL;
- uint64_t fileuid;
+ uint32_t fileuid;
if (st->codecpar->codec_type != AVMEDIA_TYPE_ATTACHMENT)
continue;
+ attachment = av_realloc_array(attachment, mkv->attachments->num_entries + 1, sizeof(mkv_attachment));
+ if (!attachment)
+ return AVERROR(ENOMEM);
+ mkv->attachments->entries = attachment;
+
attached_file = start_ebml_master(dyn_cp, MATROSKA_ID_ATTACHEDFILE, 0);
if (t = av_dict_get(st->metadata, "title", NULL, 0))
av_sha_update(sha, st->codecpar->extradata, st->codecpar->extradata_size);
av_sha_final(sha, digest);
av_free(sha);
- fileuid = AV_RL64(digest);
+ fileuid = AV_RL32(digest);
} else {
fileuid = av_lfg_get(&c);
}
- av_log(s, AV_LOG_VERBOSE, "Using %.16"PRIx64" for attachment %d\n",
- fileuid, i);
+ av_log(s, AV_LOG_VERBOSE, "Using %.8"PRIx32" for attachment %d\n",
+ fileuid, mkv->attachments->num_entries);
put_ebml_string(dyn_cp, MATROSKA_ID_FILEMIMETYPE, mimetype);
put_ebml_binary(dyn_cp, MATROSKA_ID_FILEDATA, st->codecpar->extradata, st->codecpar->extradata_size);
put_ebml_uint(dyn_cp, MATROSKA_ID_FILEUID, fileuid);
end_ebml_master(dyn_cp, attached_file);
+
+ mkv->attachments->entries[mkv->attachments->num_entries].stream_idx = i;
+ mkv->attachments->entries[mkv->attachments->num_entries++].fileuid = fileuid;
}
end_ebml_master_crc32(pb, &dyn_cp, mkv, attachments);
ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_INFO, avio_tell(pb));
if (ret < 0) goto fail;
- ret = start_ebml_master_crc32(pb, &mkv->info_bc, &mkv->info, MATROSKA_ID_INFO, 0);
+ ret = start_ebml_master_crc32(pb, &mkv->info_bc, mkv, &mkv->info, MATROSKA_ID_INFO, 0);
if (ret < 0)
return ret;
pb = mkv->info_bc;
put_ebml_void(pb, 11); // assumes double-precision float to be written
}
}
- if (s->pb->seekable)
- put_ebml_void(s->pb, avio_tell(pb) + ((mkv->write_crc && mkv->mode != MODE_WEBM) ? 2 /* ebml id + data size */ + 4 /* CRC32 */ : 0));
+ if (s->pb->seekable && !mkv->is_live)
+ put_ebml_void(s->pb, avio_tell(pb));
else
end_ebml_master_crc32(s->pb, &mkv->info_bc, mkv, mkv->info);
pb = s->pb;
if (ret < 0)
goto fail;
- ret = mkv_write_tags(s);
+ ret = mkv_write_attachments(s);
if (ret < 0)
goto fail;
- ret = mkv_write_attachments(s);
+ ret = mkv_write_tags(s);
if (ret < 0)
goto fail;
}
avio_flush(s->pb);
}
+static int mkv_check_new_extra_data(AVFormatContext *s, AVPacket *pkt)
+{
+ MatroskaMuxContext *mkv = s->priv_data;
+ mkv_track *track = &mkv->tracks[pkt->stream_index];
+ AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar;
+ uint8_t *side_data;
+ int side_data_size = 0, ret;
+
+ side_data = av_packet_get_side_data(pkt, AV_PKT_DATA_NEW_EXTRADATA,
+ &side_data_size);
+
+ switch (par->codec_id) {
+ case AV_CODEC_ID_FLAC:
+ if (side_data_size && s->pb->seekable) {
+ AVCodecParameters *codecpriv_par;
+ int64_t curpos;
+ if (side_data_size != par->extradata_size) {
+ av_log(s, AV_LOG_ERROR, "Invalid FLAC STREAMINFO metadata for output stream %d\n",
+ pkt->stream_index);
+ return AVERROR(EINVAL);
+ }
+ codecpriv_par = avcodec_parameters_alloc();
+ if (!codecpriv_par)
+ return AVERROR(ENOMEM);
+ ret = avcodec_parameters_copy(codecpriv_par, par);
+ if (ret < 0) {
+ avcodec_parameters_free(&codecpriv_par);
+ return ret;
+ }
+ memcpy(codecpriv_par->extradata, side_data, side_data_size);
+ curpos = avio_tell(mkv->tracks_bc);
+ avio_seek(mkv->tracks_bc, track->codecpriv_offset, SEEK_SET);
+ mkv_write_codecprivate(s, mkv->tracks_bc, codecpriv_par, 1, 0);
+ avio_seek(mkv->tracks_bc, curpos, SEEK_SET);
+ avcodec_parameters_free(&codecpriv_par);
+ }
+ break;
+ default:
+ if (side_data_size)
+ av_log(s, AV_LOG_DEBUG, "Ignoring new extradata in a packet for stream %d.\n", pkt->stream_index);
+ break;
+ }
+
+ return 0;
+}
+
static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_cue)
{
MatroskaMuxContext *mkv = s->priv_data;
if (mkv->cluster_pos == -1) {
mkv->cluster_pos = avio_tell(s->pb);
- ret = start_ebml_master_crc32(s->pb, &mkv->dyn_bc, &mkv->cluster, MATROSKA_ID_CLUSTER, 0);
+ ret = start_ebml_master_crc32(s->pb, &mkv->dyn_bc, mkv, &mkv->cluster, MATROSKA_ID_CLUSTER, 0);
if (ret < 0)
return ret;
put_ebml_uint(mkv->dyn_bc, MATROSKA_ID_CLUSTERTIMECODE, FFMAX(0, ts));
}
pb = mkv->dyn_bc;
- relative_packet_pos = avio_tell(s->pb) - mkv->cluster.pos + avio_tell(pb);
+ relative_packet_pos = avio_tell(pb);
if (par->codec_type != AVMEDIA_TYPE_SUBTITLE) {
mkv_write_block(s, pb, MATROSKA_ID_SIMPLEBLOCK, pkt, keyframe);
int ret;
int start_new_cluster;
+ ret = mkv_check_new_extra_data(s, pkt);
+ if (ret < 0)
+ return ret;
+
if (mkv->tracks[pkt->stream_index].write_dts)
cluster_time = pkt->dts - mkv->cluster_pts;
else
return ret;
}
- if (pb->seekable) {
+ if (pb->seekable && !mkv->is_live) {
if (mkv->cues->num_entries) {
if (mkv->reserve_cues_space) {
int64_t cues_end;
avio_seek(pb, mkv->info.pos, SEEK_SET);
end_ebml_master_crc32(pb, &mkv->info_bc, mkv, mkv->info);
+ // write tracks master
+ avio_seek(pb, mkv->tracks_master.pos, SEEK_SET);
+ end_ebml_master_crc32(pb, &mkv->tracks_bc, mkv, mkv->tracks_master);
+
// update stream durations
- if (mkv->stream_durations) {
+ if (!mkv->is_live && mkv->stream_durations) {
int i;
+ int64_t curr = avio_tell(mkv->tags_bc);
for (i = 0; i < s->nb_streams; ++i) {
AVStream *st = s->streams[i];
- double duration_sec = mkv->stream_durations[i] * av_q2d(st->time_base);
- char duration_string[20] = "";
- av_log(s, AV_LOG_DEBUG, "stream %d end duration = %" PRIu64 "\n", i,
- mkv->stream_durations[i]);
+ if (mkv->stream_duration_offsets[i] > 0) {
+ double duration_sec = mkv->stream_durations[i] * av_q2d(st->time_base);
+ char duration_string[20] = "";
+
+ av_log(s, AV_LOG_DEBUG, "stream %d end duration = %" PRIu64 "\n", i,
+ mkv->stream_durations[i]);
- if (!mkv->is_live && mkv->stream_duration_offsets[i] > 0) {
avio_seek(mkv->tags_bc, mkv->stream_duration_offsets[i], SEEK_SET);
snprintf(duration_string, 20, "%02d:%02d:%012.9f",
put_ebml_binary(mkv->tags_bc, MATROSKA_ID_TAGSTRING, duration_string, 20);
}
}
+ avio_seek(mkv->tags_bc, curr, SEEK_SET);
}
if (mkv->tags.pos && !mkv->is_live) {
avio_seek(pb, mkv->tags.pos, SEEK_SET);