* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
+#include <stdint.h>
+
#include "avc.h"
+#include "hevc.h"
#include "avformat.h"
#include "avlanguage.h"
#include "flacenc.h"
int have_attachments;
int reserve_cues_space;
+ int cluster_size_limit;
int64_t cues_pos;
+ int64_t cluster_time_limit;
+ int wrote_chapters;
} MatroskaMuxContext;
static int mkv_add_seekhead_entry(mkv_seekhead *seekhead, unsigned int elementid, uint64_t filepos)
{
- mkv_seekhead_entry *entries = seekhead->entries;
+ int err;
// don't store more elements than we reserved space for
if (seekhead->max_entries > 0 && seekhead->max_entries <= seekhead->num_entries)
return -1;
- entries = av_realloc(entries, (seekhead->num_entries + 1) * sizeof(mkv_seekhead_entry));
- if (entries == NULL)
- return AVERROR(ENOMEM);
+ if ((err = av_reallocp_array(&seekhead->entries, seekhead->num_entries + 1,
+ sizeof(*seekhead->entries))) < 0) {
+ seekhead->num_entries = 0;
+ return err;
+ }
- entries[seekhead->num_entries ].elementid = elementid;
- entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset;
+ seekhead->entries[seekhead->num_entries].elementid = elementid;
+ seekhead->entries[seekhead->num_entries++].segmentpos = filepos - seekhead->segment_offset;
- seekhead->entries = entries;
return 0;
}
static int mkv_add_cuepoint(mkv_cues *cues, int stream, int64_t ts, int64_t cluster_pos)
{
- mkv_cuepoint *entries = cues->entries;
+ int err;
if (ts < 0)
return 0;
- entries = av_realloc(entries, (cues->num_entries + 1) * sizeof(mkv_cuepoint));
- if (entries == NULL)
- return AVERROR(ENOMEM);
+ if ((err = av_reallocp_array(&cues->entries, cues->num_entries + 1,
+ sizeof(*cues->entries))) < 0) {
+ cues->num_entries = 0;
+ return err;
+ }
- entries[cues->num_entries ].pts = ts;
- entries[cues->num_entries ].tracknum = stream + 1;
- entries[cues->num_entries++].cluster_pos = cluster_pos - cues->segment_offset;
+ cues->entries[cues->num_entries].pts = ts;
+ cues->entries[cues->num_entries].tracknum = stream + 1;
+ cues->entries[cues->num_entries++].cluster_pos = cluster_pos - cues->segment_offset;
- cues->entries = entries;
return 0;
}
ret = put_wv_codecpriv(dyn_cp, codec);
else if (codec->codec_id == AV_CODEC_ID_H264)
ret = ff_isom_write_avcc(dyn_cp, codec->extradata, codec->extradata_size);
+ else if (codec->codec_id == AV_CODEC_ID_HEVC)
+ ret = ff_isom_write_hvcc(dyn_cp, codec->extradata, codec->extradata_size, 0);
else if (codec->codec_id == AV_CODEC_ID_ALAC) {
if (codec->extradata_size < 36) {
av_log(s, AV_LOG_ERROR,
tag = av_dict_get(st->metadata, "language", NULL, 0);
put_ebml_string(pb, MATROSKA_ID_TRACKLANGUAGE, tag ? tag->value:"und");
- if (st->disposition)
+ // The default value for TRACKFLAGDEFAULT is 1, so add element
+ // if we need to clear it.
+ if (!(st->disposition & AV_DISPOSITION_DEFAULT))
put_ebml_uint(pb, MATROSKA_ID_TRACKFLAGDEFAULT, !!(st->disposition & AV_DISPOSITION_DEFAULT));
// look for a codec ID string specific to mkv to use,
AVRational scale = {1, 1E9};
int i, ret;
- if (!s->nb_chapters)
+ if (!s->nb_chapters || mkv->wrote_chapters)
return 0;
ret = mkv_add_seekhead_entry(mkv->main_seekhead, MATROSKA_ID_CHAPTERS, avio_tell(pb));
}
end_ebml_master(pb, editionentry);
end_ebml_master(pb, chapters);
+
+ mkv->wrote_chapters = 1;
return 0;
}
end_ebml_master(s->pb, targets);
while ((t = av_dict_get(m, "", t, AV_DICT_IGNORE_SUFFIX)))
- if (av_strcasecmp(t->key, "title"))
+ if (av_strcasecmp(t->key, "title") &&
+ av_strcasecmp(t->key, "encoding_tool"))
mkv_write_simpletag(s->pb, t);
end_ebml_master(s->pb, tag);
segment_uid[i] = av_lfg_get(&lfg);
put_ebml_string(pb, MATROSKA_ID_MUXINGAPP , LIBAVFORMAT_IDENT);
- put_ebml_string(pb, MATROSKA_ID_WRITINGAPP, LIBAVFORMAT_IDENT);
+ if ((tag = av_dict_get(s->metadata, "encoding_tool", NULL, 0)))
+ put_ebml_string(pb, MATROSKA_ID_WRITINGAPP, tag->value);
+ else
+ put_ebml_string(pb, MATROSKA_ID_WRITINGAPP, LIBAVFORMAT_IDENT);
put_ebml_binary(pb, MATROSKA_ID_SEGMENTUID, segment_uid, 16);
}
mkv->cur_audio_pkt.size = 0;
avio_flush(pb);
+
+ // start a new cluster every 5 MB or 5 sec, or 32k / 1 sec for streaming or
+ // after 4k and on a keyframe
+ if (pb->seekable) {
+ if (mkv->cluster_time_limit < 0)
+ mkv->cluster_time_limit = 5000;
+ if (mkv->cluster_size_limit < 0)
+ mkv->cluster_size_limit = 5 * 1024 * 1024;
+ } else {
+ if (mkv->cluster_time_limit < 0)
+ mkv->cluster_time_limit = 1000;
+ if (mkv->cluster_size_limit < 0)
+ mkv->cluster_size_limit = 32 * 1024;
+ }
+
return 0;
}
if (codec->codec_id == AV_CODEC_ID_H264 && codec->extradata_size > 0 &&
(AV_RB24(codec->extradata) == 1 || AV_RB32(codec->extradata) == 1))
ff_avc_parse_nal_units_buf(pkt->data, &data, &size);
+ else if (codec->codec_id == AV_CODEC_ID_HEVC && codec->extradata_size > 6 &&
+ (AV_RB24(codec->extradata) == 1 || AV_RB32(codec->extradata) == 1))
+ /* extradata is Annex B, assume the bitstream is too and convert it */
+ ff_hevc_annexb2mp4_buf(pkt->data, &data, &size, 0, NULL);
else if (codec->codec_id == AV_CODEC_ID_WAVPACK) {
int ret = mkv_strip_wavpack(pkt->data, &data, &size);
if (ret < 0) {
int codec_type = s->streams[pkt->stream_index]->codec->codec_type;
int keyframe = !!(pkt->flags & AV_PKT_FLAG_KEY);
int cluster_size;
- int cluster_size_limit;
int64_t cluster_time;
- int64_t cluster_time_limit;
AVIOContext *pb;
int ret;
if (s->pb->seekable) {
pb = s->pb;
cluster_size = avio_tell(pb) - mkv->cluster_pos;
- cluster_time_limit = 5000;
- cluster_size_limit = 5 * 1024 * 1024;
} else {
pb = mkv->dyn_bc;
cluster_size = avio_tell(pb);
- cluster_time_limit = 1000;
- cluster_size_limit = 32 * 1024;
}
if (mkv->cluster_pos &&
- (cluster_size > cluster_size_limit ||
- cluster_time > cluster_time_limit ||
+ (cluster_size > mkv->cluster_size_limit ||
+ cluster_time > mkv->cluster_time_limit ||
(codec_type == AVMEDIA_TYPE_VIDEO && keyframe &&
cluster_size > 4 * 1024))) {
av_log(s, AV_LOG_DEBUG, "Starting new cluster at offset %" PRIu64
mkv->cluster_pos = 0;
if (mkv->dyn_bc)
mkv_flush_dynbuf(s);
+ avio_flush(s->pb);
}
// check if we have an audio packet cached
return ret;
}
+static int mkv_write_flush_packet(AVFormatContext *s, AVPacket *pkt)
+{
+ MatroskaMuxContext *mkv = s->priv_data;
+ AVIOContext *pb;
+ if (s->pb->seekable)
+ pb = s->pb;
+ else
+ pb = mkv->dyn_bc;
+ if (!pkt) {
+ if (mkv->cluster_pos) {
+ av_log(s, AV_LOG_DEBUG, "Flushing cluster at offset %" PRIu64
+ " bytes\n", avio_tell(pb));
+ end_ebml_master(pb, mkv->cluster);
+ mkv->cluster_pos = 0;
+ if (mkv->dyn_bc)
+ mkv_flush_dynbuf(s);
+ avio_flush(s->pb);
+ }
+ return 0;
+ }
+ return mkv_write_packet(s, pkt);
+}
+
static int mkv_write_trailer(AVFormatContext *s)
{
MatroskaMuxContext *mkv = s->priv_data;
end_ebml_master(pb, mkv->cluster);
}
+ if (mkv->mode != MODE_WEBM) {
+ ret = mkv_write_chapters(s);
+ if (ret < 0) return ret;
+ }
+
if (pb->seekable) {
if (mkv->cues->num_entries) {
if (mkv->reserve_cues_space) {
#define OFFSET(x) offsetof(MatroskaMuxContext, x)
#define FLAGS AV_OPT_FLAG_ENCODING_PARAM
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_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 },
{ NULL },
};
.video_codec = CONFIG_LIBX264_ENCODER ?
AV_CODEC_ID_H264 : AV_CODEC_ID_MPEG4,
.write_header = mkv_write_header,
- .write_packet = mkv_write_packet,
+ .write_packet = mkv_write_flush_packet,
.write_trailer = mkv_write_trailer,
.flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
- AVFMT_TS_NONSTRICT,
+ AVFMT_TS_NONSTRICT | AVFMT_ALLOW_FLUSH,
.codec_tag = (const AVCodecTag* const []){
ff_codec_bmp_tags, ff_codec_wav_tags, 0
},
.audio_codec = AV_CODEC_ID_VORBIS,
.video_codec = AV_CODEC_ID_VP8,
.write_header = mkv_write_header,
- .write_packet = mkv_write_packet,
+ .write_packet = mkv_write_flush_packet,
.write_trailer = mkv_write_trailer,
.flags = AVFMT_GLOBALHEADER | AVFMT_VARIABLE_FPS |
- AVFMT_TS_NONSTRICT,
+ AVFMT_TS_NONSTRICT | AVFMT_ALLOW_FLUSH,
.priv_class = &webm_class,
};
#endif
AV_CODEC_ID_VORBIS : AV_CODEC_ID_AC3,
.video_codec = AV_CODEC_ID_NONE,
.write_header = mkv_write_header,
- .write_packet = mkv_write_packet,
+ .write_packet = mkv_write_flush_packet,
.write_trailer = mkv_write_trailer,
- .flags = AVFMT_GLOBALHEADER | AVFMT_TS_NONSTRICT,
+ .flags = AVFMT_GLOBALHEADER | AVFMT_TS_NONSTRICT |
+ AVFMT_ALLOW_FLUSH,
.codec_tag = (const AVCodecTag* const []){ ff_codec_wav_tags, 0 },
.priv_class = &mka_class,
};