Commit 37cef8c7 authored by Vignesh Venkatasubramanian's avatar Vignesh Venkatasubramanian Committed by Michael Niedermayer

libavformat/matroskaenc: Add WebM DASH support

WebM DASH specification [1] requires the Clusters and Cues to be output in a
specific way. Adding a flag to matroskaenc that will enable support for
creating WebM/Mkv files conforming to the WebM DASH specification.

[1] http://wiki.webmproject.org/adaptive-streaming/webm-dash-specificationSigned-off-by: 's avatarVignesh Venkatasubramanian <vigneshv@google.com>
Signed-off-by: 's avatarMichael Niedermayer <michaelni@gmx.at>
parent a996eacc
...@@ -70,6 +70,7 @@ typedef struct mkv_seekhead { ...@@ -70,6 +70,7 @@ typedef struct mkv_seekhead {
typedef struct { typedef struct {
uint64_t pts; uint64_t pts;
int stream_idx;
int tracknum; int tracknum;
int64_t cluster_pos; ///< file offset of the cluster containing the block int64_t cluster_pos; ///< file offset of the cluster containing the block
int64_t relative_pos; ///< relative offset from the position of the cluster containing the block int64_t relative_pos; ///< relative offset from the position of the cluster containing the block
...@@ -114,6 +115,8 @@ typedef struct MatroskaMuxContext { ...@@ -114,6 +115,8 @@ typedef struct MatroskaMuxContext {
int cluster_size_limit; int cluster_size_limit;
int64_t cues_pos; int64_t cues_pos;
int64_t cluster_time_limit; int64_t cluster_time_limit;
int is_dash;
int dash_track_number;
uint32_t chapter_id_offset; uint32_t chapter_id_offset;
int wrote_chapters; int wrote_chapters;
...@@ -399,8 +402,8 @@ static mkv_cues * mkv_start_cues(int64_t segment_offset) ...@@ -399,8 +402,8 @@ static mkv_cues * mkv_start_cues(int64_t segment_offset)
return cues; return cues;
} }
static int mkv_add_cuepoint(mkv_cues *cues, int stream, int64_t ts, int64_t cluster_pos, int64_t relative_pos, static int mkv_add_cuepoint(mkv_cues *cues, int stream, int tracknum, int64_t ts,
int64_t duration) int64_t cluster_pos, int64_t relative_pos, int64_t duration)
{ {
mkv_cuepoint *entries = cues->entries; mkv_cuepoint *entries = cues->entries;
...@@ -413,7 +416,8 @@ static int mkv_add_cuepoint(mkv_cues *cues, int stream, int64_t ts, int64_t clus ...@@ -413,7 +416,8 @@ static int mkv_add_cuepoint(mkv_cues *cues, int stream, int64_t ts, int64_t clus
cues->entries = entries; cues->entries = entries;
cues->entries[cues->num_entries].pts = ts; cues->entries[cues->num_entries].pts = ts;
cues->entries[cues->num_entries].tracknum = stream + 1; cues->entries[cues->num_entries].stream_idx = stream;
cues->entries[cues->num_entries].tracknum = tracknum;
cues->entries[cues->num_entries].cluster_pos = cluster_pos - cues->segment_offset; cues->entries[cues->num_entries].cluster_pos = cluster_pos - cues->segment_offset;
cues->entries[cues->num_entries].relative_pos = relative_pos; cues->entries[cues->num_entries].relative_pos = relative_pos;
cues->entries[cues->num_entries++].duration = duration; cues->entries[cues->num_entries++].duration = duration;
...@@ -443,7 +447,7 @@ static int64_t mkv_write_cues(AVIOContext *pb, mkv_cues *cues, mkv_track *tracks ...@@ -443,7 +447,7 @@ static int64_t mkv_write_cues(AVIOContext *pb, mkv_cues *cues, mkv_track *tracks
for (j = 0; j < num_tracks; j++) for (j = 0; j < num_tracks; j++)
tracks[j].has_cue = 0; tracks[j].has_cue = 0;
for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) { for (j = 0; j < cues->num_entries - i && entry[j].pts == pts; j++) {
int tracknum = entry[j].tracknum - 1; int tracknum = entry[j].stream_idx;
av_assert0(tracknum>=0 && tracknum<num_tracks); av_assert0(tracknum>=0 && tracknum<num_tracks);
if (tracks[tracknum].has_cue) if (tracks[tracknum].has_cue)
continue; continue;
...@@ -646,8 +650,10 @@ static int mkv_write_tracks(AVFormatContext *s) ...@@ -646,8 +650,10 @@ static int mkv_write_tracks(AVFormatContext *s)
get_aac_sample_rates(s, codec, &sample_rate, &output_sample_rate); get_aac_sample_rates(s, codec, &sample_rate, &output_sample_rate);
track = start_ebml_master(pb, MATROSKA_ID_TRACKENTRY, 0); track = start_ebml_master(pb, MATROSKA_ID_TRACKENTRY, 0);
put_ebml_uint (pb, MATROSKA_ID_TRACKNUMBER , i + 1); put_ebml_uint (pb, MATROSKA_ID_TRACKNUMBER,
put_ebml_uint (pb, MATROSKA_ID_TRACKUID , i + 1); mkv->is_dash ? mkv->dash_track_number : i + 1);
put_ebml_uint (pb, MATROSKA_ID_TRACKUID,
mkv->is_dash ? mkv->dash_track_number : i + 1);
put_ebml_uint (pb, MATROSKA_ID_TRACKFLAGLACING , 0); // no lacing (yet) put_ebml_uint (pb, MATROSKA_ID_TRACKFLAGLACING , 0); // no lacing (yet)
if ((tag = av_dict_get(st->metadata, "title", NULL, 0))) if ((tag = av_dict_get(st->metadata, "title", NULL, 0)))
...@@ -1435,7 +1441,8 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb, ...@@ -1435,7 +1441,8 @@ static void mkv_write_block(AVFormatContext *s, AVIOContext *pb,
put_ebml_id(pb, blockid); put_ebml_id(pb, blockid);
put_ebml_num(pb, size+4, 0); put_ebml_num(pb, size+4, 0);
avio_w8(pb, 0x80 | (pkt->stream_index + 1)); // this assumes stream_index is less than 126 // this assumes stream_index is less than 126
avio_w8(pb, 0x80 | (mkv->is_dash ? mkv->dash_track_number : (pkt->stream_index + 1)));
avio_wb16(pb, ts - mkv->cluster_pts); avio_wb16(pb, ts - mkv->cluster_pts);
avio_w8(pb, flags); avio_w8(pb, flags);
avio_write(pb, data + offset, size); avio_write(pb, data + offset, size);
...@@ -1549,7 +1556,7 @@ static void mkv_flush_dynbuf(AVFormatContext *s) ...@@ -1549,7 +1556,7 @@ static void mkv_flush_dynbuf(AVFormatContext *s)
mkv->dyn_bc = NULL; mkv->dyn_bc = NULL;
} }
static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt, int add_cue)
{ {
MatroskaMuxContext *mkv = s->priv_data; MatroskaMuxContext *mkv = s->priv_data;
AVIOContext *pb = s->pb; AVIOContext *pb = s->pb;
...@@ -1606,8 +1613,13 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt) ...@@ -1606,8 +1613,13 @@ static int mkv_write_packet_internal(AVFormatContext *s, AVPacket *pkt)
end_ebml_master(pb, blockgroup); end_ebml_master(pb, blockgroup);
} }
if ((codec->codec_type == AVMEDIA_TYPE_VIDEO && keyframe) || codec->codec_type == AVMEDIA_TYPE_SUBTITLE) { if ((codec->codec_type == AVMEDIA_TYPE_VIDEO && keyframe) ||
ret = mkv_add_cuepoint(mkv->cues, pkt->stream_index, ts, mkv->cluster_pos, relative_packet_pos, codec->codec_type == AVMEDIA_TYPE_SUBTITLE ||
add_cue) {
ret = mkv_add_cuepoint(mkv->cues,
pkt->stream_index,
mkv->is_dash ? mkv->dash_track_number : pkt->stream_index + 1,
ts, mkv->cluster_pos, relative_packet_pos,
codec->codec_type == AVMEDIA_TYPE_SUBTITLE ? duration : -1); codec->codec_type == AVMEDIA_TYPE_SUBTITLE ? duration : -1);
if (ret < 0) return ret; if (ret < 0) return ret;
} }
...@@ -1625,6 +1637,7 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1625,6 +1637,7 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt)
int64_t cluster_time; int64_t cluster_time;
AVIOContext *pb; AVIOContext *pb;
int ret; int ret;
int start_new_cluster;
if (mkv->tracks[pkt->stream_index].write_dts) if (mkv->tracks[pkt->stream_index].write_dts)
cluster_time = pkt->dts - mkv->cluster_pts; cluster_time = pkt->dts - mkv->cluster_pts;
...@@ -1642,11 +1655,26 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1642,11 +1655,26 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt)
cluster_size = avio_tell(pb); cluster_size = avio_tell(pb);
} }
if (mkv->cluster_pos != -1 && if (mkv->is_dash && codec_type == AVMEDIA_TYPE_VIDEO) {
(cluster_size > mkv->cluster_size_limit || // WebM DASH specification states that the first block of every cluster
cluster_time > mkv->cluster_time_limit || // has to be a key frame. So for DASH video, we only create a cluster
(codec_type == AVMEDIA_TYPE_VIDEO && keyframe && // on seeing key frames.
cluster_size > 4 * 1024))) { start_new_cluster = keyframe;
} else if (mkv->is_dash && codec_type == AVMEDIA_TYPE_AUDIO &&
cluster_time > mkv->cluster_time_limit) {
// For DASH audio, we create a Cluster based on cluster_time_limit
start_new_cluster = 1;
} else if (!mkv->is_dash &&
(cluster_size > mkv->cluster_size_limit ||
cluster_time > mkv->cluster_time_limit ||
(codec_type == AVMEDIA_TYPE_VIDEO && keyframe &&
cluster_size > 4 * 1024))) {
start_new_cluster = 1;
} else {
start_new_cluster = 0;
}
if (mkv->cluster_pos != -1 && start_new_cluster) {
av_log(s, AV_LOG_DEBUG, "Starting new cluster at offset %" PRIu64 av_log(s, AV_LOG_DEBUG, "Starting new cluster at offset %" PRIu64
" bytes, pts %" PRIu64 "dts %" PRIu64 "\n", " bytes, pts %" PRIu64 "dts %" PRIu64 "\n",
avio_tell(pb), pkt->pts, pkt->dts); avio_tell(pb), pkt->pts, pkt->dts);
...@@ -1659,7 +1687,9 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1659,7 +1687,9 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt)
// check if we have an audio packet cached // check if we have an audio packet cached
if (mkv->cur_audio_pkt.size > 0) { if (mkv->cur_audio_pkt.size > 0) {
ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt); // for DASH audio, a CuePoint has to be added when there is a new cluster.
ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt,
mkv->is_dash ? start_new_cluster : 0);
av_free_packet(&mkv->cur_audio_pkt); av_free_packet(&mkv->cur_audio_pkt);
if (ret < 0) { if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Could not write cached audio packet ret:%d\n", ret); av_log(s, AV_LOG_ERROR, "Could not write cached audio packet ret:%d\n", ret);
...@@ -1680,7 +1710,7 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt) ...@@ -1680,7 +1710,7 @@ static int mkv_write_packet(AVFormatContext *s, AVPacket *pkt)
ret = av_copy_packet_side_data(&mkv->cur_audio_pkt, &mkv->cur_audio_pkt); ret = av_copy_packet_side_data(&mkv->cur_audio_pkt, &mkv->cur_audio_pkt);
} }
} else } else
ret = mkv_write_packet_internal(s, pkt); ret = mkv_write_packet_internal(s, pkt, 0);
return ret; return ret;
} }
...@@ -1716,7 +1746,7 @@ static int mkv_write_trailer(AVFormatContext *s) ...@@ -1716,7 +1746,7 @@ static int mkv_write_trailer(AVFormatContext *s)
// check if we have an audio packet cached // check if we have an audio packet cached
if (mkv->cur_audio_pkt.size > 0) { if (mkv->cur_audio_pkt.size > 0) {
ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt); ret = mkv_write_packet_internal(s, &mkv->cur_audio_pkt, 0);
av_free_packet(&mkv->cur_audio_pkt); av_free_packet(&mkv->cur_audio_pkt);
if (ret < 0) { if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Could not write cached audio packet ret:%d\n", ret); av_log(s, AV_LOG_ERROR, "Could not write cached audio packet ret:%d\n", ret);
...@@ -1831,6 +1861,8 @@ static const AVOption options[] = { ...@@ -1831,6 +1861,8 @@ static const AVOption options[] = {
{ "reserve_index_space", "Reserve a given amount of space (in bytes) at the beginning of the file for the index (cues).", OFFSET(reserve_cues_space), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, { "reserve_index_space", "Reserve a given amount of space (in bytes) at the beginning of the file for the index (cues).", OFFSET(reserve_cues_space), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS },
{ "cluster_size_limit", "Store at most the provided amount of bytes in a cluster. ", OFFSET(cluster_size_limit), AV_OPT_TYPE_INT , { .i64 = -1 }, -1, INT_MAX, FLAGS }, { "cluster_size_limit", "Store at most the provided amount of bytes in a cluster. ", OFFSET(cluster_size_limit), AV_OPT_TYPE_INT , { .i64 = -1 }, -1, INT_MAX, FLAGS },
{ "cluster_time_limit", "Store at most the provided number of milliseconds in a cluster.", OFFSET(cluster_time_limit), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, FLAGS }, { "cluster_time_limit", "Store at most the provided number of milliseconds in a cluster.", OFFSET(cluster_time_limit), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, FLAGS },
{ "dash", "Create a WebM file conforming to WebM DASH specification", OFFSET(is_dash), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS },
{ "dash_track_number", "Track number for the DASH stream", OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 127, FLAGS },
{ NULL }, { NULL },
}; };
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment