summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/ffmpeg/A05-mkv-chapters.patch57
-rw-r--r--contrib/ffmpeg/A06-mkv-default-subtitle.patch30
-rw-r--r--contrib/ffmpeg/A07-mp4-chapters.patch60
-rw-r--r--contrib/ffmpeg/A08-mp4-faststart.patch301
-rw-r--r--contrib/ffmpeg/A09-mp4-64bit.patch46
-rw-r--r--contrib/ffmpeg/A10-mp4-track-enable.patch142
-rw-r--r--contrib/ffmpeg/A11-mp4-chapter-properties.patch47
-rw-r--r--contrib/ffmpeg/module.defs10
-rw-r--r--gtk/configure.ac18
-rw-r--r--gtk/module.defs8
-rw-r--r--gtk/src/audiohandler.c2
-rw-r--r--gtk/src/callbacks.c43
-rw-r--r--gtk/src/ghb.ui2
-rw-r--r--gtk/src/hb-backend.c12
-rw-r--r--gtk/src/makedeps.py4
-rw-r--r--gtk/src/queuehandler.c2
-rw-r--r--libhb/common.c99
-rw-r--r--libhb/common.h8
-rw-r--r--libhb/deccc608sub.c2
-rw-r--r--libhb/decmetadata.c7
-rw-r--r--libhb/decpgssub.c3
-rw-r--r--libhb/decssasub.c22
-rw-r--r--libhb/dectx3gsub.c1
-rw-r--r--libhb/decutf8sub.c2
-rw-r--r--libhb/decvobsub.c2
-rw-r--r--libhb/encavcodec.c22
-rw-r--r--libhb/encavcodecaudio.c22
-rw-r--r--libhb/encfaac.c11
-rw-r--r--libhb/enclame.c7
-rw-r--r--libhb/enctheora.c30
-rw-r--r--libhb/encvorbis.c15
-rw-r--r--libhb/encx264.c6
-rw-r--r--libhb/fifo.c6
-rw-r--r--libhb/internal.h23
-rw-r--r--libhb/module.defs19
-rw-r--r--libhb/muxavformat.c1239
-rw-r--r--libhb/muxcommon.c256
-rw-r--r--libhb/muxmkv.c58
-rw-r--r--libhb/muxmp4.c348
-rw-r--r--libhb/sync.c52
-rw-r--r--libhb/work.c1
-rw-r--r--macosx/Controller.m7
-rw-r--r--macosx/HandBrake.xcodeproj/project.pbxproj12
-rw-r--r--macosx/module.defs8
-rw-r--r--make/configure.py15
-rw-r--r--make/include/main.defs10
-rw-r--r--test/module.defs10
-rw-r--r--test/test.c2
48 files changed, 2600 insertions, 509 deletions
diff --git a/contrib/ffmpeg/A05-mkv-chapters.patch b/contrib/ffmpeg/A05-mkv-chapters.patch
new file mode 100644
index 000000000..f4082be1d
--- /dev/null
+++ b/contrib/ffmpeg/A05-mkv-chapters.patch
@@ -0,0 +1,57 @@
+From c4d83dfad34d69a86bf04ded97ae3fda087bfa25 Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 08:45:55 -0700
+Subject: [PATCH 1/9] matroskaenc: Allow chapters to be written in trailer
+
+This allows creation of frame accurate chapter marks from sources like
+DVD and BD where the precise chapter location is not known until the
+chapter mark has been reached during reading.
+---
+ libavformat/matroskaenc.c | 10 +++++++++-
+ 1 file changed, 9 insertions(+), 1 deletion(-)
+
+diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
+index b37d10c..99de151 100644
+--- a/libavformat/matroskaenc.c
++++ b/libavformat/matroskaenc.c
+@@ -94,6 +94,7 @@ typedef struct MatroskaMuxContext {
+ AVPacket cur_audio_pkt;
+
+ int have_attachments;
++ int wrote_chapters;
+ } MatroskaMuxContext;
+
+
+@@ -688,7 +689,7 @@ static int mkv_write_chapters(AVFormatContext *s)
+ 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));
+@@ -721,6 +722,8 @@ static int mkv_write_chapters(AVFormatContext *s)
+ }
+ end_ebml_master(pb, editionentry);
+ end_ebml_master(pb, chapters);
++
++ mkv->wrote_chapters = 1;
+ return 0;
+ }
+
+@@ -1259,6 +1262,11 @@ static int mkv_write_trailer(AVFormatContext *s)
+ 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) {
+ cuespos = mkv_write_cues(pb, mkv->cues, s->nb_streams);
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A06-mkv-default-subtitle.patch b/contrib/ffmpeg/A06-mkv-default-subtitle.patch
new file mode 100644
index 000000000..9074a3443
--- /dev/null
+++ b/contrib/ffmpeg/A06-mkv-default-subtitle.patch
@@ -0,0 +1,30 @@
+From f1febbdddc573c7684e1761abfe568ebfcfcdcd1 Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 08:49:58 -0700
+Subject: [PATCH 3/9] matroskaenc: Fix writing TRACKDEFAULTFLAG
+
+The element was only being written when the value == 1. But the default
+value of this element is 1, so this has no useful effect. This element
+needs to be written when the value == 0.
+---
+ libavformat/matroskaenc.c | 4 +++-
+ 1 file changed, 3 insertions(+), 1 deletion(-)
+
+diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c
+index 4858af5..358ba66 100644
+--- a/libavformat/matroskaenc.c
++++ b/libavformat/matroskaenc.c
+@@ -568,7 +568,9 @@ static int mkv_write_tracks(AVFormatContext *s)
+ 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,
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A07-mp4-chapters.patch b/contrib/ffmpeg/A07-mp4-chapters.patch
new file mode 100644
index 000000000..0fec0eaf9
--- /dev/null
+++ b/contrib/ffmpeg/A07-mp4-chapters.patch
@@ -0,0 +1,60 @@
+From 36bdd52964ef68532eea50a81431c0c4c9f329e5 Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 08:51:46 -0700
+Subject: [PATCH 4/9] movenc: Allow chapters to be written in trailer
+
+This allows creation of frame accurate chapter marks from sources
+like DVD and BD where the precise chapter location is not known until
+the chapter mark has been reached during reading.
+---
+ libavformat/movenc.c | 19 ++++++++++++++++---
+ 1 file changed, 16 insertions(+), 3 deletions(-)
+
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index 496158c..f41f8d7 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -3038,7 +3038,7 @@ static int mov_write_header(AVFormatContext *s)
+ }
+
+ mov->nb_streams = s->nb_streams;
+- if (mov->mode & (MODE_MOV|MODE_IPOD) && s->nb_chapters)
++ if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters)
+ mov->chapter_track = mov->nb_streams++;
+
+ if (mov->flags & FF_MOV_FLAG_RTP_HINT) {
+@@ -3053,7 +3053,9 @@ static int mov_write_header(AVFormatContext *s)
+ }
+ }
+
+- mov->tracks = av_mallocz(mov->nb_streams*sizeof(*mov->tracks));
++ // Reserve an extra stream for chapters for the case where chapters
++ // are written in the trailer
++ mov->tracks = av_mallocz((mov->nb_streams+1)*sizeof(*mov->tracks));
+ if (!mov->tracks)
+ return AVERROR(ENOMEM);
+
+@@ -3189,8 +3191,19 @@ static int mov_write_trailer(AVFormatContext *s)
+ AVIOContext *pb = s->pb;
+ int res = 0;
+ int i;
++ int64_t moov_pos;
++
++ // If there were no chapters when the header was written, but there
++ // are chapters now, write them in the trailer. This only works
++ // when we are not doing fragments.
++ if (!mov->chapter_track && !(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
++ if (mov->mode & (MODE_MP4|MODE_MOV|MODE_IPOD) && s->nb_chapters) {
++ mov->chapter_track = mov->nb_streams++;
++ mov_create_chapter_track(s, mov->chapter_track);
++ }
++ }
+
+- int64_t moov_pos = avio_tell(pb);
++ moov_pos = avio_tell(pb);
+
+ if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
+ /* Write size of mdat tag */
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A08-mp4-faststart.patch b/contrib/ffmpeg/A08-mp4-faststart.patch
new file mode 100644
index 000000000..c4d8d706b
--- /dev/null
+++ b/contrib/ffmpeg/A08-mp4-faststart.patch
@@ -0,0 +1,301 @@
+From 650a3dbf0cb6e38011bd6ea3ccec7148c7e9adb9 Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 09:01:45 -0700
+Subject: [PATCH 5/9] movenc: allow placing moov atom at beginning of file
+
+Adds option to reserve space for moov.
+---
+ libavformat/movenc.c | 22 ++++++++++++++++++++--
+ libavformat/movenc.h | 3 +++
+ 2 files changed, 23 insertions(+), 2 deletions(-)
+
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index f41f8d7..684f8d6 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -51,6 +51,7 @@ static const AVOption options[] = {
+ { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+ { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+ { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
++ { "moov_size", "maximum moov size so it can be placed at the begin", offsetof(MOVMuxContext, reserved_moov_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, 0 },
+ FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags),
+ { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
+ { "iods_audio_profile", "iods audio profile atom.", offsetof(MOVMuxContext, iods_audio_profile), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 255, AV_OPT_FLAG_ENCODING_PARAM},
+@@ -3146,8 +3147,13 @@ static int mov_write_header(AVFormatContext *s)
+ FF_MOV_FLAG_FRAGMENT;
+ }
+
+- if (!(mov->flags & FF_MOV_FLAG_FRAGMENT))
++ if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
++ if(mov->reserved_moov_size) {
++ mov->reserved_moov_pos= avio_tell(pb);
++ avio_skip(pb, mov->reserved_moov_size);
++ }
+ mov_write_mdat_tag(pb, mov);
++ }
+
+ if (t = av_dict_get(s->metadata, "creation_time", NULL, 0))
+ mov->time = ff_iso8601_to_unix_time(t->value);
+@@ -3219,9 +3225,21 @@ static int mov_write_trailer(AVFormatContext *s)
+ ffio_wfourcc(pb, "mdat");
+ avio_wb64(pb, mov->mdat_size + 16);
+ }
+- avio_seek(pb, moov_pos, SEEK_SET);
++ avio_seek(pb, mov->reserved_moov_size ? mov->reserved_moov_pos : moov_pos, SEEK_SET);
+
+ mov_write_moov_tag(pb, mov, s);
++ if (mov->reserved_moov_size) {
++ int64_t size= mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_moov_pos);
++ if(size < 8){
++ av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size);
++ return -1;
++ }
++ avio_wb32(pb, size);
++ ffio_wfourcc(pb, "free");
++ for(i=0; i<size; i++)
++ avio_w8(pb, 0);
++ avio_seek(pb, moov_pos, SEEK_SET);
++ }
+ } else {
+ mov_flush_fragment(s);
+ mov_write_mfra_tag(pb, mov);
+diff --git a/libavformat/movenc.h b/libavformat/movenc.h
+index e20ef14..f91fc5f 100644
+--- a/libavformat/movenc.h
++++ b/libavformat/movenc.h
+@@ -153,6 +153,9 @@ typedef struct MOVMuxContext {
+ int max_fragment_size;
+ int ism_lookahead;
+ AVIOContext *mdat_buf;
++
++ int reserved_moov_size;
++ int64_t reserved_moov_pos;
+ } MOVMuxContext;
+
+ #define FF_MOV_FLAG_RTP_HINT 1
+--
+1.8.1.4
+
+From 256ce14e2492ccdcb987afd6fb66ef590e58f02e Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 09:11:32 -0700
+Subject: [PATCH 6/9] movenc: add faststart option for web streaming
+
+Faststart moves moov atom to beginning of file and rewrites the reset of
+the file after muxing is complete.
+---
+ libavformat/movenc.c | 140 +++++++++++++++++++++++++++++++++++++++++++++++++--
+ libavformat/movenc.h | 3 +-
+ 2 files changed, 137 insertions(+), 6 deletions(-)
+
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index 684f8d6..7348cf8 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -51,6 +51,7 @@ static const AVOption options[] = {
+ { "separate_moof", "Write separate moof/mdat atoms for each track", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_SEPARATE_MOOF}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+ { "frag_custom", "Flush fragments on caller requests", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FRAG_CUSTOM}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+ { "isml", "Create a live smooth streaming feed (for pushing to a publishing point)", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_ISML}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
++ { "faststart", "Run a second pass to put the moov at the beginning of the file", 0, AV_OPT_TYPE_CONST, {.i64 = FF_MOV_FLAG_FASTSTART}, INT_MIN, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, "movflags" },
+ { "moov_size", "maximum moov size so it can be placed at the begin", offsetof(MOVMuxContext, reserved_moov_size), AV_OPT_TYPE_INT, {.dbl = 0}, 0, INT_MAX, AV_OPT_FLAG_ENCODING_PARAM, 0 },
+ FF_RTP_FLAG_OPTS(MOVMuxContext, rtp_flags),
+ { "skip_iods", "Skip writing iods atom.", offsetof(MOVMuxContext, iods_skip), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, AV_OPT_FLAG_ENCODING_PARAM},
+@@ -3007,6 +3008,14 @@ static int mov_write_header(AVFormatContext *s)
+ FF_MOV_FLAG_FRAG_CUSTOM))
+ mov->flags |= FF_MOV_FLAG_FRAGMENT;
+
++ /* faststart: moov at the beginning of the file, if supported */
++ if (mov->flags & FF_MOV_FLAG_FASTSTART) {
++ if (mov->flags & FF_MOV_FLAG_FRAGMENT)
++ mov->flags &= ~FF_MOV_FLAG_FASTSTART;
++ else
++ mov->reserved_moov_size = -1;
++ }
++
+ /* Non-seekable output is ok if using fragmentation. If ism_lookahead
+ * is enabled, we don't support non-seekable output at all. */
+ if (!s->pb->seekable &&
+@@ -3150,7 +3159,8 @@ static int mov_write_header(AVFormatContext *s)
+ if (!(mov->flags & FF_MOV_FLAG_FRAGMENT)) {
+ if(mov->reserved_moov_size) {
+ mov->reserved_moov_pos= avio_tell(pb);
+- avio_skip(pb, mov->reserved_moov_size);
++ if(mov->reserved_moov_size > 0)
++ avio_skip(pb, mov->reserved_moov_size);
+ }
+ mov_write_mdat_tag(pb, mov);
+ }
+@@ -3191,6 +3201,115 @@ static int mov_write_header(AVFormatContext *s)
+ return -1;
+ }
+
++static int get_moov_size(AVFormatContext *s)
++{
++ int ret;
++ uint8_t *buf;
++ AVIOContext *moov_buf;
++ MOVMuxContext *mov = s->priv_data;
++
++ if ((ret = avio_open_dyn_buf(&moov_buf)) < 0)
++ return ret;
++ mov_write_moov_tag(moov_buf, mov, s);
++ ret = avio_close_dyn_buf(moov_buf, &buf);
++ av_free(buf);
++ return ret;
++}
++
++/**
++ * This function gets the moov size if moved to the top of the file: the chunk
++ * offset table can switch between stco (32-bit entries) to co64 (64-bit
++ * entries) when the moov is moved to the top, so the size of the moov would
++ * change. It also updates the chunk offset tables.
++ */
++static int compute_moov_size(AVFormatContext *s)
++{
++ int i, moov_size, moov_size2;
++ MOVMuxContext *mov = s->priv_data;
++
++ moov_size = get_moov_size(s);
++ if (moov_size < 0)
++ return moov_size;
++
++ for (i = 0; i < mov->nb_streams; i++)
++ mov->tracks[i].data_offset += moov_size;
++
++ moov_size2 = get_moov_size(s);
++ if (moov_size2 < 0)
++ return moov_size2;
++
++ /* if the size changed, we just switched from stco to co64 and needs to
++ * update the offsets */
++ if (moov_size2 != moov_size)
++ for (i = 0; i < mov->nb_streams; i++)
++ mov->tracks[i].data_offset += moov_size2 - moov_size;
++
++ return moov_size2;
++}
++
++static int shift_data(AVFormatContext *s)
++{
++ int ret = 0, moov_size;
++ MOVMuxContext *mov = s->priv_data;
++ int64_t pos, pos_end = avio_tell(s->pb);
++ uint8_t *buf, *read_buf[2];
++ int read_buf_id = 0;
++ int read_size[2];
++ AVIOContext *read_pb;
++
++ moov_size = compute_moov_size(s);
++ if (moov_size < 0)
++ return moov_size;
++
++ buf = av_malloc(moov_size * 2);
++ if (!buf)
++ return AVERROR(ENOMEM);
++ read_buf[0] = buf;
++ read_buf[1] = buf + moov_size;
++
++ /* Shift the data: the AVIO context of the output can only be used for
++ * writing, so we re-open the same output, but for reading. It also avoids
++ * a read/seek/write/seek back and forth. */
++ avio_flush(s->pb);
++ ret = avio_open(&read_pb, s->filename, AVIO_FLAG_READ);
++ if (ret < 0) {
++ av_log(s, AV_LOG_ERROR, "Unable to re-open %s output file for "
++ "the second pass (faststart)\n", s->filename);
++ goto end;
++ }
++
++ /* mark the end of the shift to up to the last data we wrote, and get ready
++ * for writing */
++ pos_end = avio_tell(s->pb);
++ avio_seek(s->pb, mov->reserved_moov_pos + moov_size, SEEK_SET);
++
++ /* start reading at where the new moov will be placed */
++ avio_seek(read_pb, mov->reserved_moov_pos, SEEK_SET);
++ pos = avio_tell(read_pb);
++
++#define READ_BLOCK do { \
++ read_size[read_buf_id] = avio_read(read_pb, read_buf[read_buf_id], moov_size); \
++ read_buf_id ^= 1; \
++} while (0)
++
++ /* shift data by chunk of at most moov_size */
++ READ_BLOCK;
++ do {
++ int n;
++ READ_BLOCK;
++ n = read_size[read_buf_id];
++ if (n <= 0)
++ break;
++ avio_write(s->pb, read_buf[read_buf_id], n);
++ pos += n;
++ } while (pos < pos_end);
++ avio_close(read_pb);
++
++end:
++ av_free(buf);
++ return ret;
++}
++
+ static int mov_write_trailer(AVFormatContext *s)
+ {
+ MOVMuxContext *mov = s->priv_data;
+@@ -3225,11 +3344,20 @@ static int mov_write_trailer(AVFormatContext *s)
+ ffio_wfourcc(pb, "mdat");
+ avio_wb64(pb, mov->mdat_size + 16);
+ }
+- avio_seek(pb, mov->reserved_moov_size ? mov->reserved_moov_pos : moov_pos, SEEK_SET);
+
+- mov_write_moov_tag(pb, mov, s);
+- if (mov->reserved_moov_size) {
+- int64_t size= mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_moov_pos);
++ avio_seek(pb, mov->reserved_moov_size > 0 ? mov->reserved_moov_pos : moov_pos, SEEK_SET);
++
++ if (mov->reserved_moov_size == -1) {
++ av_log(s, AV_LOG_INFO, "Starting second pass: moving header on top of the file\n");
++ res = shift_data(s);
++ if (res == 0) {
++ avio_seek(s->pb, mov->reserved_moov_pos, SEEK_SET);
++ mov_write_moov_tag(pb, mov, s);
++ }
++ } else if (mov->reserved_moov_size > 0) {
++ int64_t size;
++ mov_write_moov_tag(pb, mov, s);
++ size = mov->reserved_moov_size - (avio_tell(pb) - mov->reserved_moov_pos);
+ if(size < 8){
+ av_log(s, AV_LOG_ERROR, "reserved_moov_size is too small, needed %"PRId64" additional\n", 8-size);
+ return -1;
+@@ -3239,6 +3367,8 @@ static int mov_write_trailer(AVFormatContext *s)
+ for(i=0; i<size; i++)
+ avio_w8(pb, 0);
+ avio_seek(pb, moov_pos, SEEK_SET);
++ } else {
++ mov_write_moov_tag(pb, mov, s);
+ }
+ } else {
+ mov_flush_fragment(s);
+diff --git a/libavformat/movenc.h b/libavformat/movenc.h
+index f91fc5f..3ea0dd5 100644
+--- a/libavformat/movenc.h
++++ b/libavformat/movenc.h
+@@ -154,7 +154,7 @@ typedef struct MOVMuxContext {
+ int ism_lookahead;
+ AVIOContext *mdat_buf;
+
+- int reserved_moov_size;
++ int reserved_moov_size; ///< 0 for disabled, -1 for automatic, size otherwise
+ int64_t reserved_moov_pos;
+ } MOVMuxContext;
+
+@@ -165,6 +165,7 @@ typedef struct MOVMuxContext {
+ #define FF_MOV_FLAG_SEPARATE_MOOF 16
+ #define FF_MOV_FLAG_FRAG_CUSTOM 32
+ #define FF_MOV_FLAG_ISML 64
++#define FF_MOV_FLAG_FASTSTART 128
+
+ int ff_mov_write_packet(AVFormatContext *s, AVPacket *pkt);
+
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A09-mp4-64bit.patch b/contrib/ffmpeg/A09-mp4-64bit.patch
new file mode 100644
index 000000000..04fec6032
--- /dev/null
+++ b/contrib/ffmpeg/A09-mp4-64bit.patch
@@ -0,0 +1,46 @@
+From 68f89a1ec9ccef22268158640c1f62f0370f48ba Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 09:12:54 -0700
+Subject: [PATCH 7/9] movenc: fix detection of 64bit offset requirement
+
+The old method doesn't work when moov is relocated to beginning of file
+---
+ libavformat/movenc.c | 16 +++++++++++++---
+ 1 file changed, 13 insertions(+), 3 deletions(-)
+
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index 7348cf8..2f6c003 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -83,15 +83,25 @@ static int64_t update_size(AVIOContext *pb, int64_t pos)
+ return curpos - pos;
+ }
+
++static int is_co64_required(const MOVTrack *track)
++{
++ int i;
++
++ for (i = 0; i < track->entry; i++) {
++ if (track->cluster[i].pos + track->data_offset > UINT32_MAX)
++ return 1;
++ }
++ return 0;
++}
++
+ /* Chunk offset atom */
+ static int mov_write_stco_tag(AVIOContext *pb, MOVTrack *track)
+ {
+ int i;
+- int mode64 = 0; // use 32 bit size variant if possible
++ int mode64 = is_co64_required(track); // use 32 bit size variant if possible
+ int64_t pos = avio_tell(pb);
+ avio_wb32(pb, 0); /* size */
+- if (pos > UINT32_MAX) {
+- mode64 = 1;
++ if (mode64) {
+ ffio_wfourcc(pb, "co64");
+ } else
+ ffio_wfourcc(pb, "stco");
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A10-mp4-track-enable.patch b/contrib/ffmpeg/A10-mp4-track-enable.patch
new file mode 100644
index 000000000..282326157
--- /dev/null
+++ b/contrib/ffmpeg/A10-mp4-track-enable.patch
@@ -0,0 +1,142 @@
+From 25a0ee27bb5d2bb46781ea4a2a3c88581ad75fde Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 09:16:08 -0700
+Subject: [PATCH 8/9] movenc: Make tkhd "enabled" flag QuickTime compatible
+
+QuickTime will play multiple audio tracks concurrently if this flag is
+set for multiple audio tracks. And if no subtitle track has this flag
+set QuickTime will show no subtitles in the subtitle menu.
+---
+ libavformat/mov.c | 4 +++-
+ libavformat/movenc.c | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++-
+ libavformat/movenc.h | 1 +
+ 3 files changed, 63 insertions(+), 2 deletions(-)
+
+diff --git a/libavformat/mov.c b/libavformat/mov.c
+index 7fe0548..cb0e395 100644
+--- a/libavformat/mov.c
++++ b/libavformat/mov.c
+@@ -2129,6 +2129,7 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+ AVStream *st;
+ MOVStreamContext *sc;
+ int version;
++ int flags;
+
+ if (c->fc->nb_streams < 1)
+ return 0;
+@@ -2136,13 +2137,14 @@ static int mov_read_tkhd(MOVContext *c, AVIOContext *pb, MOVAtom atom)
+ sc = st->priv_data;
+
+ version = avio_r8(pb);
+- avio_rb24(pb); /* flags */
++ flags = avio_rb24(pb); /* flags */
+ /*
+ MOV_TRACK_ENABLED 0x0001
+ MOV_TRACK_IN_MOVIE 0x0002
+ MOV_TRACK_IN_PREVIEW 0x0004
+ MOV_TRACK_IN_POSTER 0x0008
+ */
++ st->disposition |= (flags & 0x1) ? AV_DISPOSITION_DEFAULT : 0;
+
+ if (version == 1) {
+ avio_rb64(pb);
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index 2f6c003..bc77a6f 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -1387,7 +1387,13 @@ static int mov_write_tkhd_tag(AVIOContext *pb, MOVTrack *track, AVStream *st)
+ (version == 1) ? avio_wb32(pb, 104) : avio_wb32(pb, 92); /* size */
+ ffio_wfourcc(pb, "tkhd");
+ avio_w8(pb, version);
+- avio_wb24(pb, 0xf); /* flags (track enabled) */
++ avio_wb24(pb, (track->flags & MOV_TRACK_ENABLED) ? 0x3 : 0x2); /* flags */
++ /*
++ MOV_TRACK_ENABLED 0x0001
++ MOV_TRACK_IN_MOVIE 0x0002
++ MOV_TRACK_IN_PREVIEW 0x0004
++ MOV_TRACK_IN_POSTER 0x0008
++ */
+ if (version == 1) {
+ avio_wb64(pb, track->time);
+ avio_wb64(pb, track->time);
+@@ -3003,6 +3009,56 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum)
+ }
+ }
+
++// st->disposition controls the "enabled" flag in the tkhd tag.
++// QuickTime will not play a track if it is not enabled. So make sure
++// that one track of each type (audio, video, subtitle) is enabled.
++//
++// Subtitles are special. For audio and video, setting "enabled" also
++// makes the track "default" (i.e. it is rendered when played). For
++// subtitles, an "enabled" subtitle is not rendered by default, but
++// if no subtitle is enabled, the subtitle menu in QuickTime will be
++// empty!
++static void enable_tracks(AVFormatContext *s)
++{
++ MOVMuxContext *mov = s->priv_data;
++ int i;
++ uint8_t enabled[AVMEDIA_TYPE_NB];
++ int first[AVMEDIA_TYPE_NB];
++
++ for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
++ enabled[i] = 0;
++ first[i] = -1;
++ }
++
++ for (i = 0; i < s->nb_streams; i++) {
++ AVStream *st = s->streams[i];
++
++ if (st->codec->codec_type <= AVMEDIA_TYPE_UNKNOWN ||
++ st->codec->codec_type >= AVMEDIA_TYPE_NB)
++ continue;
++
++ if (first[st->codec->codec_type] < 0)
++ first[st->codec->codec_type] = i;
++ if (st->disposition & AV_DISPOSITION_DEFAULT) {
++ mov->tracks[i].flags |= MOV_TRACK_ENABLED;
++ enabled[st->codec->codec_type] = 1;
++ }
++ }
++
++ for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
++ switch (i) {
++ case AVMEDIA_TYPE_VIDEO:
++ case AVMEDIA_TYPE_AUDIO:
++ case AVMEDIA_TYPE_SUBTITLE:
++ if (!enabled[i] && first[i] >= 0)
++ mov->tracks[first[i]].flags |= MOV_TRACK_ENABLED;
++ break;
++ default:
++ break;
++ }
++ }
++}
++
+ static int mov_write_header(AVFormatContext *s)
+ {
+ AVIOContext *pb = s->pb;
+@@ -3156,6 +3212,8 @@ static int mov_write_header(AVFormatContext *s)
+ }
+ }
+
++ enable_tracks(s);
++
+ if (mov->mode == MODE_ISM) {
+ /* If no fragmentation options have been set, set a default. */
+ if (!(mov->flags & (FF_MOV_FLAG_FRAG_KEYFRAME |
+diff --git a/libavformat/movenc.h b/libavformat/movenc.h
+index 3ea0dd5..9bb8a78 100644
+--- a/libavformat/movenc.h
++++ b/libavformat/movenc.h
+@@ -84,6 +84,7 @@ typedef struct MOVIndex {
+ int has_keyframes;
+ #define MOV_TRACK_CTTS 0x0001
+ #define MOV_TRACK_STPS 0x0002
++#define MOV_TRACK_ENABLED 0x0004
+ uint32_t flags;
+ int language;
+ int track_id;
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/A11-mp4-chapter-properties.patch b/contrib/ffmpeg/A11-mp4-chapter-properties.patch
new file mode 100644
index 000000000..9f64af2cb
--- /dev/null
+++ b/contrib/ffmpeg/A11-mp4-chapter-properties.patch
@@ -0,0 +1,47 @@
+From 9393762b54c17abd736f8d5dd96cd22c334989da Mon Sep 17 00:00:00 2001
+From: John Stebbins <[email protected]>
+Date: Fri, 10 May 2013 09:19:16 -0700
+Subject: [PATCH 9/9] movenc: Make chapter track QuickTime compatible
+
+QuickTime requires that the stsd.text box be completely filled in.
+---
+ libavformat/movenc.c | 9 ++++++++-
+ 1 file changed, 8 insertions(+), 1 deletion(-)
+
+diff --git a/libavformat/movenc.c b/libavformat/movenc.c
+index bc77a6f..96adfd7 100644
+--- a/libavformat/movenc.c
++++ b/libavformat/movenc.c
+@@ -2982,12 +2982,17 @@ static void mov_create_chapter_track(AVFormatContext *s, int tracknum)
+ MOVTrack *track = &mov->tracks[tracknum];
+ AVPacket pkt = { .stream_index = tracknum, .flags = AV_PKT_FLAG_KEY };
+ int i, len;
++ // These properties are required to make QT recognize the chapter track
++ uint8_t chapter_properties[43] = {0, 0, 0, 0, 0, 0, 0, 1,};
+
+ track->mode = mov->mode;
+ track->tag = MKTAG('t','e','x','t');
+ track->timescale = MOV_TIMESCALE;
+ track->enc = avcodec_alloc_context3(NULL);
+ track->enc->codec_type = AVMEDIA_TYPE_SUBTITLE;
++ track->enc->extradata = av_malloc(43);
++ track->enc->extradata_size = 43;
++ memcpy(track->enc->extradata, chapter_properties, 43);
+
+ for (i = 0; i < s->nb_chapters; i++) {
+ AVChapter *c = s->chapters[i];
+@@ -3443,8 +3448,10 @@ static int mov_write_trailer(AVFormatContext *s)
+ mov_write_mfra_tag(pb, mov);
+ }
+
+- if (mov->chapter_track)
++ if (mov->chapter_track) {
++ av_free(mov->tracks[mov->chapter_track].enc->extradata);
+ av_freep(&mov->tracks[mov->chapter_track].enc);
++ }
+
+ for (i=0; i<mov->nb_streams; i++) {
+ if (mov->tracks[i].tag == MKTAG('r','t','p',' '))
+--
+1.8.1.4
+
diff --git a/contrib/ffmpeg/module.defs b/contrib/ffmpeg/module.defs
index f906631cc..fdce86fde 100644
--- a/contrib/ffmpeg/module.defs
+++ b/contrib/ffmpeg/module.defs
@@ -39,6 +39,16 @@ FFMPEG.CONFIGURE.extra += \
--enable-encoder=libfdk_aac
endif
+ifeq (1,$(FEATURE.avformat))
+FFMPEG.CONFIGURE.extra += \
+ --enable-muxer=matroska \
+ --enable-muxer=webm \
+ --enable-muxer=mov \
+ --enable-muxer=mp4 \
+ --enable-muxer=psp \
+ --enable-muxer=ipod
+endif
+
## check against tuple: B-SYSTEM where B is { 0 | 1 } for cross-compiling flag
ifeq (0-cygwin,$(BUILD.cross)-$(BUILD.system))
FFMPEG.CONFIGURE.extra += --enable-pthreads --enable-memalign-hack
diff --git a/gtk/configure.ac b/gtk/configure.ac
index 64aa72744..88b77ef8b 100644
--- a/gtk/configure.ac
+++ b/gtk/configure.ac
@@ -64,6 +64,14 @@ AC_ARG_ENABLE(faac,
AS_HELP_STRING([--enable-faac], [enable faac encoder]),
use_faac=yes, use_faac=no)
+AC_ARG_ENABLE(mp4v2,
+ AS_HELP_STRING([--enable-mp4v2], [enable mp4v2 muxer]),
+ use_mp4v2=yes, use_mp4v2=no)
+
+AC_ARG_ENABLE(libmkv,
+ AS_HELP_STRING([--enable-libmkv], [enable libmkv muxer]),
+ use_libmkv=yes, use_libmkv=no)
+
AC_ARG_ENABLE(gst,
AS_HELP_STRING([--disable-gst], [disable gstreamer (live preview)]),
gst_disable=yes, gst_disable=no)
@@ -207,7 +215,7 @@ case $host in
;;
esac
-HB_LIBS="-lhb -la52 -lmkv -lavresample -lavformat -lavcodec -lavutil -ldvdnav -ldvdread -lmp3lame -lmpeg2 -lvorbis -lvorbisenc -logg -lsamplerate -lx264 -lmp4v2 -lswscale -ltheoraenc -ltheoradec -lz -lbz2 -lpthread -lbluray -lass -lfontconfig -lfreetype -lxml2"
+HB_LIBS="-lhb -la52 -lavresample -lavformat -lavcodec -lavutil -ldvdnav -ldvdread -lmp3lame -lmpeg2 -lvorbis -lvorbisenc -logg -lsamplerate -lx264 -lswscale -ltheoraenc -ltheoradec -lz -lbz2 -lpthread -lbluray -lass -lfontconfig -lfreetype -lxml2"
if test "x$use_fdk_aac" = "xyes" ; then
HB_LIBS+=" -lfdk-aac"
@@ -217,6 +225,14 @@ if test "x$use_faac" = "xyes" ; then
HB_LIBS+=" -lfaac"
fi
+if test "x$use_mp4v2" = "xyes" ; then
+ HB_LIBS+=" -lmp4v2"
+fi
+
+if test "x$use_libmkv" = "xyes" ; then
+ HB_LIBS+=" -lmkv"
+fi
+
AC_SUBST(HB_LIBS)
AC_SUBST(GHB_TOOLS_CFLAGS)
AC_SUBST(GHB_TOOLS_LIBS)
diff --git a/gtk/module.defs b/gtk/module.defs
index cee9d7790..bafca356d 100644
--- a/gtk/module.defs
+++ b/gtk/module.defs
@@ -36,3 +36,11 @@ endif
ifeq (1,$(FEATURE.faac))
GTK.CONFIGURE.extra += --enable-faac
endif
+
+ifeq (1,$(FEATURE.mp4v2))
+ GTK.CONFIGURE.extra += --enable-mp4v2
+endif
+
+ifeq (1,$(FEATURE.libmkv))
+ GTK.CONFIGURE.extra += --enable-libmkv
+endif
diff --git a/gtk/src/audiohandler.c b/gtk/src/audiohandler.c
index c25cfe1f3..c90e67239 100644
--- a/gtk/src/audiohandler.c
+++ b/gtk/src/audiohandler.c
@@ -71,7 +71,7 @@ ghb_select_audio_codec(gint mux, hb_audio_config_t *aconfig, gint acodec, gint f
if (enc->codec == fallback &&
!(enc->muxers & mux))
{
- if ( mux == HB_MUX_MKV )
+ if ( mux & HB_MUX_MASK_MKV )
fallback = HB_ACODEC_LAME;
else
fallback = HB_ACODEC_FAAC;
diff --git a/gtk/src/callbacks.c b/gtk/src/callbacks.c
index fed28a22a..603c23fc4 100644
--- a/gtk/src/callbacks.c
+++ b/gtk/src/callbacks.c
@@ -1280,12 +1280,41 @@ update_acodec_combo(signal_user_data_t *ud)
ghb_grey_combo_options (ud);
}
+static void
+set_visible(GtkWidget *widget, gboolean visible)
+{
+ if (visible)
+ {
+ gtk_widget_show_now(widget);
+ }
+ else
+ {
+ gtk_widget_hide(widget);
+ }
+}
+
+static void show_container_options(signal_user_data_t *ud)
+{
+ GtkWidget *w1, *w2, *w3;
+ w1 = GHB_WIDGET(ud->builder, "Mp4LargeFile");
+ w2 = GHB_WIDGET(ud->builder, "Mp4HttpOptimize");
+ w3 = GHB_WIDGET(ud->builder, "Mp4iPodCompatible");
+
+ gint mux = ghb_settings_combo_int(ud->settings, "FileFormat");
+ gint enc = ghb_settings_combo_int(ud->settings, "VideoEncoder");
+
+ set_visible(w1, (mux == HB_MUX_MP4V2));
+ set_visible(w2, (mux & HB_MUX_MASK_MP4));
+ set_visible(w3, (mux & HB_MUX_MASK_MP4) && (enc == HB_VCODEC_X264));
+}
+
G_MODULE_EXPORT void
container_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
{
g_debug("container_changed_cb ()");
ghb_widget_to_setting(ud->settings, widget);
ghb_check_dependency(ud, widget, NULL);
+ show_container_options(ud);
update_acodec_combo(ud);
ghb_update_destination_extension(ud);
ghb_clear_presets_selection(ud);
@@ -1879,6 +1908,7 @@ vcodec_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
ghb_widget_to_setting(ud->settings, widget);
ghb_check_dependency(ud, widget, NULL);
+ show_container_options(ud);
ghb_clear_presets_selection(ud);
ghb_live_reset(ud);
ghb_vquality_range(ud, &vqmin, &vqmax, &step, &page, &digits, &inverted);
@@ -3361,19 +3391,6 @@ ghb_log_cb(GIOChannel *source, GIOCondition cond, gpointer data)
return TRUE;
}
-static void
-set_visible(GtkWidget *widget, gboolean visible)
-{
- if (visible)
- {
- gtk_widget_show_now(widget);
- }
- else
- {
- gtk_widget_hide(widget);
- }
-}
-
G_MODULE_EXPORT void
show_activity_clicked_cb(GtkWidget *xwidget, signal_user_data_t *ud)
{
diff --git a/gtk/src/ghb.ui b/gtk/src/ghb.ui
index ba8f46856..ad999da0b 100644
--- a/gtk/src/ghb.ui
+++ b/gtk/src/ghb.ui
@@ -1192,7 +1192,7 @@ This setting allows you to synchronize the files.</property>
</object>
<packing>
<property name="x_options">GTK_FILL</property>
- <property name="y_options">GTK_FILL</property>
+ <property name="y_options"></property>
</packing>
</child>
<child>
diff --git a/gtk/src/hb-backend.c b/gtk/src/hb-backend.c
index ace8291b4..0787b26ff 100644
--- a/gtk/src/hb-backend.c
+++ b/gtk/src/hb-backend.c
@@ -4433,7 +4433,7 @@ ghb_validate_video(GValue *settings)
mux = ghb_settings_combo_int(settings, "FileFormat");
vcodec = ghb_settings_combo_int(settings, "VideoEncoder");
- if ((mux == HB_MUX_MP4) && (vcodec == HB_VCODEC_THEORA))
+ if ((mux & HB_MUX_MASK_MP4) && (vcodec == HB_VCODEC_THEORA))
{
// mp4/theora combination is not supported.
message = g_strdup_printf(
@@ -4581,7 +4581,7 @@ ghb_validate_audio(GValue *settings)
{
codec = HB_ACODEC_AC3;
}
- else if (mux == HB_MUX_MKV)
+ else if (mux & HB_MUX_MASK_MKV)
{
codec = HB_ACODEC_LAME;
}
@@ -4594,7 +4594,7 @@ ghb_validate_audio(GValue *settings)
}
gchar *a_unsup = NULL;
gchar *mux_s = NULL;
- if (mux == HB_MUX_MP4)
+ if (mux & HB_MUX_MASK_MP4)
{
mux_s = "MP4";
// mp4/vorbis|DTS combination is not supported.
@@ -4761,7 +4761,7 @@ add_job(hb_handle_t *h, GValue *js, gint unique_id, gint titleindex)
}
job->mux = ghb_settings_combo_int(js, "FileFormat");
- if (job->mux == HB_MUX_MP4)
+ if (job->mux & HB_MUX_MASK_MP4)
{
job->largeFileSize = ghb_settings_get_boolean(js, "Mp4LargeFile");
job->mp4_optimize = ghb_settings_get_boolean(js, "Mp4HttpOptimize");
@@ -4957,12 +4957,12 @@ add_job(hb_handle_t *h, GValue *js, gint unique_id, gint titleindex)
}
job->vcodec = ghb_settings_combo_int(js, "VideoEncoder");
- if ((job->mux == HB_MUX_MP4 ) && (job->vcodec == HB_VCODEC_THEORA))
+ if ((job->mux & HB_MUX_MASK_MP4 ) && (job->vcodec == HB_VCODEC_THEORA))
{
// mp4/theora combination is not supported.
job->vcodec = HB_VCODEC_FFMPEG_MPEG4;
}
- if ((job->vcodec == HB_VCODEC_X264) && (job->mux == HB_MUX_MP4))
+ if ((job->vcodec == HB_VCODEC_X264) && (job->mux & HB_MUX_MASK_MP4))
{
job->ipod_atom = ghb_settings_get_boolean(js, "Mp4iPodCompatible");
}
diff --git a/gtk/src/makedeps.py b/gtk/src/makedeps.py
index 4557732ca..79914ef5f 100644
--- a/gtk/src/makedeps.py
+++ b/gtk/src/makedeps.py
@@ -28,9 +28,6 @@ dep_map = (
DepEntry("VideoFramerate", "VideoFrameratePFR", "source", True, True),
DepEntry("VideoFramerate", "VideoFramerateVFR", "source", False, True),
DepEntry("VideoTwoPass", "VideoTurboTwoPass", "TRUE", False, False),
- DepEntry("FileFormat", "Mp4LargeFile", "mp4", False, True),
- DepEntry("FileFormat", "Mp4HttpOptimize", "mp4", False, True),
- DepEntry("FileFormat", "Mp4iPodCompatible", "mp4", False, True),
DepEntry("PictureDecombDeinterlace", "PictureDeinterlace", "TRUE", True, True),
DepEntry("PictureDecombDeinterlace", "PictureDeinterlaceCustom", "TRUE", True, True),
DepEntry("PictureDecombDeinterlace", "PictureDeinterlaceLabel", "TRUE", True, True),
@@ -50,7 +47,6 @@ dep_map = (
DepEntry("VideoEncoder", "x264_tab", "x264", False, True),
DepEntry("VideoEncoder", "x264VideoSettings", "x264", False, True),
DepEntry("VideoEncoder", "lavc_mpeg4_tab", "ffmpeg|ffmpeg4|ffmpeg2", False, True),
- DepEntry("VideoEncoder", "Mp4iPodCompatible", "x264", False, False),
DepEntry("AudioTrackQualityEnable", "AudioBitrateLabel", "TRUE", True, False),
DepEntry("AudioTrackQualityEnable", "AudioBitrate", "TRUE", True, False),
DepEntry("AudioEncoderActual", "AudioBitrateLabel", "copy:mp3|copy:aac|copy:ac3|copy:dts|copy:dtshd", True, False),
diff --git a/gtk/src/queuehandler.c b/gtk/src/queuehandler.c
index d8d1e18a6..d8b6f6d2c 100644
--- a/gtk/src/queuehandler.c
+++ b/gtk/src/queuehandler.c
@@ -172,7 +172,7 @@ add_to_queue_list(signal_user_data_t *ud, GValue *settings, GtkTreeIter *piter)
g_string_append_printf(str,
"<b>Format:</b> <small>%s Container</small>\n", container);
}
- if (mux == HB_MUX_MP4)
+ if (mux & HB_MUX_MASK_MP4)
{
gboolean ipod, http, large;
diff --git a/libhb/common.c b/libhb/common.c
index e6c996f5e..18302e3b8 100644
--- a/libhb/common.c
+++ b/libhb/common.c
@@ -300,25 +300,30 @@ hb_container_t *hb_containers_last_item = NULL;
hb_container_internal_t hb_containers[] =
{
// legacy muxers, back to HB 0.9.4 whenever possible (disabled)
- { { "M4V file", "m4v", "m4v", 0, }, NULL, 0, HB_GID_MUX_MP4, },
- { { "MP4 file", "mp4", "mp4", 0, }, NULL, 0, HB_GID_MUX_MP4, },
- { { "MKV file", "mkv", "mkv", 0, }, NULL, 0, HB_GID_MUX_MKV, },
+ { { "M4V file", "m4v", "m4v", 0, }, NULL, 0, HB_GID_MUX_MP4, },
+ { { "MP4 file", "mp4", "mp4", 0, }, NULL, 0, HB_GID_MUX_MP4, },
+ { { "MKV file", "mkv", "mkv", 0, }, NULL, 0, HB_GID_MUX_MKV, },
// actual muxers
- { { "MPEG-4 (mp4v2)", "mp4v2", "mp4", HB_MUX_MP4V2, }, NULL, 1, HB_GID_MUX_MP4, },
- { { "Matroska (libmkv)", "libmkv", "mkv", HB_MUX_LIBMKV, }, NULL, 1, HB_GID_MUX_MKV, },
+ { { "MPEG-4 (mp4v2)", "mp4v2", "mp4", HB_MUX_MP4V2, }, NULL, 1, HB_GID_MUX_MP4, },
+ { { "Matroska (libmkv)", "libmkv", "mkv", HB_MUX_LIBMKV, }, NULL, 1, HB_GID_MUX_MKV, },
+ { { "MPEG-4 (avformat)", "av_mp4", "mp4", HB_MUX_AV_MP4, }, NULL, 1, HB_GID_MUX_MP4, },
+ { { "Matroska (avformat)", "av_mkv", "mkv", HB_MUX_AV_MKV, }, NULL, 1, HB_GID_MUX_MKV, },
};
int hb_containers_count = sizeof(hb_containers) / sizeof(hb_containers[0]);
static int hb_container_is_enabled(int format)
{
switch (format)
{
-#if 1 //#ifdef USE_MP4V2
+#ifdef USE_MP4V2
case HB_MUX_MP4V2:
- return 1;
#endif
-
- // the following muxers are always enabled
+#ifdef USE_LIBMKV
case HB_MUX_LIBMKV:
+#endif
+#ifdef USE_AVFORMAT
+ case HB_MUX_AV_MP4:
+ case HB_MUX_AV_MKV:
+#endif
return 1;
default:
@@ -3499,46 +3504,50 @@ int hb_subtitle_can_burn( int source )
int hb_subtitle_can_pass( int source, int mux )
{
- if ( mux == HB_MUX_MKV )
+ switch (mux)
{
- switch( source )
- {
- case PGSSUB:
- case VOBSUB:
- case SSASUB:
- case SRTSUB:
- case UTF8SUB:
- case TX3GSUB:
- case CC608SUB:
- case CC708SUB:
- return 1;
+ case HB_MUX_AV_MKV:
+ case HB_MUX_LIBMKV:
+ switch( source )
+ {
+ case PGSSUB:
+ case VOBSUB:
+ case SSASUB:
+ case SRTSUB:
+ case UTF8SUB:
+ case TX3GSUB:
+ case CC608SUB:
+ case CC708SUB:
+ return 1;
- default:
- return 0;
- }
- }
- else if ( mux == HB_MUX_MP4 )
- {
- switch( source )
- {
- case VOBSUB:
- case SSASUB:
- case SRTSUB:
- case UTF8SUB:
- case TX3GSUB:
- case CC608SUB:
- case CC708SUB:
+ default:
+ return 0;
+ } break;
+
+ case HB_MUX_MP4V2:
+ if (source == VOBSUB)
+ {
return 1;
+ } // fall through to next case...
+ case HB_MUX_AV_MP4:
+ switch( source )
+ {
+ case SSASUB:
+ case SRTSUB:
+ case UTF8SUB:
+ case TX3GSUB:
+ case CC608SUB:
+ case CC708SUB:
+ return 1;
- default:
- return 0;
- }
- }
- else
- {
- // Internal error. Should never get here.
- hb_error("internel error. Bad mux %d\n", mux);
- return 0;
+ default:
+ return 0;
+ } break;
+
+ default:
+ // Internal error. Should never get here.
+ hb_error("internel error. Bad mux %d\n", mux);
+ return 0;
}
}
diff --git a/libhb/common.h b/libhb/common.h
index 472d8d018..2ce180034 100644
--- a/libhb/common.h
+++ b/libhb/common.h
@@ -463,9 +463,12 @@ struct hb_job_s
*/
#define HB_MUX_MASK 0xFF0000
#define HB_MUX_MP4V2 0x010000
-#define HB_MUX_MASK_MP4 0x0F0000
+#define HB_MUX_AV_MP4 0x020000
+#define HB_MUX_MASK_MP4 0x030000
#define HB_MUX_LIBMKV 0x100000
-#define HB_MUX_MASK_MKV 0xF00000
+#define HB_MUX_AV_MKV 0x200000
+#define HB_MUX_MASK_MKV 0x300000
+#define HB_MUX_MASK_AV 0x220000
/* default muxer for each container */
#define HB_MUX_MP4 HB_MUX_MP4V2
#define HB_MUX_MKV HB_MUX_LIBMKV
@@ -590,6 +593,7 @@ struct hb_audio_config_s
int normalize_mix_level; /* mix level normalization (boolean) */
int dither_method; /* dither algorithm */
char * name; /* Output track name */
+ int delay;
} out;
/* Input */
diff --git a/libhb/deccc608sub.c b/libhb/deccc608sub.c
index 1d16410e4..c18fa76b8 100644
--- a/libhb/deccc608sub.c
+++ b/libhb/deccc608sub.c
@@ -1497,6 +1497,7 @@ void write_cc_line_as_transcript (struct eia608_screen *data, struct s_write *wb
* Put this subtitle in a hb_buffer_t and shove it into the subtitle fifo
*/
buffer = hb_buffer_init( length + 1 );
+ buffer->s.frametype = HB_FRAME_SUBTITLE;
buffer->s.start = wb->data608->current_visible_start_ms;
buffer->s.stop = get_fts(wb);
memcpy( buffer->data, wb->subline, length + 1 );
@@ -1650,6 +1651,7 @@ int write_cc_buffer_as_srt (struct eia608_screen *data, struct s_write *wb)
if (wb->enc_buffer_used)
{
hb_buffer_t *buffer = hb_buffer_init( wb->enc_buffer_used + 1 );
+ buffer->s.frametype = HB_FRAME_SUBTITLE;
buffer->s.start = ms_start;
buffer->s.stop = ms_end;
memcpy( buffer->data, wb->enc_buffer, wb->enc_buffer_used + 1 );
diff --git a/libhb/decmetadata.c b/libhb/decmetadata.c
index 5f6b6af7b..c7c0d9858 100644
--- a/libhb/decmetadata.c
+++ b/libhb/decmetadata.c
@@ -7,10 +7,12 @@
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
-#include <mp4v2/mp4v2.h>
#include "common.h"
+#if defined(USE_MP4V2)
+#include <mp4v2/mp4v2.h>
+
static int decmp4metadata( hb_title_t *title )
{
MP4FileHandle input_file;
@@ -172,6 +174,7 @@ static int decmp4metadata( hb_title_t *title )
}
return result;
}
+#endif // USE_MP4V2
/*
* decmetadata()
@@ -190,6 +193,7 @@ int decmetadata( hb_title_t *title )
return 0;
}
+#if defined(USE_MP4V2)
/*
* Hacky way of figuring out if this is an MP4, in which case read the data using libmp4v2
*/
@@ -197,5 +201,6 @@ int decmetadata( hb_title_t *title )
{
return decmp4metadata( title );
}
+#endif
return 0;
}
diff --git a/libhb/decpgssub.c b/libhb/decpgssub.c
index 8229c6eda..3c6c8fe90 100644
--- a/libhb/decpgssub.c
+++ b/libhb/decpgssub.c
@@ -332,6 +332,7 @@ static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_buffer_close( &pv->list_pass_buffer );
out->s = in->s;
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->sequence = in->sequence;
}
out->s.start = out->s.stop = pts;
@@ -348,6 +349,7 @@ static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
out = hb_frame_buffer_init(AV_PIX_FMT_YUVA420P,
rect->w, rect->h);
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->s.id = in->s.id;
out->sequence = in->sequence;
out->s.start = pts;
@@ -407,6 +409,7 @@ static int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
{
out = hb_buffer_init( 1 );
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->s.id = in->s.id;
out->s.start = pts;
out->s.stop = pts;
diff --git a/libhb/decssasub.c b/libhb/decssasub.c
index d648f1d9d..277e3672b 100644
--- a/libhb/decssasub.c
+++ b/libhb/decssasub.c
@@ -33,6 +33,7 @@ struct hb_work_private_s
{
// If decoding to PICTURESUB format:
int readOrder;
+ int raw;
hb_job_t *job;
};
@@ -336,6 +337,7 @@ static hb_buffer_t *ssa_decode_line_to_utf8( uint8_t *in_data, int in_size, int
out->size = dst - out->data;
// Copy metadata from the input packet to the output packet
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->s.start = in_start;
out->s.stop = in_stop;
out->sequence = in_sequence;
@@ -401,6 +403,17 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i
if ( parse_timing_from_ssa_packet( (char *) in_data, &in_start, &in_stop ) )
goto fail;
+ if (pv->raw)
+ {
+ out = hb_buffer_init(in_size + 3);
+ snprintf((char*)out->data, in_size + 3, "%s\r\n", in_data);
+ out->s.frametype = HB_FRAME_SUBTITLE;
+ out->s.start = in_start;
+ out->s.stop = in_stop;
+ out->sequence = in_sequence;
+ return out;
+ }
+
// Convert the SSA packet to MKV-SSA format, which is what libass expects
char *mkvIn;
int numPartsRead;
@@ -439,6 +452,7 @@ static hb_buffer_t *ssa_decode_line_to_mkv_ssa( hb_work_object_t * w, uint8_t *i
strcat( mkvIn, (char *) styleToTextFields );
out->size = strlen(mkvIn);
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->s.start = in_start;
out->s.stop = in_stop;
out->sequence = in_sequence;
@@ -464,6 +478,11 @@ static int decssaInit( hb_work_object_t * w, hb_job_t * job )
pv = calloc( 1, sizeof( hb_work_private_t ) );
w->private_data = pv;
pv->job = job;
+
+ if (job->mux & HB_MUX_MASK_AV)
+ {
+ pv->raw = 1;
+ }
return 0;
}
@@ -485,7 +504,8 @@ static int decssaWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
return HB_WORK_DONE;
}
- if ( w->subtitle->config.dest == PASSTHRUSUB && pv->job->mux == HB_MUX_MKV )
+ if (w->subtitle->config.dest == PASSTHRUSUB &&
+ (pv->job->mux & HB_MUX_MASK_MKV))
{
*buf_out = ssa_to_mkv_ssa(w, in);
}
diff --git a/libhb/dectx3gsub.c b/libhb/dectx3gsub.c
index a2231a4fb..f492040c3 100644
--- a/libhb/dectx3gsub.c
+++ b/libhb/dectx3gsub.c
@@ -168,6 +168,7 @@ static hb_buffer_t *tx3g_decode_to_utf8( hb_buffer_t *in )
out->size = dst - out->data;
// Copy metadata from the input packet to the output packet
+ out->s.frametype = HB_FRAME_SUBTITLE;
out->s.start = in->s.start;
out->s.stop = in->s.stop;
diff --git a/libhb/decutf8sub.c b/libhb/decutf8sub.c
index ea6440954..0cd0513d9 100644
--- a/libhb/decutf8sub.c
+++ b/libhb/decutf8sub.c
@@ -31,6 +31,8 @@ static int decutf8Work(hb_work_object_t * w,
// Pass the packets through without modification
hb_buffer_t *out = *buf_in;
+ out->s.frametype = HB_FRAME_SUBTITLE;
+
// Warn if the subtitle's duration has not been passed through by the
// demuxer, which will prevent the subtitle from displaying at all
if (out->s.stop == 0)
diff --git a/libhb/decvobsub.c b/libhb/decvobsub.c
index 12bf94417..3b6c3cb79 100644
--- a/libhb/decvobsub.c
+++ b/libhb/decvobsub.c
@@ -123,6 +123,7 @@ int decsubWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
pv->buf = hb_buffer_init( 0xFFFF );
memcpy( pv->buf->data, in->data, in->size );
pv->buf->s.id = in->s.id;
+ pv->buf->s.frametype = HB_FRAME_SUBTITLE;
pv->buf->sequence = in->sequence;
pv->size_got = in->size;
if( in->s.start >= 0 )
@@ -537,6 +538,7 @@ static hb_buffer_t * CropSubtitle( hb_work_object_t * w, uint8_t * raw )
realheight = crop[1] - crop[0] + 1;
buf = hb_frame_buffer_init( AV_PIX_FMT_YUVA420P, realwidth, realheight );
+ buf->s.frametype = HB_FRAME_SUBTITLE;
buf->s.start = pv->pts_start;
buf->s.stop = pv->pts_stop;
buf->s.type = SUBTITLE_BUF;
diff --git a/libhb/encavcodec.c b/libhb/encavcodec.c
index dcb19da34..f8f9d4796 100644
--- a/libhb/encavcodec.c
+++ b/libhb/encavcodec.c
@@ -39,8 +39,7 @@ struct hb_work_private_s
struct {
int64_t start;
- int64_t stop;
- int64_t renderOffset;
+ int64_t duration;
} frame_info[FRAME_INFO_SIZE];
};
@@ -209,7 +208,7 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
job->anamorphic.par_width, job->anamorphic.par_height );
}
- if( job->mux & HB_MUX_MP4 )
+ if( job->mux & HB_MUX_MASK_MP4 )
{
context->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
@@ -268,7 +267,7 @@ int encavcodecInit( hb_work_object_t * w, hb_job_t * job )
{
job->areBframes = 1;
}
- if( ( job->mux & HB_MUX_MP4 ) && job->pass != 1 )
+ if( ( job->mux & HB_MUX_MASK_MP4 ) && job->pass != 1 )
{
w->config->mpeg4.length = context->extradata_size;
memcpy( w->config->mpeg4.bytes, context->extradata,
@@ -309,7 +308,7 @@ static void save_frame_info( hb_work_private_t * pv, hb_buffer_t * in )
{
int i = pv->frameno_in & FRAME_INFO_MASK;
pv->frame_info[i].start = in->s.start;
- pv->frame_info[i].stop = in->s.stop;
+ pv->frame_info[i].duration = in->s.stop - in->s.start;
}
static int64_t get_frame_start( hb_work_private_t * pv, int64_t frameno )
@@ -318,10 +317,10 @@ static int64_t get_frame_start( hb_work_private_t * pv, int64_t frameno )
return pv->frame_info[i].start;
}
-static int64_t get_frame_stop( hb_work_private_t * pv, int64_t frameno )
+static int64_t get_frame_duration( hb_work_private_t * pv, int64_t frameno )
{
int i = frameno & FRAME_INFO_MASK;
- return pv->frame_info[i].stop;
+ return pv->frame_info[i].duration;
}
static void compute_dts_offset( hb_work_private_t * pv, hb_buffer_t * buf )
@@ -469,10 +468,11 @@ int encavcodecWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
else
{
int64_t frameno = pkt.pts;
- buf->size = pkt.size;
- buf->s.start = get_frame_start( pv, frameno );
- buf->s.stop = get_frame_stop( pv, frameno );
- buf->s.flags &= ~HB_FRAME_REF;
+ buf->size = pkt.size;
+ buf->s.start = get_frame_start( pv, frameno );
+ buf->s.duration = get_frame_duration( pv, frameno );
+ buf->s.stop = buf->s.stop + buf->s.duration;
+ buf->s.flags &= ~HB_FRAME_REF;
switch ( pv->context->coded_frame->pict_type )
{
case AV_PICTURE_TYPE_P:
diff --git a/libhb/encavcodecaudio.c b/libhb/encavcodecaudio.c
index e24eaca32..44c3be75d 100644
--- a/libhb/encavcodecaudio.c
+++ b/libhb/encavcodecaudio.c
@@ -243,6 +243,9 @@ static int encavcodecaInit(hb_work_object_t *w, hb_job_t *job)
w->config->extradata.length = context->extradata_size;
}
+ audio->config.out.delay = av_rescale_q(context->delay, context->time_base,
+ (AVRational){1, 90000});
+
return 0;
}
@@ -388,21 +391,14 @@ static hb_buffer_t* Encode(hb_work_object_t *w)
if (got_packet && pkt.size)
{
out->size = pkt.size;
-
// The output pts from libav is in context->time_base. Convert it back
// to our timebase.
- //
- // Also account for the "delay" factor that libav seems to arbitrarily
- // subtract from the packet. Not sure WTH they think they are doing by
- // offsetting the value in a negative direction.
- out->s.start = av_rescale_q(pv->context->delay + pkt.pts,
- pv->context->time_base,
- (AVRational){1, 90000});
-
- out->s.stop = out->s.start + (90000 * pv->samples_per_frame /
- audio->config.out.samplerate);
-
- out->s.type = AUDIO_BUF;
+ out->s.start = av_rescale_q(pkt.pts, pv->context->time_base,
+ (AVRational){1, 90000});
+ out->s.duration = (double)90000 * pv->samples_per_frame /
+ audio->config.out.samplerate;
+ out->s.stop = out->s.start + out->s.duration;
+ out->s.type = AUDIO_BUF;
out->s.frametype = HB_FRAME_AUDIO;
}
else
diff --git a/libhb/encfaac.c b/libhb/encfaac.c
index 3bdd7cee0..551a7ea01 100644
--- a/libhb/encfaac.c
+++ b/libhb/encfaac.c
@@ -201,12 +201,13 @@ static hb_buffer_t * Encode( hb_work_object_t * w )
{
hb_buffer_t * buf = hb_buffer_init( size );
memcpy( buf->data, pv->obuf, size );
- buf->size = size;
- buf->s.start = pv->pts;
- pv->pts += pv->framedur;
- buf->s.stop = pv->pts;
- buf->s.type = AUDIO_BUF;
+ buf->size = size;
+ buf->s.start = pv->pts;
+ buf->s.duration = pv->framedur;
+ buf->s.stop = buf->s.start + buf->s.duration;
+ buf->s.type = AUDIO_BUF;
buf->s.frametype = HB_FRAME_AUDIO;
+ pv->pts += pv->framedur;
return buf;
}
return NULL;
diff --git a/libhb/enclame.c b/libhb/enclame.c
index 2858f6078..6cb4ab3a3 100644
--- a/libhb/enclame.c
+++ b/libhb/enclame.c
@@ -145,9 +145,10 @@ static hb_buffer_t * Encode( hb_work_object_t * w )
}
}
- buf = hb_buffer_init( pv->output_bytes );
- buf->s.start = pts + 90000 * pos / pv->out_discrete_channels / sizeof( float ) / audio->config.out.samplerate;
- buf->s.stop = buf->s.start + 90000 * 1152 / audio->config.out.samplerate;
+ buf = hb_buffer_init( pv->output_bytes );
+ buf->s.start = pts + 90000 * pos / pv->out_discrete_channels / sizeof( float ) / audio->config.out.samplerate;
+ buf->s.duration = (double)90000 * 1152 / audio->config.out.samplerate;
+ buf->s.stop = buf->s.start + buf->s.duration;
pv->pts = buf->s.stop;
buf->size = lame_encode_buffer_float(
pv->lame, samples[0], samples[1],
diff --git a/libhb/enctheora.c b/libhb/enctheora.c
index 07fdb7f95..36fd9e98a 100644
--- a/libhb/enctheora.c
+++ b/libhb/enctheora.c
@@ -178,17 +178,17 @@ int enctheoraInit( hb_work_object_t * w, hb_job_t * job )
th_comment_init( &tc );
- th_encode_flushheader( pv->ctx, &tc, &op );
- memcpy(w->config->theora.headers[0], &op, sizeof(op));
- memcpy(w->config->theora.headers[0] + sizeof(op), op.packet, op.bytes );
+ ogg_packet *header;
- th_encode_flushheader( pv->ctx, &tc, &op );
- memcpy(w->config->theora.headers[1], &op, sizeof(op));
- memcpy(w->config->theora.headers[1] + sizeof(op), op.packet, op.bytes );
-
- th_encode_flushheader( pv->ctx, &tc, &op );
- memcpy(w->config->theora.headers[2], &op, sizeof(op));
- memcpy(w->config->theora.headers[2] + sizeof(op), op.packet, op.bytes );
+ int ii;
+ for (ii = 0; ii < 3; ii++)
+ {
+ th_encode_flushheader( pv->ctx, &tc, &op );
+ header = (ogg_packet*)w->config->theora.headers[ii];
+ memcpy(header, &op, sizeof(op));
+ header->packet = w->config->theora.headers[ii] + sizeof(ogg_packet);
+ memcpy(header->packet, op.packet, op.bytes );
+ }
th_comment_clear( &tc );
@@ -362,15 +362,15 @@ int enctheoraWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
}
th_encode_packetout( pv->ctx, 0, &op );
- buf = hb_buffer_init( op.bytes + sizeof(op) );
- memcpy(buf->data, &op, sizeof(op));
- memcpy(buf->data + sizeof(op), op.packet, op.bytes);
+ buf = hb_buffer_init(op.bytes);
+ memcpy(buf->data, op.packet, op.bytes);
buf->f.fmt = AV_PIX_FMT_YUV420P;
buf->f.width = frame_width;
buf->f.height = frame_height;
buf->s.frametype = ( th_packet_iskeyframe(&op) ) ? HB_FRAME_KEY : HB_FRAME_REF;
- buf->s.start = in->s.start;
- buf->s.stop = in->s.stop;
+ buf->s.start = in->s.start;
+ buf->s.stop = in->s.stop;
+ buf->s.duration = in->s.stop - in->s.start;
*buf_out = buf;
diff --git a/libhb/encvorbis.c b/libhb/encvorbis.c
index e75bce622..c81b44d76 100644
--- a/libhb/encvorbis.c
+++ b/libhb/encvorbis.c
@@ -114,11 +114,13 @@ int encvorbisInit(hb_work_object_t *w, hb_job_t *job)
/* get the 3 headers */
vorbis_analysis_headerout(&pv->vd, &pv->vc,
&header[0], &header[1], &header[2]);
+ ogg_packet *pheader;
for (i = 0; i < 3; i++)
{
- memcpy(w->config->vorbis.headers[i], &header[i], sizeof(ogg_packet));
- memcpy(w->config->vorbis.headers[i] + sizeof(ogg_packet),
- header[i].packet, header[i].bytes);
+ pheader = (ogg_packet*)w->config->theora.headers[i];
+ memcpy(pheader, &header[i], sizeof(ogg_packet));
+ pheader->packet = w->config->theora.headers[i] + sizeof(ogg_packet);
+ memcpy(pheader->packet, header[i].packet, header[i].bytes );
}
pv->input_samples = pv->out_discrete_channels * OGGVORBIS_FRAME_SIZE;
@@ -180,10 +182,8 @@ static hb_buffer_t * Flush( hb_work_object_t * w )
if( vorbis_bitrate_flushpacket( &pv->vd, &op ) )
{
- buf = hb_buffer_init( sizeof( ogg_packet ) + op.bytes );
- memcpy( buf->data, &op, sizeof( ogg_packet ) );
- memcpy( buf->data + sizeof( ogg_packet ), op.packet,
- op.bytes );
+ buf = hb_buffer_init( op.bytes );
+ memcpy( buf->data, op.packet, op.bytes );
blocksize = vorbis_packet_blocksize(&pv->vi, &op);
buf->s.type = AUDIO_BUF;
@@ -191,6 +191,7 @@ static hb_buffer_t * Flush( hb_work_object_t * w )
buf->s.start = (int64_t)(vorbis_granule_time(&pv->vd, op.granulepos) * 90000);
buf->s.stop = (int64_t)(vorbis_granule_time(&pv->vd, (pv->prev_blocksize + blocksize)/4 + op.granulepos) * 90000);
+ buf->s.duration = buf->s.stop - buf->s.start;
/* The stop time isn't accurate for the first ~3 packets, as the actual blocksize depends on the previous _and_ current packets. */
pv->prev_blocksize = blocksize;
return buf;
diff --git a/libhb/encx264.c b/libhb/encx264.c
index df978206a..790fbcece 100644
--- a/libhb/encx264.c
+++ b/libhb/encx264.c
@@ -389,9 +389,9 @@ static hb_buffer_t *nal_encode( hb_work_object_t *w, x264_picture_t *pic_out,
buf->s.frametype = 0;
// use the pts to get the original frame's duration.
- int64_t duration = get_frame_duration( pv, pic_out->i_pts );
- buf->s.start = pic_out->i_pts;
- buf->s.stop = pic_out->i_pts + duration;
+ buf->s.duration = get_frame_duration( pv, pic_out->i_pts );
+ buf->s.start = pic_out->i_pts;
+ buf->s.stop = buf->s.start + buf->s.duration;
buf->s.renderOffset = pic_out->i_dts;
if ( !w->config->h264.init_delay && pic_out->i_dts < 0 )
{
diff --git a/libhb/fifo.c b/libhb/fifo.c
index df83340fd..a32242c03 100644
--- a/libhb/fifo.c
+++ b/libhb/fifo.c
@@ -299,6 +299,9 @@ hb_buffer_t * hb_buffer_init( int size )
b->alloc = buffer_pool->buffer_size;
b->size = size;
b->data = data;
+ b->s.start = -1;
+ b->s.stop = -1;
+ b->s.renderOffset = -1;
return( b );
}
}
@@ -335,6 +338,9 @@ hb_buffer_t * hb_buffer_init( int size )
buffers.allocated += b->alloc;
hb_unlock(buffers.lock);
}
+ b->s.start = -1;
+ b->s.stop = -1;
+ b->s.renderOffset = -1;
return b;
}
diff --git a/libhb/internal.h b/libhb/internal.h
index 4a2f775a9..d93119fb0 100644
--- a/libhb/internal.h
+++ b/libhb/internal.h
@@ -83,14 +83,15 @@ struct hb_buffer_s
uint8_t discontinuity;
int new_chap; // Video packets: if non-zero, is the index of the chapter whose boundary was crossed
- #define HB_FRAME_IDR 0x01
- #define HB_FRAME_I 0x02
- #define HB_FRAME_AUDIO 0x04
- #define HB_FRAME_P 0x10
- #define HB_FRAME_B 0x20
- #define HB_FRAME_BREF 0x40
- #define HB_FRAME_KEY 0x0F
- #define HB_FRAME_REF 0xF0
+ #define HB_FRAME_IDR 0x01
+ #define HB_FRAME_I 0x02
+ #define HB_FRAME_AUDIO 0x04
+ #define HB_FRAME_SUBTITLE 0x08
+ #define HB_FRAME_P 0x10
+ #define HB_FRAME_B 0x20
+ #define HB_FRAME_BREF 0x40
+ #define HB_FRAME_KEY 0x0F
+ #define HB_FRAME_REF 0xF0
uint8_t frametype;
uint16_t flags;
} s;
@@ -466,7 +467,9 @@ typedef struct hb_mux_data_s hb_mux_data_t;
hb_mux_object_t * hb_mux_##a##_init( hb_job_t * );
DECLARE_MUX( mp4 );
-DECLARE_MUX( avi );
-DECLARE_MUX( ogm );
DECLARE_MUX( mkv );
+DECLARE_MUX( avformat );
+void hb_muxmp4_process_subtitle_style( uint8_t *input,
+ uint8_t *output,
+ uint8_t *style, uint16_t *stylesize );
diff --git a/libhb/module.defs b/libhb/module.defs
index 9f5e46795..49df1b28f 100644
--- a/libhb/module.defs
+++ b/libhb/module.defs
@@ -46,6 +46,15 @@ endif
ifeq (1,$(FEATURE.faac))
LIBHB.GCC.D += USE_FAAC
endif
+ifeq (1,$(FEATURE.mp4v2))
+LIBHB.GCC.D += USE_MP4V2
+endif
+ifeq (1,$(FEATURE.libmkv))
+LIBHB.GCC.D += USE_LIBMKV
+endif
+ifeq (1,$(FEATURE.avformat))
+LIBHB.GCC.D += USE_AVFORMAT
+endif
LIBHB.GCC.D += __LIBHB__ USE_PTHREAD
LIBHB.GCC.I += $(LIBHB.build/) $(CONTRIB.build/)include
@@ -102,7 +111,7 @@ LIBHB.lib = $(LIBHB.build/)hb.lib
LIBHB.dll.libs = $(foreach n, \
a52 ass avcodec avformat avutil avresample dvdnav dvdread \
- fontconfig freetype mkv mpeg2 mp3lame mp4v2 \
+ fontconfig freetype mpeg2 mp3lame \
ogg samplerate swscale theora vorbis vorbisenc x264 xml2 bluray, \
$(CONTRIB.build/)lib/lib$(n).a )
@@ -114,6 +123,14 @@ ifeq (1,$(FEATURE.faac))
LIBHB.dll.libs += $(CONTRIB.build/)lib/libfaac.a
endif
+ifeq (1,$(FEATURE.mp4v2))
+LIBHB.dll.libs += $(CONTRIB.build/)lib/libmp4v2.a
+endif
+
+ifeq (1,$(FEATURE.libmkv))
+LIBHB.dll.libs += $(CONTRIB.build/)lib/libmkv.a
+endif
+
ifneq ($(HAS.iconv),1)
LIBHB.dll.libs += $(CONTRIB.build/)lib/libiconv.a
else
diff --git a/libhb/muxavformat.c b/libhb/muxavformat.c
new file mode 100644
index 000000000..7053c6dc6
--- /dev/null
+++ b/libhb/muxavformat.c
@@ -0,0 +1,1239 @@
+/* muxavformat.c
+
+ Copyright (c) 2003-2013 HandBrake Team
+ This file is part of the HandBrake source code
+ Homepage: <http://handbrake.fr/>.
+ It may be used under the terms of the GNU General Public License v2.
+ For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
+ */
+
+#if defined(USE_AVFORMAT)
+
+#include <ogg/ogg.h>
+#include "libavformat/avformat.h"
+#include "libavutil/avstring.h"
+#include "libavutil/intreadwrite.h"
+
+#include "hb.h"
+#include "lang.h"
+
+struct hb_mux_data_s
+{
+ enum
+ {
+ MUX_TYPE_VIDEO,
+ MUX_TYPE_AUDIO,
+ MUX_TYPE_SUBTITLE
+ } type;
+
+ AVStream *st;
+
+ int64_t duration;
+
+ hb_buffer_t * delay_buf;
+
+ int64_t prev_chapter_tc;
+ int16_t current_chapter;
+};
+
+struct hb_mux_object_s
+{
+ HB_MUX_COMMON;
+
+ hb_job_t * job;
+
+ AVFormatContext * oc;
+ AVRational time_base;
+
+ int ntracks;
+ hb_mux_data_t ** tracks;
+
+ int delay;
+};
+
+enum
+{
+ META_TITLE,
+ META_ARTIST,
+ META_DIRECTOR,
+ META_COMPOSER,
+ META_RELEASE_DATE,
+ META_COMMENT,
+ META_ALBUM,
+ META_GENRE,
+ META_DESCRIPTION,
+ META_SYNOPSIS,
+ META_LAST
+};
+
+enum
+{
+ META_MUX_MP4,
+ META_MUX_MKV,
+ META_MUX_LAST
+};
+
+const char *metadata_keys[META_LAST][META_MUX_LAST] =
+{
+ {"title", "TITLE"},
+ {"artist", "ARTIST"},
+ {"album_artist", "DIRECTOR"},
+ {"composer", "COMPOSER"},
+ {"date", "DATE_RELEASED"},
+ {"comment", "SUMMARY"},
+ {"album", NULL},
+ {"genre", "GENRE"},
+ {"description", "DESCRIPTION"},
+ {"synopsis", "SYNOPSIS"}
+};
+
+static char* lookup_lang_code(int mux, char *iso639_2)
+{
+ iso639_lang_t *lang;
+ char *out = NULL;
+
+ switch (mux)
+ {
+ case HB_MUX_AV_MP4:
+ out = iso639_2;
+ break;
+ case HB_MUX_AV_MKV:
+ // MKV lang codes should be ISO-639-2B if it exists,
+ // else ISO-639-2
+ lang = lang_for_code2( iso639_2 );
+ out = lang->iso639_2b ? lang->iso639_2b : lang->iso639_2;
+ break;
+ default:
+ break;
+ }
+ return out;
+}
+
+/**********************************************************************
+ * avformatInit
+ **********************************************************************
+ * Allocates hb_mux_data_t structures, create file and write headers
+ *********************************************************************/
+static int avformatInit( hb_mux_object_t * m )
+{
+ hb_job_t * job = m->job;
+ hb_audio_t * audio;
+ hb_mux_data_t * track;
+ int meta_mux;
+ int max_tracks;
+ int ii, ret;
+
+ const char *muxer_name = NULL;
+
+ uint8_t default_track_flag = 1;
+ uint8_t need_fonts = 0;
+ char *lang;
+
+
+ m->delay = -1;
+ max_tracks = 1 + hb_list_count( job->list_audio ) +
+ hb_list_count( job->list_subtitle );
+
+ m->tracks = calloc(max_tracks, sizeof(hb_mux_data_t*));
+
+ m->oc = avformat_alloc_context();
+ if (m->oc == NULL)
+ {
+ hb_error( "Could not initialize avformat context." );
+ goto error;
+ }
+
+ switch (job->mux)
+ {
+ case HB_MUX_AV_MP4:
+ m->time_base.num = 1;
+ m->time_base.den = 90000;
+ if( job->ipod_atom )
+ muxer_name = "ipod";
+ else
+ muxer_name = "mp4";
+ meta_mux = META_MUX_MP4;
+ break;
+
+ case HB_MUX_AV_MKV:
+ // libavformat is essentially hard coded such that it only
+ // works with a timebase of 1/1000
+ m->time_base.num = 1;
+ m->time_base.den = 1000;
+ muxer_name = "matroska";
+ meta_mux = META_MUX_MKV;
+ break;
+
+ default:
+ {
+ hb_error("Invalid Mux %x", job->mux);
+ goto error;
+ }
+ }
+ m->oc->oformat = av_guess_format(muxer_name, NULL, NULL);
+ if(m->oc->oformat == NULL)
+ {
+ hb_error("Could not guess output format %s", muxer_name);
+ goto error;
+ }
+ av_strlcpy(m->oc->filename, job->file, sizeof(m->oc->filename));
+ ret = avio_open2(&m->oc->pb, job->file, AVIO_FLAG_WRITE,
+ &m->oc->interrupt_callback, NULL);
+ if( ret < 0 )
+ {
+ hb_error( "avio_open2 failed!");
+ goto error;
+ }
+
+ /* Video track */
+ track = m->tracks[m->ntracks++] = calloc(1, sizeof( hb_mux_data_t ) );
+ job->mux_data = track;
+
+ track->type = MUX_TYPE_VIDEO;
+ track->st = avformat_new_stream(m->oc, NULL);
+ if (track->st == NULL)
+ {
+ hb_error("Could not initialize video stream");
+ goto error;
+ }
+ track->st->time_base = m->time_base;
+ avcodec_get_context_defaults3(track->st->codec, NULL);
+
+ track->st->codec->codec_type = AVMEDIA_TYPE_VIDEO;
+ track->st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+
+ uint8_t *priv_data = NULL;
+ int priv_size = 0;
+ switch (job->vcodec)
+ {
+ case HB_VCODEC_X264:
+ track->st->codec->codec_id = AV_CODEC_ID_H264;
+
+ /* Taken from x264 muxers.c */
+ priv_size = 5 + 1 + 2 + job->config.h264.sps_length + 1 + 2 +
+ job->config.h264.pps_length;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+
+ priv_data[0] = 1;
+ priv_data[1] = job->config.h264.sps[1]; /* AVCProfileIndication */
+ priv_data[2] = job->config.h264.sps[2]; /* profile_compat */
+ priv_data[3] = job->config.h264.sps[3]; /* AVCLevelIndication */
+ priv_data[4] = 0xff; // nalu size length is four bytes
+ priv_data[5] = 0xe1; // one sps
+
+ priv_data[6] = job->config.h264.sps_length >> 8;
+ priv_data[7] = job->config.h264.sps_length;
+
+ memcpy(priv_data+8, job->config.h264.sps,
+ job->config.h264.sps_length);
+
+ priv_data[8+job->config.h264.sps_length] = 1; // one pps
+ priv_data[9+job->config.h264.sps_length] =
+ job->config.h264.pps_length >> 8;
+ priv_data[10+job->config.h264.sps_length] =
+ job->config.h264.pps_length;
+
+ memcpy(priv_data+11+job->config.h264.sps_length,
+ job->config.h264.pps, job->config.h264.pps_length );
+ break;
+
+ case HB_VCODEC_FFMPEG_MPEG4:
+ track->st->codec->codec_id = AV_CODEC_ID_MPEG4;
+
+ if (job->config.mpeg4.length != 0)
+ {
+ priv_size = job->config.mpeg4.length;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, job->config.mpeg4.bytes, priv_size);
+ }
+ break;
+
+ case HB_VCODEC_FFMPEG_MPEG2:
+ track->st->codec->codec_id = AV_CODEC_ID_MPEG2VIDEO;
+
+ if (job->config.mpeg4.length != 0)
+ {
+ priv_size = job->config.mpeg4.length;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, job->config.mpeg4.bytes, priv_size);
+ }
+ break;
+
+ case HB_VCODEC_THEORA:
+ {
+ track->st->codec->codec_id = AV_CODEC_ID_THEORA;
+
+ int size = 0;
+ ogg_packet *ogg_headers[3];
+
+ for (ii = 0; ii < 3; ii++)
+ {
+ ogg_headers[ii] = (ogg_packet *)job->config.theora.headers[ii];
+ size += ogg_headers[ii]->bytes + 2;
+ }
+
+ priv_size = size;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+
+ size = 0;
+ for(ii = 0; ii < 3; ii++)
+ {
+ AV_WB16(priv_data + size, ogg_headers[ii]->bytes);
+ size += 2;
+ memcpy(priv_data+size, ogg_headers[ii]->packet,
+ ogg_headers[ii]->bytes);
+ size += ogg_headers[ii]->bytes;
+ }
+ } break;
+
+ default:
+ hb_error("muxavformat: Unknown video codec: %x", job->vcodec);
+ goto error;
+ }
+ track->st->codec->extradata = priv_data;
+ track->st->codec->extradata_size = priv_size;
+
+ track->st->codec->width = job->width;
+ track->st->codec->height = job->height;
+ track->st->sample_aspect_ratio.num = job->anamorphic.par_width;
+ track->st->sample_aspect_ratio.den = job->anamorphic.par_height;
+ track->st->codec->sample_aspect_ratio.num = job->anamorphic.par_width;
+ track->st->codec->sample_aspect_ratio.den = job->anamorphic.par_height;
+ track->st->disposition |= AV_DISPOSITION_DEFAULT;
+
+ int vrate_base, vrate;
+ if( job->pass == 2 )
+ {
+ hb_interjob_t * interjob = hb_interjob_get( job->h );
+ vrate_base = interjob->vrate_base;
+ vrate = interjob->vrate;
+ }
+ else
+ {
+ vrate_base = job->vrate_base;
+ vrate = job->vrate;
+ }
+
+ // If the vrate is 27000000, there's a good chance this is
+ // a standard rate that we have in our hb_video_rates table.
+ // Because of rounding errors and approximations made while
+ // measuring framerate, the actual value may not be exact. So
+ // we look for rates that are "close" and make an adjustment
+ // to fps.den.
+ if (vrate == 27000000)
+ {
+ const hb_rate_t *video_framerate = NULL;
+ while ((video_framerate = hb_video_framerate_get_next(video_framerate)) != NULL)
+ {
+ if (abs(vrate_base - video_framerate->rate) < 10)
+ {
+ vrate_base = video_framerate->rate;
+ break;
+ }
+ }
+ }
+ hb_reduce(&vrate_base, &vrate, vrate_base, vrate);
+ if (job->mux == HB_MUX_AV_MP4)
+ {
+ // libavformat mp4 muxer requires that the codec time_base have the
+ // same denominator as the stream time_base, it uses it for the
+ // mdhd timescale.
+ double scale = (double)track->st->time_base.den / vrate;
+ track->st->codec->time_base.den = track->st->time_base.den;
+ track->st->codec->time_base.num = vrate_base * scale;
+ }
+ else
+ {
+ track->st->codec->time_base.num = vrate_base;
+ track->st->codec->time_base.den = vrate;
+ }
+
+ /* add the audio tracks */
+ for(ii = 0; ii < hb_list_count( job->list_audio ); ii++ )
+ {
+ audio = hb_list_item( job->list_audio, ii );
+ track = m->tracks[m->ntracks++] = calloc(1, sizeof( hb_mux_data_t ) );
+ audio->priv.mux_data = track;
+
+ track->type = MUX_TYPE_AUDIO;
+
+ track->st = avformat_new_stream(m->oc, NULL);
+ if (track->st == NULL)
+ {
+ hb_error("Could not initialize audio stream");
+ goto error;
+ }
+ avcodec_get_context_defaults3(track->st->codec, NULL);
+
+ track->st->codec->codec_type = AVMEDIA_TYPE_AUDIO;
+ track->st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+ if (job->mux == HB_MUX_AV_MP4)
+ {
+ track->st->codec->time_base.num = audio->config.out.samples_per_frame;
+ track->st->codec->time_base.den = audio->config.out.samplerate;
+ track->st->time_base.num = 1;
+ track->st->time_base.den = audio->config.out.samplerate;
+ }
+ else
+ {
+ track->st->codec->time_base = m->time_base;
+ }
+
+ priv_data = NULL;
+ priv_size = 0;
+ switch (audio->config.out.codec & HB_ACODEC_MASK)
+ {
+ case HB_ACODEC_DCA:
+ case HB_ACODEC_DCA_HD:
+ track->st->codec->codec_id = AV_CODEC_ID_DTS;
+ break;
+ case HB_ACODEC_AC3:
+ track->st->codec->codec_id = AV_CODEC_ID_AC3;
+ break;
+ case HB_ACODEC_LAME:
+ case HB_ACODEC_MP3:
+ track->st->codec->codec_id = AV_CODEC_ID_MP3;
+ break;
+ case HB_ACODEC_VORBIS:
+ {
+ track->st->codec->codec_id = AV_CODEC_ID_VORBIS;
+
+ int jj, size = 0;
+ ogg_packet *ogg_headers[3];
+
+ for (jj = 0; jj < 3; jj++)
+ {
+ ogg_headers[jj] = (ogg_packet *)audio->priv.config.vorbis.headers[jj];
+ size += ogg_headers[jj]->bytes + 2;
+ }
+
+ priv_size = size;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+
+ size = 0;
+ for(jj = 0; jj < 3; jj++)
+ {
+ AV_WB16(priv_data + size, ogg_headers[jj]->bytes);
+ size += 2;
+ memcpy(priv_data+size, ogg_headers[jj]->packet,
+ ogg_headers[jj]->bytes);
+ size += ogg_headers[jj]->bytes;
+ }
+ } break;
+ case HB_ACODEC_FFFLAC:
+ case HB_ACODEC_FFFLAC24:
+ track->st->codec->codec_id = AV_CODEC_ID_FLAC;
+
+ if (audio->priv.config.extradata.bytes)
+ {
+ priv_size = audio->priv.config.extradata.length;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data,
+ audio->priv.config.extradata.bytes,
+ audio->priv.config.extradata.length);
+ }
+ break;
+ case HB_ACODEC_FAAC:
+ case HB_ACODEC_FFAAC:
+ case HB_ACODEC_CA_AAC:
+ case HB_ACODEC_CA_HAAC:
+ case HB_ACODEC_FDK_AAC:
+ case HB_ACODEC_FDK_HAAC:
+ track->st->codec->codec_id = AV_CODEC_ID_AAC;
+
+ if (audio->priv.config.extradata.bytes)
+ {
+ priv_size = audio->priv.config.extradata.length;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data,
+ audio->priv.config.extradata.bytes,
+ audio->priv.config.extradata.length);
+ }
+ break;
+ default:
+ hb_error("muxavformat: Unknown audio codec: %x",
+ audio->config.out.codec);
+ goto error;
+ }
+ track->st->codec->extradata = priv_data;
+ track->st->codec->extradata_size = priv_size;
+
+ if( default_track_flag )
+ {
+ track->st->disposition |= AV_DISPOSITION_DEFAULT;
+ default_track_flag = 0;
+ }
+
+ lang = lookup_lang_code(job->mux, audio->config.lang.iso639_2 );
+ if (lang != NULL)
+ {
+ av_dict_set(&track->st->metadata, "language", lang, 0);
+ }
+ track->st->codec->sample_rate = audio->config.out.samplerate;
+ if (audio->config.out.codec & HB_ACODEC_PASS_FLAG)
+ {
+ track->st->codec->channels = av_get_channel_layout_nb_channels(audio->config.in.channel_layout);
+ track->st->codec->channel_layout = audio->config.in.channel_layout;
+ }
+ else
+ {
+ track->st->codec->channels = hb_mixdown_get_discrete_channel_count(audio->config.out.mixdown);
+ track->st->codec->channel_layout = hb_ff_mixdown_xlat(audio->config.out.mixdown, NULL);
+ }
+
+ char *name;
+ if (audio->config.out.name == NULL)
+ {
+ switch (track->st->codec->channels)
+ {
+ case 1:
+ name = "Mono";
+ break;
+
+ case 2:
+ name = "Stereo";
+ break;
+
+ default:
+ name = "Surround";
+ break;
+ }
+ }
+ else
+ {
+ name = audio->config.out.name;
+ }
+ av_dict_set(&track->st->metadata, "title", name, 0);
+ }
+
+ char * subidx_fmt =
+ "size: %dx%d\n"
+ "org: %d, %d\n"
+ "scale: 100%%, 100%%\n"
+ "alpha: 100%%\n"
+ "smooth: OFF\n"
+ "fadein/out: 50, 50\n"
+ "align: OFF at LEFT TOP\n"
+ "time offset: 0\n"
+ "forced subs: %s\n"
+ "palette: %06x, %06x, %06x, %06x, %06x, %06x, "
+ "%06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x\n"
+ "custom colors: OFF, tridx: 0000, "
+ "colors: 000000, 000000, 000000, 000000\n";
+
+ int subtitle_default = -1;
+ for( ii = 0; ii < hb_list_count( job->list_subtitle ); ii++ )
+ {
+ hb_subtitle_t *subtitle = hb_list_item( job->list_subtitle, ii );
+
+ if( subtitle->config.dest == PASSTHRUSUB )
+ {
+ if ( subtitle->config.default_track )
+ subtitle_default = ii;
+ }
+ }
+ // Quicktime requires that at least one subtitle is enabled,
+ // else it doesn't show any of the subtitles.
+ // So check to see if any of the subtitles are flagged to be
+ // the defualt. The default will the the enabled track, else
+ // enable the first track.
+ if (job->mux == HB_MUX_AV_MP4 && subtitle_default == -1)
+ {
+ subtitle_default = 0;
+ }
+
+ for( ii = 0; ii < hb_list_count( job->list_subtitle ); ii++ )
+ {
+ hb_subtitle_t * subtitle;
+ uint32_t rgb[16];
+ char subidx[2048];
+ int len;
+
+ subtitle = hb_list_item( job->list_subtitle, ii );
+ if (subtitle->config.dest != PASSTHRUSUB)
+ continue;
+
+ track = m->tracks[m->ntracks++] = calloc(1, sizeof( hb_mux_data_t ) );
+ subtitle->mux_data = track;
+
+ track->type = MUX_TYPE_SUBTITLE;
+ track->st = avformat_new_stream(m->oc, NULL);
+ if (track->st == NULL)
+ {
+ hb_error("Could not initialize subtitle stream");
+ goto error;
+ }
+ avcodec_get_context_defaults3(track->st->codec, NULL);
+
+ track->st->codec->codec_type = AVMEDIA_TYPE_SUBTITLE;
+ track->st->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
+ track->st->time_base = m->time_base;
+ track->st->codec->time_base = m->time_base;
+ track->st->codec->width = subtitle->width;
+ track->st->codec->height = subtitle->height;
+
+ priv_data = NULL;
+ priv_size = 0;
+ switch (subtitle->source)
+ {
+ case VOBSUB:
+ {
+ int jj;
+ track->st->codec->codec_id = AV_CODEC_ID_DVD_SUBTITLE;
+
+ for (jj = 0; jj < 16; jj++)
+ rgb[jj] = hb_yuv2rgb(subtitle->palette[jj]);
+ len = snprintf(subidx, 2048, subidx_fmt,
+ subtitle->width, subtitle->height,
+ 0, 0, "OFF",
+ rgb[0], rgb[1], rgb[2], rgb[3],
+ rgb[4], rgb[5], rgb[6], rgb[7],
+ rgb[8], rgb[9], rgb[10], rgb[11],
+ rgb[12], rgb[13], rgb[14], rgb[15]);
+
+ priv_size = len + 1;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, subidx, priv_size);
+ } break;
+
+ case PGSSUB:
+ {
+ track->st->codec->codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE;
+ } break;
+
+ case SSASUB:
+ {
+ if (job->mux == HB_MUX_AV_MP4)
+ {
+ track->st->codec->codec_id = AV_CODEC_ID_MOV_TEXT;
+ }
+ else
+ {
+ track->st->codec->codec_id = AV_CODEC_ID_SSA;
+ need_fonts = 1;
+
+ if (subtitle->extradata_size)
+ {
+ priv_size = subtitle->extradata_size;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, subtitle->extradata, priv_size);
+ }
+ }
+ } break;
+
+ case CC608SUB:
+ case CC708SUB:
+ case UTF8SUB:
+ case TX3GSUB:
+ case SRTSUB:
+ {
+ if (job->mux == HB_MUX_AV_MP4)
+ track->st->codec->codec_id = AV_CODEC_ID_MOV_TEXT;
+ else
+ track->st->codec->codec_id = AV_CODEC_ID_TEXT;
+ } break;
+
+ default:
+ continue;
+ }
+ if (track->st->codec->codec_id == AV_CODEC_ID_MOV_TEXT)
+ {
+ // Build codec extradata for tx3g.
+ // If we were using a libav codec to generate this data
+ // this would (or should) be done for us.
+ uint8_t properties[] = {
+ 0x00, 0x00, 0x00, 0x00, // Display Flags
+ 0x01, // Horiz. Justification
+ 0xff, // Vert. Justification
+ 0x00, 0x00, 0x00, 0xff, // Bg color
+ 0x00, 0x00, 0x00, 0x00, // Default text box
+ 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, // Reserved
+ 0x00, 0x01, // Font ID
+ 0x00, // Font face
+ 0x18, // Font size
+ 0xff, 0xff, 0xff, 0xff, // Fg color
+ // Font table:
+ 0x00, 0x00, 0x00, 0x12, // Font table size
+ 'f','t','a','b', // Tag
+ 0x00, 0x01, // Count
+ 0x00, 0x01, // Font ID
+ 0x05, // Font name length
+ 'A','r','i','a','l' // Font name
+ };
+
+ int width, height = 60;
+ if (job->anamorphic.mode)
+ width = job->width * ((float)job->anamorphic.par_width / job->anamorphic.par_height);
+ else
+ width = job->width;
+ track->st->codec->width = width;
+ track->st->codec->height = height;
+ properties[14] = height >> 8;
+ properties[15] = height & 0xff;
+ properties[16] = width >> 8;
+ properties[17] = width & 0xff;
+
+ priv_size = sizeof(properties);
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, properties, priv_size);
+ }
+ track->st->codec->extradata = priv_data;
+ track->st->codec->extradata_size = priv_size;
+
+ if ( ii == subtitle_default )
+ {
+ track->st->disposition |= AV_DISPOSITION_DEFAULT;
+ }
+
+ lang = lookup_lang_code(job->mux, subtitle->iso639_2 );
+ if (lang != NULL)
+ {
+ av_dict_set(&track->st->metadata, "language", lang, 0);
+ }
+ }
+
+ if (need_fonts)
+ {
+ hb_list_t * list_attachment = job->list_attachment;
+ int i;
+ for ( i = 0; i < hb_list_count(list_attachment); i++ )
+ {
+ hb_attachment_t * attachment = hb_list_item( list_attachment, i );
+
+ if (attachment->type == FONT_TTF_ATTACH &&
+ attachment->size > 0)
+ {
+ AVStream *st = avformat_new_stream(m->oc, NULL);
+ if (st == NULL)
+ {
+ hb_error("Could not initialize attachment stream");
+ goto error;
+ }
+ avcodec_get_context_defaults3(st->codec, NULL);
+
+ st->codec->codec_type = AVMEDIA_TYPE_ATTACHMENT;
+ track->st->codec->codec_id = AV_CODEC_ID_TTF;
+
+ priv_size = attachment->size;
+ priv_data = av_malloc(priv_size);
+ if (priv_data == NULL)
+ {
+ hb_error("malloc failure");
+ goto error;
+ }
+ memcpy(priv_data, attachment->data, priv_size);
+
+ track->st->codec->extradata = priv_data;
+ track->st->codec->extradata_size = priv_size;
+
+ av_dict_set(&st->metadata, "filename", attachment->name, 0);
+ }
+ }
+ }
+
+ if( job->metadata )
+ {
+ hb_metadata_t *md = job->metadata;
+
+ hb_deep_log(2, "Writing Metadata to output file...");
+ if (md->name &&
+ metadata_keys[META_TITLE][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_TITLE][meta_mux], md->name, 0);
+ }
+ if (md->artist &&
+ metadata_keys[META_ARTIST][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_ARTIST][meta_mux], md->artist, 0);
+ }
+ if (md->album_artist &&
+ metadata_keys[META_DIRECTOR][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_DIRECTOR][meta_mux],
+ md->album_artist, 0);
+ }
+ if (md->composer &&
+ metadata_keys[META_COMPOSER][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_COMPOSER][meta_mux],
+ md->composer, 0);
+ }
+ if (md->release_date &&
+ metadata_keys[META_RELEASE_DATE][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_RELEASE_DATE][meta_mux],
+ md->release_date, 0);
+ }
+ if (md->comment &&
+ metadata_keys[META_COMMENT][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_COMMENT][meta_mux], md->comment, 0);
+ }
+ if (!md->name && md->album &&
+ metadata_keys[META_ALBUM][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_ALBUM][meta_mux], md->album, 0);
+ }
+ if (md->genre &&
+ metadata_keys[META_GENRE][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_GENRE][meta_mux], md->genre, 0);
+ }
+ if (md->description &&
+ metadata_keys[META_DESCRIPTION][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_DESCRIPTION][meta_mux],
+ md->description, 0);
+ }
+ if (md->long_description &&
+ metadata_keys[META_SYNOPSIS][meta_mux] != NULL)
+ {
+ av_dict_set(&m->oc->metadata,
+ metadata_keys[META_SYNOPSIS][meta_mux],
+ md->long_description, 0);
+ }
+ }
+
+ AVDictionary * av_opts = NULL;
+ if (job->mp4_optimize && (job->mux & HB_MUX_MASK_MP4))
+ av_dict_set( &av_opts, "movflags", "faststart", 0 );
+
+ ret = avformat_write_header(m->oc, &av_opts);
+ if( ret < 0 )
+ {
+ av_dict_free( &av_opts );
+ hb_error( "muxavformat: avformat_write_header failed!");
+ goto error;
+ }
+
+ AVDictionaryEntry *t = NULL;
+ while( ( t = av_dict_get( av_opts, "", t, AV_DICT_IGNORE_SUFFIX ) ) )
+ {
+ hb_log( "muxavformat: Unknown option %s", t->key );
+ }
+ av_dict_free( &av_opts );
+
+ return 0;
+
+error:
+ free(job->mux_data);
+ job->mux_data = NULL;
+ avformat_free_context(m->oc);
+ *job->die = 1;
+ return -1;
+}
+
+static int add_chapter(hb_mux_object_t *m, int64_t start, int64_t end, char * title)
+{
+ AVChapter *chap;
+ AVChapter **chapters;
+ int nchap = m->oc->nb_chapters;
+
+ nchap++;
+ chapters = av_realloc(m->oc->chapters, nchap * sizeof(AVChapter*));
+ if (chapters == NULL)
+ {
+ hb_error("malloc failure");
+ return -1;
+ }
+
+ chap = av_mallocz(sizeof(AVChapter));
+ if (chap == NULL)
+ {
+ hb_error("malloc failure");
+ return -1;
+ }
+
+ m->oc->chapters = chapters;
+ m->oc->chapters[nchap-1] = chap;
+ m->oc->nb_chapters = nchap;
+
+ chap->id = nchap;
+ chap->time_base = m->time_base;
+ chap->start = start;
+ chap->end = end;
+ av_dict_set(&chap->metadata, "title", title, 0);
+
+ return 0;
+}
+
+// Video with b-frames and certain audio types require a lead-in delay.
+// Compute the max delay and offset all timestamps by this amount.
+//
+// For mp4, avformat will automatically put entries in the edts atom
+// to account for the offset of the first dts in each track.
+static void computeDelay(hb_mux_object_t *m)
+{
+ int ii;
+ hb_audio_t * audio;
+
+ m->delay = m->job->config.h264.init_delay;
+ for(ii = 0; ii < hb_list_count( m->job->list_audio ); ii++ )
+ {
+ audio = hb_list_item( m->job->list_audio, ii );
+ if (audio->config.out.delay > m->delay)
+ m->delay = audio->config.out.delay;
+ }
+}
+
+static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *buf)
+{
+ AVPacket pkt;
+ int64_t dts, pts, duration = -1;
+ hb_job_t *job = m->job;
+ uint8_t tx3g_out[2048];
+
+ if (m->delay == -1)
+ {
+ computeDelay(m);
+ }
+
+ if (buf != NULL)
+ {
+ if (buf->s.start != -1)
+ buf->s.start += m->delay;
+ if (buf->s.renderOffset != -1)
+ buf->s.renderOffset += m->delay;
+ }
+
+ // We only compute dts duration for MP4 files
+ if (track->type == MUX_TYPE_VIDEO && (job->mux & HB_MUX_MASK_MP4))
+ {
+ hb_buffer_t * tmp;
+
+ // delay by one frame so that we can compute duration properly.
+ tmp = track->delay_buf;
+ track->delay_buf = buf;
+ buf = tmp;
+ }
+ if (buf == NULL)
+ return 0;
+
+ if (buf->s.renderOffset == -1)
+ {
+ dts = av_rescale_q(buf->s.start, (AVRational){1,90000},
+ track->st->time_base);
+ }
+ else
+ {
+ dts = av_rescale_q(buf->s.renderOffset, (AVRational){1,90000},
+ track->st->time_base);
+ }
+
+ pts = av_rescale_q(buf->s.start, (AVRational){1,90000},
+ track->st->time_base);
+
+ if (track->type == MUX_TYPE_VIDEO && track->delay_buf != NULL)
+ {
+ int64_t delayed_dts;
+ delayed_dts = av_rescale_q(track->delay_buf->s.renderOffset,
+ (AVRational){1,90000},
+ track->st->time_base);
+ duration = delayed_dts - dts;
+ }
+ if (duration < 0 && buf->s.duration > 0)
+ {
+ duration = av_rescale_q(buf->s.duration, (AVRational){1,90000},
+ track->st->time_base);
+ }
+ if (duration < 0)
+ {
+ // There is a possiblility that some subtitles get through the pipeline
+ // without ever discovering their true duration. Make the duration
+ // 10 seconds in this case.
+ if (track->type == MUX_TYPE_SUBTITLE)
+ duration = av_rescale_q(10, (AVRational){1,1},
+ track->st->time_base);
+ else
+ duration = 0;
+ }
+
+ av_init_packet(&pkt);
+ pkt.data = buf->data;
+ pkt.size = buf->size;
+ pkt.dts = dts;
+ pkt.pts = pts;
+ pkt.duration = duration;
+
+ if (track->type == MUX_TYPE_VIDEO &&
+ (job->vcodec == HB_VCODEC_X264 || job->vcodec & HB_VCODEC_FFMPEG_MASK))
+ {
+ if (buf->s.frametype == HB_FRAME_IDR)
+ pkt.flags |= AV_PKT_FLAG_KEY;
+ }
+ else if (buf->s.frametype & HB_FRAME_KEY)
+ {
+ pkt.flags |= AV_PKT_FLAG_KEY;
+ }
+
+ track->duration += pkt.duration;
+
+ switch (track->type)
+ {
+ case MUX_TYPE_VIDEO:
+ {
+ if (job->chapter_markers && buf->s.new_chap)
+ {
+ hb_chapter_t *chapter;
+
+ // reached chapter N, write marker for chapter N-1
+ // we don't know the end time of chapter N-1 till we receive
+ // chapter N. So we are always writing the previous chapter
+ // mark.
+ track->current_chapter = buf->s.new_chap - 1;
+
+ // chapter numbers start at 1, but the list starts at 0
+ chapter = hb_list_item(job->list_chapter,
+ track->current_chapter - 1);
+
+ // make sure we're not writing a chapter that has 0 length
+ if (chapter != NULL && track->prev_chapter_tc < pkt.pts)
+ {
+ char title[1024];
+ if (chapter->title != NULL)
+ {
+ snprintf(title, 1023, "%s", chapter->title);
+ }
+ else
+ {
+ snprintf(title, 1023, "Chapter %d",
+ track->current_chapter);
+ }
+ add_chapter(m, track->prev_chapter_tc, pkt.pts, title);
+ }
+ track->prev_chapter_tc = pkt.pts;
+ }
+ } break;
+
+ case MUX_TYPE_SUBTITLE:
+ {
+ if (job->mux == HB_MUX_AV_MP4)
+ {
+ /* Write an empty sample */
+ if ( track->duration < pts )
+ {
+ AVPacket empty_pkt;
+ uint8_t empty[2] = {0,0};
+
+ av_init_packet(&empty_pkt);
+ empty_pkt.data = empty;
+ empty_pkt.size = 2;
+ empty_pkt.dts = track->duration;
+ empty_pkt.pts = track->duration;
+ empty_pkt.duration = pts - duration;
+ empty_pkt.convergence_duration = empty_pkt.duration;
+ empty_pkt.stream_index = track->st->index;
+ int ret = av_interleaved_write_frame(m->oc, &empty_pkt);
+ if (ret < 0)
+ {
+ hb_error("av_interleaved_write_frame failed!");
+ *job->die = 1;
+ return -1;
+ }
+ track->duration = pts;
+ }
+ uint8_t styleatom[2048];;
+ uint16_t stylesize = 0;
+ uint8_t buffer[2048];
+ uint16_t buffersize = 0;
+
+ *buffer = '\0';
+
+ /*
+ * Copy the subtitle into buffer stripping markup and creating
+ * style atoms for them.
+ */
+ hb_muxmp4_process_subtitle_style( buf->data,
+ buffer,
+ styleatom, &stylesize );
+
+ buffersize = strlen((char*)buffer);
+
+ /* Write the subtitle sample */
+ memcpy( tx3g_out + 2, buffer, buffersize );
+ memcpy( tx3g_out + 2 + buffersize, styleatom, stylesize);
+ tx3g_out[0] = ( buffersize >> 8 ) & 0xff;
+ tx3g_out[1] = buffersize & 0xff;
+ pkt.data = tx3g_out;
+ pkt.size = buffersize + stylesize + 2;
+ }
+ pkt.convergence_duration = pkt.duration;
+
+ } break;
+ case MUX_TYPE_AUDIO:
+ default:
+ break;
+ }
+
+ pkt.stream_index = track->st->index;
+ int ret = av_interleaved_write_frame(m->oc, &pkt);
+ if (ret < 0)
+ {
+ hb_error("av_interleaved_write_frame failed!");
+ *job->die = 1;
+ return -1;
+ }
+
+ hb_buffer_close( &buf );
+ return 0;
+}
+
+static int avformatEnd(hb_mux_object_t *m)
+{
+ hb_job_t *job = m->job;
+ hb_mux_data_t *track = job->mux_data;
+
+ if( !job->mux_data )
+ {
+ /*
+ * We must have failed to create the file in the first place.
+ */
+ return 0;
+ }
+
+ // Flush any delayed frames
+ int ii;
+ for (ii = 0; ii < m->ntracks; ii++)
+ {
+ avformatMux(m, m->tracks[ii], NULL);
+ }
+
+ if (job->chapter_markers)
+ {
+ hb_chapter_t *chapter;
+
+ // get the last chapter
+ chapter = hb_list_item(job->list_chapter, track->current_chapter++);
+
+ // only write the last chapter marker if it lasts at least 1.5 second
+ if (chapter != NULL && chapter->duration > 135000LL)
+ {
+ char title[1024];
+ if (chapter->title != NULL)
+ {
+ snprintf(title, 1023, "%s", chapter->title);
+ }
+ else
+ {
+ snprintf(title, 1023, "Chapter %d", track->current_chapter);
+ }
+ add_chapter(m, track->prev_chapter_tc, track->duration, title);
+ }
+ }
+
+ // Update and track private data that can change during
+ // encode.
+ for(ii = 0; ii < hb_list_count( job->list_audio ); ii++)
+ {
+ AVStream *st;
+ hb_audio_t * audio;
+
+ audio = hb_list_item(job->list_audio, ii);
+ st = audio->priv.mux_data->st;
+
+ switch (audio->config.out.codec & HB_ACODEC_MASK)
+ {
+ case HB_ACODEC_FFFLAC:
+ case HB_ACODEC_FFFLAC24:
+ if( audio->priv.config.extradata.bytes )
+ {
+ uint8_t *priv_data;
+ int priv_size;
+
+ priv_size = audio->priv.config.extradata.length;
+ priv_data = av_realloc(st->codec->extradata, priv_size);
+ if (priv_data == NULL)
+ {
+ break;
+ }
+ memcpy(priv_data,
+ audio->priv.config.extradata.bytes,
+ audio->priv.config.extradata.length);
+ st->codec->extradata = priv_data;
+ st->codec->extradata_size = priv_size;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ av_write_trailer(m->oc);
+ avio_close(m->oc->pb);
+ avformat_free_context(m->oc);
+ m->oc = NULL;
+
+ return 0;
+}
+
+hb_mux_object_t * hb_mux_avformat_init( hb_job_t * job )
+{
+ hb_mux_object_t * m = calloc( sizeof( hb_mux_object_t ), 1 );
+ m->init = avformatInit;
+ m->mux = avformatMux;
+ m->end = avformatEnd;
+ m->job = job;
+ return m;
+}
+
+#endif // USE_AVFORMAT
diff --git a/libhb/muxcommon.c b/libhb/muxcommon.c
index a97ada699..9417c707b 100644
--- a/libhb/muxcommon.c
+++ b/libhb/muxcommon.c
@@ -459,12 +459,22 @@ hb_work_object_t * hb_muxer_init( hb_job_t * job )
{
switch( job->mux )
{
- case HB_MUX_MP4:
+#ifdef USE_MP4V2
+ case HB_MUX_MP4V2:
mux->m = hb_mux_mp4_init( job );
break;
- case HB_MUX_MKV:
+#endif
+#ifdef USE_LIBMKV
+ case HB_MUX_LIBMKV:
mux->m = hb_mux_mkv_init( job );
break;
+#endif
+#ifdef USE_AVFORMAT
+ case HB_MUX_AV_MP4:
+ case HB_MUX_AV_MKV:
+ mux->m = hb_mux_avformat_init( job );
+ break;
+#endif
default:
hb_error( "No muxer selected, exiting" );
*job->die = 1;
@@ -544,3 +554,245 @@ hb_work_object_t hb_muxer =
muxClose
};
+typedef struct stylerecord_s {
+ enum style_s {ITALIC, BOLD, UNDERLINE} style;
+ uint16_t start;
+ uint16_t stop;
+ struct stylerecord_s *next;
+} stylerecord;
+
+static void hb_makestylerecord( stylerecord **stack,
+ enum style_s style, int start )
+{
+ stylerecord *record = calloc( sizeof( stylerecord ), 1 );
+
+ if( record )
+ {
+ record->style = style;
+ record->start = start;
+ record->next = *stack;
+ *stack = record;
+ }
+}
+
+static void hb_makestyleatom( stylerecord *record, uint8_t *style)
+{
+ uint8_t face = 1;
+ hb_deep_log(3, "Made style '%s' from %d to %d",
+ record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop);
+
+ switch( record->style )
+ {
+ case ITALIC:
+ face = 2;
+ break;
+ case BOLD:
+ face = 1;
+ break;
+ case UNDERLINE:
+ face = 4;
+ break;
+ default:
+ face = 2;
+ break;
+ }
+
+ style[0] = (record->start >> 8) & 0xff; // startChar
+ style[1] = record->start & 0xff;
+ style[2] = (record->stop >> 8) & 0xff; // endChar
+ style[3] = record->stop & 0xff;
+ style[4] = (1 >> 8) & 0xff; // font-ID
+ style[5] = 1 & 0xff;
+ style[6] = face; // face-style-flags: 1 bold; 2 italic; 4 underline
+ style[7] = 24; // font-size
+ style[8] = 255; // r
+ style[9] = 255; // g
+ style[10] = 255; // b
+ style[11] = 255; // a
+}
+
+/*
+ * Copy the input to output removing markup and adding markup to the style
+ * atom where appropriate.
+ */
+void hb_muxmp4_process_subtitle_style( uint8_t *input,
+ uint8_t *output,
+ uint8_t *style, uint16_t *stylesize )
+{
+ uint8_t *reader = input;
+ uint8_t *writer = output;
+ uint8_t stylecount = 0;
+ uint16_t utf8_count = 0; // utf8 count from start of subtitle
+ stylerecord *stylestack = NULL;
+ stylerecord *oldrecord = NULL;
+
+ while(*reader != '\0') {
+ if( ( *reader & 0xc0 ) == 0x80 )
+ {
+ /*
+ * Track the utf8_count when doing markup so that we get the tx3g stops
+ * based on UTF8 chr counts rather than bytes.
+ */
+ utf8_count++;
+ hb_deep_log( 3, "MuxMP4: Counted %d UTF-8 chrs within subtitle so far",
+ utf8_count);
+ }
+ if (*reader == '<') {
+ /*
+ * possible markup, peek at the next chr
+ */
+ switch(*(reader+1)) {
+ case 'i':
+ if (*(reader+2) == '>') {
+ reader += 3;
+ hb_makestylerecord(&stylestack, ITALIC, (writer - output - utf8_count));
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ case 'b':
+ if (*(reader+2) == '>') {
+ reader += 3;
+ hb_makestylerecord(&stylestack, BOLD, (writer - output - utf8_count));
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ case 'u':
+ if (*(reader+2) == '>') {
+ reader += 3;
+ hb_makestylerecord(&stylestack, UNDERLINE, (writer - output - utf8_count));
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ case '/':
+ switch(*(reader+2)) {
+ case 'i':
+ if (*(reader+3) == '>') {
+ /*
+ * Check whether we then immediately start more markup of the same type, if so then
+ * lets not close it now and instead continue this markup.
+ */
+ if ((*(reader+4) && *(reader+4) == '<') &&
+ (*(reader+5) && *(reader+5) == 'i') &&
+ (*(reader+6) && *(reader+6) == '>')) {
+ /*
+ * Opening italics right after, so don't close off these italics.
+ */
+ hb_deep_log(3, "Joining two sets of italics");
+ reader += (4 + 3);
+ continue;
+ }
+
+
+ if ((*(reader+4) && *(reader+4) == ' ') &&
+ (*(reader+5) && *(reader+5) == '<') &&
+ (*(reader+6) && *(reader+6) == 'i') &&
+ (*(reader+7) && *(reader+7) == '>')) {
+ /*
+ * Opening italics right after, so don't close off these italics.
+ */
+ hb_deep_log(3, "Joining two sets of italics (plus space)");
+ reader += (4 + 4);
+ *writer++ = ' ';
+ continue;
+ }
+ if (stylestack && stylestack->style == ITALIC) {
+ uint8_t style_record[12];
+ stylestack->stop = writer - output - utf8_count;
+ hb_makestyleatom(stylestack, style_record);
+
+ memcpy(style + 10 + (12 * stylecount), style_record, 12);
+ stylecount++;
+
+ oldrecord = stylestack;
+ stylestack = stylestack->next;
+ free(oldrecord);
+ } else {
+ hb_error("Mismatched Subtitle markup '%s'", input);
+ }
+ reader += 4;
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ case 'b':
+ if (*(reader+3) == '>') {
+ if (stylestack && stylestack->style == BOLD) {
+ uint8_t style_record[12];
+ stylestack->stop = writer - output - utf8_count;
+ hb_makestyleatom(stylestack, style_record);
+
+ memcpy(style + 10 + (12 * stylecount), style_record, 12);
+ stylecount++;
+ oldrecord = stylestack;
+ stylestack = stylestack->next;
+ free(oldrecord);
+ } else {
+ hb_error("Mismatched Subtitle markup '%s'", input);
+ }
+
+ reader += 4;
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ case 'u':
+ if (*(reader+3) == '>') {
+ if (stylestack && stylestack->style == UNDERLINE) {
+ uint8_t style_record[12];
+ stylestack->stop = writer - output - utf8_count;
+ hb_makestyleatom(stylestack, style_record);
+
+ memcpy(style + 10 + (12 * stylecount), style_record, 12);
+ stylecount++;
+
+ oldrecord = stylestack;
+ stylestack = stylestack->next;
+ free(oldrecord);
+ } else {
+ hb_error("Mismatched Subtitle markup '%s'", input);
+ }
+ reader += 4;
+ } else {
+ *writer++ = *reader++;
+ }
+ break;
+ default:
+ *writer++ = *reader++;
+ break;
+ }
+ break;
+ default:
+ *writer++ = *reader++;
+ break;
+ }
+ } else if (*reader == '\r') {
+ // skip '\r' and replace with '\n' if necessary
+ if (*(++reader) != '\n') {
+ *writer++ = '\n';
+ }
+ } else {
+ *writer++ = *reader++;
+ }
+ }
+ *writer = '\0';
+
+ if( stylecount )
+ {
+ *stylesize = 10 + ( stylecount * 12 );
+
+ memcpy( style + 4, "styl", 4);
+
+ style[0] = 0;
+ style[1] = 0;
+ style[2] = (*stylesize >> 8) & 0xff;
+ style[3] = *stylesize & 0xff;
+ style[8] = (stylecount >> 8) & 0xff;
+ style[9] = stylecount & 0xff;
+
+ }
+
+}
+
diff --git a/libhb/muxmkv.c b/libhb/muxmkv.c
index a170eb557..ca72a7be8 100644
--- a/libhb/muxmkv.c
+++ b/libhb/muxmkv.c
@@ -8,6 +8,9 @@
*/
/* libmkv header */
+
+#if defined(USE_LIBMKV)
+
#include "libmkv.h"
#include <ogg/ogg.h>
@@ -17,15 +20,16 @@
/* Scale factor to apply to timecodes to convert from HandBrake's
* 1/90000s to nanoseconds as expected by libmkv */
-#define TIMECODE_SCALE 1000000000 / 90000
+#define NANOSECOND_SCALE 1000000000L
+#define TIMECODE_SCALE 1000000000L / 90000
struct hb_mux_object_s
{
HB_MUX_COMMON;
- hb_job_t * job;
-
+ hb_job_t * job;
mk_Writer * file;
+ int delay;
};
struct hb_mux_data_s
@@ -202,6 +206,9 @@ static int MKVInit( hb_mux_object_t * m )
mux_data = calloc(1, sizeof( hb_mux_data_t ) );
audio->priv.mux_data = mux_data;
+ if (audio->config.out.delay > m->delay)
+ m->delay = audio->config.out.delay;
+
mux_data->codec = audio->config.out.codec;
memset(track, 0, sizeof(mk_TrackConfig));
@@ -440,14 +447,13 @@ static int MKVMux(hb_mux_object_t *m, hb_mux_data_t *mux_data, hb_buffer_t *buf)
char chapter_name[1024];
hb_chapter_t *chapter_data;
uint64_t timecode = 0;
- ogg_packet *op = NULL;
hb_job_t *job = m->job;
+ // Adjust for audio preroll and scale units
+ timecode = (buf->s.start + m->delay) * TIMECODE_SCALE;
if (mux_data == job->mux_data)
{
/* Video */
- timecode = buf->s.start * TIMECODE_SCALE;
-
if (job->chapter_markers && buf->s.new_chap)
{
// reached chapter N, write marker for chapter N-1
@@ -475,22 +481,6 @@ static int MKVMux(hb_mux_object_t *m, hb_mux_data_t *mux_data, hb_buffer_t *buf)
}
mux_data->prev_chapter_tc = timecode;
}
-
- if (job->vcodec == HB_VCODEC_THEORA)
- {
- /* ughhh, theora is a pain :( */
- op = (ogg_packet *)buf->data;
- op->packet = buf->data + sizeof( ogg_packet );
- if (mk_startFrame(m->file, mux_data->track) < 0)
- {
- hb_error( "Failed to write frame to output file, Disk Full?" );
- *job->die = 1;
- }
- mk_addFrameData(m->file, mux_data->track, op->packet, op->bytes);
- mk_setFrameFlags(m->file, mux_data->track, timecode, 1, 0);
- hb_buffer_close( &buf );
- return 0;
- }
}
else if (mux_data->subtitle)
{
@@ -500,14 +490,13 @@ static int MKVMux(hb_mux_object_t *m, hb_mux_data_t *mux_data, hb_buffer_t *buf)
*job->die = 1;
}
uint64_t duration;
- timecode = buf->s.start * TIMECODE_SCALE;
- if (buf->s.stop <= buf->s.start)
+ if (buf->s.duration < 0)
{
- duration = 0;
+ duration = 10 * NANOSECOND_SCALE;
}
else
{
- duration = buf->s.stop * TIMECODE_SCALE - timecode;
+ duration = buf->s.duration * TIMECODE_SCALE;
}
mk_addFrameData(m->file, mux_data->track, buf->data, buf->size);
mk_setFrameFlags(m->file, mux_data->track, timecode, 1, duration);
@@ -518,22 +507,6 @@ static int MKVMux(hb_mux_object_t *m, hb_mux_data_t *mux_data, hb_buffer_t *buf)
else
{
/* Audio */
- timecode = buf->s.start * TIMECODE_SCALE;
- if (mux_data->codec == HB_ACODEC_VORBIS)
- {
- /* ughhh, vorbis is a pain :( */
- op = (ogg_packet *)buf->data;
- op->packet = buf->data + sizeof( ogg_packet );
- if (mk_startFrame(m->file, mux_data->track))
- {
- hb_error( "Failed to write frame to output file, Disk Full?" );
- *job->die = 1;
- }
- mk_addFrameData(m->file, mux_data->track, op->packet, op->bytes);
- mk_setFrameFlags(m->file, mux_data->track, timecode, 1, 0);
- hb_buffer_close( &buf );
- return 0;
- }
}
if( mk_startFrame(m->file, mux_data->track) < 0)
@@ -690,3 +663,4 @@ hb_mux_object_t * hb_mux_mkv_init( hb_job_t * job )
m->job = job;
return m;
}
+#endif // USE_LIBMKV
diff --git a/libhb/muxmp4.c b/libhb/muxmp4.c
index 66f02d5c7..e544c26be 100644
--- a/libhb/muxmp4.c
+++ b/libhb/muxmp4.c
@@ -7,11 +7,13 @@
For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html
*/
+#include "hb.h"
+
+#if defined(USE_MP4V2)
+
#include "mp4v2/mp4v2.h"
#include "a52dec/a52.h"
-#include "hb.h"
-
struct hb_mux_object_s
{
HB_MUX_COMMON;
@@ -658,257 +660,24 @@ static int MP4Init( hb_mux_object_t * m )
return 0;
}
-typedef struct stylerecord_s {
- enum style_s {ITALIC, BOLD, UNDERLINE} style;
- uint16_t start;
- uint16_t stop;
- struct stylerecord_s *next;
-} stylerecord;
-
-static void hb_makestylerecord( stylerecord **stack,
- enum style_s style, int start )
-{
- stylerecord *record = calloc( sizeof( stylerecord ), 1 );
-
- if( record )
- {
- record->style = style;
- record->start = start;
- record->next = *stack;
- *stack = record;
- }
-}
-
-static void hb_makestyleatom( stylerecord *record, uint8_t *style)
-{
- uint8_t face = 1;
- hb_deep_log(3, "Made style '%s' from %d to %d",
- record->style == ITALIC ? "Italic" : record->style == BOLD ? "Bold" : "Underline", record->start, record->stop);
-
- switch( record->style )
- {
- case ITALIC:
- face = 2;
- break;
- case BOLD:
- face = 1;
- break;
- case UNDERLINE:
- face = 4;
- break;
- default:
- face = 2;
- break;
- }
-
- style[0] = (record->start >> 8) & 0xff; // startChar
- style[1] = record->start & 0xff;
- style[2] = (record->stop >> 8) & 0xff; // endChar
- style[3] = record->stop & 0xff;
- style[4] = (1 >> 8) & 0xff; // font-ID
- style[5] = 1 & 0xff;
- style[6] = face; // face-style-flags: 1 bold; 2 italic; 4 underline
- style[7] = 24; // font-size
- style[8] = 255; // r
- style[9] = 255; // g
- style[10] = 255; // b
- style[11] = 255; // a
-
-}
-
-/*
- * Copy the input to output removing markup and adding markup to the style
- * atom where appropriate.
- */
-static void hb_muxmp4_process_subtitle_style( uint8_t *input,
- uint8_t *output,
- uint8_t *style, uint16_t *stylesize )
-{
- uint8_t *reader = input;
- uint8_t *writer = output;
- uint8_t stylecount = 0;
- uint16_t utf8_count = 0; // utf8 count from start of subtitle
- stylerecord *stylestack = NULL;
- stylerecord *oldrecord = NULL;
-
- while(*reader != '\0') {
- if( ( *reader & 0xc0 ) == 0x80 )
- {
- /*
- * Track the utf8_count when doing markup so that we get the tx3g stops
- * based on UTF8 chr counts rather than bytes.
- */
- utf8_count++;
- hb_deep_log( 3, "MuxMP4: Counted %d UTF-8 chrs within subtitle so far",
- utf8_count);
- }
- if (*reader == '<') {
- /*
- * possible markup, peek at the next chr
- */
- switch(*(reader+1)) {
- case 'i':
- if (*(reader+2) == '>') {
- reader += 3;
- hb_makestylerecord(&stylestack, ITALIC, (writer - output - utf8_count));
- } else {
- *writer++ = *reader++;
- }
- break;
- case 'b':
- if (*(reader+2) == '>') {
- reader += 3;
- hb_makestylerecord(&stylestack, BOLD, (writer - output - utf8_count));
- } else {
- *writer++ = *reader++;
- }
- break;
- case 'u':
- if (*(reader+2) == '>') {
- reader += 3;
- hb_makestylerecord(&stylestack, UNDERLINE, (writer - output - utf8_count));
- } else {
- *writer++ = *reader++;
- }
- break;
- case '/':
- switch(*(reader+2)) {
- case 'i':
- if (*(reader+3) == '>') {
- /*
- * Check whether we then immediately start more markup of the same type, if so then
- * lets not close it now and instead continue this markup.
- */
- if ((*(reader+4) && *(reader+4) == '<') &&
- (*(reader+5) && *(reader+5) == 'i') &&
- (*(reader+6) && *(reader+6) == '>')) {
- /*
- * Opening italics right after, so don't close off these italics.
- */
- hb_deep_log(3, "Joining two sets of italics");
- reader += (4 + 3);
- continue;
- }
-
-
- if ((*(reader+4) && *(reader+4) == ' ') &&
- (*(reader+5) && *(reader+5) == '<') &&
- (*(reader+6) && *(reader+6) == 'i') &&
- (*(reader+7) && *(reader+7) == '>')) {
- /*
- * Opening italics right after, so don't close off these italics.
- */
- hb_deep_log(3, "Joining two sets of italics (plus space)");
- reader += (4 + 4);
- *writer++ = ' ';
- continue;
- }
- if (stylestack && stylestack->style == ITALIC) {
- uint8_t style_record[12];
- stylestack->stop = writer - output - utf8_count;
- hb_makestyleatom(stylestack, style_record);
-
- memcpy(style + 10 + (12 * stylecount), style_record, 12);
- stylecount++;
-
- oldrecord = stylestack;
- stylestack = stylestack->next;
- free(oldrecord);
- } else {
- hb_error("Mismatched Subtitle markup '%s'", input);
- }
- reader += 4;
- } else {
- *writer++ = *reader++;
- }
- break;
- case 'b':
- if (*(reader+3) == '>') {
- if (stylestack && stylestack->style == BOLD) {
- uint8_t style_record[12];
- stylestack->stop = writer - output - utf8_count;
- hb_makestyleatom(stylestack, style_record);
-
- memcpy(style + 10 + (12 * stylecount), style_record, 12);
- stylecount++;
- oldrecord = stylestack;
- stylestack = stylestack->next;
- free(oldrecord);
- } else {
- hb_error("Mismatched Subtitle markup '%s'", input);
- }
-
- reader += 4;
- } else {
- *writer++ = *reader++;
- }
- break;
- case 'u':
- if (*(reader+3) == '>') {
- if (stylestack && stylestack->style == UNDERLINE) {
- uint8_t style_record[12];
- stylestack->stop = writer - output - utf8_count;
- hb_makestyleatom(stylestack, style_record);
-
- memcpy(style + 10 + (12 * stylecount), style_record, 12);
- stylecount++;
-
- oldrecord = stylestack;
- stylestack = stylestack->next;
- free(oldrecord);
- } else {
- hb_error("Mismatched Subtitle markup '%s'", input);
- }
- reader += 4;
- } else {
- *writer++ = *reader++;
- }
- break;
- default:
- *writer++ = *reader++;
- break;
- }
- break;
- default:
- *writer++ = *reader++;
- break;
- }
- } else if (*reader == '\r') {
- // skip '\r' and replace with '\n' if necessary
- if (*(++reader) != '\n') {
- *writer++ = '\n';
- }
- } else {
- *writer++ = *reader++;
- }
- }
- *writer = '\0';
-
- if( stylecount )
- {
- *stylesize = 10 + ( stylecount * 12 );
-
- memcpy( style + 4, "styl", 4);
-
- style[0] = 0;
- style[1] = 0;
- style[2] = (*stylesize >> 8) & 0xff;
- style[3] = *stylesize & 0xff;
- style[8] = (stylecount >> 8) & 0xff;
- style[9] = stylecount & 0xff;
-
- }
-
-}
-
static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
hb_buffer_t * buf )
{
hb_job_t * job = m->job;
- int64_t duration;
+ int64_t duration, stop = -1;
int64_t offset = 0;
hb_buffer_t *tmp;
+ if (buf->s.duration >= 0)
+ {
+ stop = buf->s.start + buf->s.duration;
+ }
+ else if (mux_data->subtitle)
+ {
+ buf->s.duration = 10 * 90000;
+ stop = buf->s.start + buf->s.duration;
+ }
+
if( mux_data == job->mux_data )
{
/* Video */
@@ -980,7 +749,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
}
else
{
- duration = buf->s.stop - m->sum_dur;
+ duration = stop - m->sum_dur;
// Due to how libx264 generates DTS, it's possible for the
// above calculation to be negative.
//
@@ -1016,7 +785,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
// We're getting the frames in decode order but the timestamps are
// for presentation so we have to use durations and effectively
// compute a DTS.
- duration = buf->s.stop - buf->s.start;
+ duration = buf->s.duration;
}
if ( duration <= 0 )
@@ -1027,7 +796,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
try to fix the error so that the file will still be playable. */
hb_log("MP4Mux: illegal duration %"PRId64", start %"PRId64","
"stop %"PRId64", sum_dur %"PRId64,
- duration, buf->s.start, buf->s.stop, m->sum_dur );
+ duration, buf->s.start, stop, m->sum_dur );
/* we don't know when the next frame starts so we can't pick a
valid duration for this one. we pick something "short"
(roughly 1/3 of an NTSC frame time) to take time from
@@ -1111,11 +880,11 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
code should coalesce overlapping subtitle lines. */
if( buf->s.start < mux_data->sum_dur )
{
- if ( buf->s.stop - mux_data->sum_dur > 90*500 )
+ if ( stop - mux_data->sum_dur > 90*500 )
{
hb_log("MP4Mux: shortening overlapping subtitle, "
"start %"PRId64", stop %"PRId64", sum_dur %"PRId64,
- buf->s.start, buf->s.stop, m->sum_dur);
+ buf->s.start, stop, m->sum_dur);
buf->s.start = mux_data->sum_dur;
}
}
@@ -1123,20 +892,10 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
{
hb_log("MP4Mux: skipping overlapping subtitle, "
"start %"PRId64", stop %"PRId64", sum_dur %"PRId64,
- buf->s.start, buf->s.stop, m->sum_dur);
+ buf->s.start, stop, m->sum_dur);
}
else
{
- int64_t duration;
-
- if( buf->s.start < 0 )
- buf->s.start = mux_data->sum_dur;
-
- if( buf->s.stop < 0 )
- duration = 90000L * 10;
- else
- duration = buf->s.stop - buf->s.start;
-
/* Write an empty sample */
if ( mux_data->sum_dur < buf->s.start )
{
@@ -1172,9 +931,9 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
buffersize = strlen((char*)buffer);
- hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%"PRId64": %s",
- (float)buf->s.start / 90000, buf->s.start, buf->s.stop,
- duration, buffer);
+ hb_deep_log(3, "MuxMP4:Sub:%fs:%"PRId64":%"PRId64":%f: %s",
+ (float)buf->s.start / 90000, buf->s.start, stop,
+ buf->s.duration, buffer);
/* Write the subtitle sample */
memcpy( output + 2, buffer, buffersize );
@@ -1186,7 +945,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
mux_data->track,
output,
buffersize + stylesize + 2,
- duration,
+ buf->s.duration,
0,
1 ))
{
@@ -1194,21 +953,11 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
*job->die = 1;
}
- mux_data->sum_dur += duration;
+ mux_data->sum_dur += buf->s.duration;
}
}
else if( mux_data->sub_format == PICTURESUB )
{
- int64_t duration;
-
- if( buf->s.start < 0 )
- buf->s.start = mux_data->sum_dur;
-
- if( buf->s.stop < 0 )
- duration = 90000L * 10;
- else
- duration = buf->s.stop - buf->s.start;
-
/* Write an empty sample */
if ( mux_data->sum_dur < buf->s.start )
{
@@ -1230,7 +979,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
mux_data->track,
buf->data,
buf->size,
- duration,
+ buf->s.duration,
0,
1 ))
{
@@ -1238,7 +987,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
*job->die = 1;
}
- mux_data->sum_dur += duration;
+ mux_data->sum_dur += buf->s.duration;
}
}
else
@@ -1266,6 +1015,7 @@ static int MP4Mux( hb_mux_object_t * m, hb_mux_data_t * mux_data,
static int MP4End( hb_mux_object_t * m )
{
hb_job_t * job = m->job;
+ int i;
if (m->file != MP4_INVALID_FILE_HANDLE)
{
@@ -1295,19 +1045,32 @@ static int MP4End( hb_mux_object_t * m )
if ( job->config.h264.init_delay )
{
- // Insert track edit to get A/V back in sync. The edit amount is
- // the init_delay.
- int64_t edit_amt = job->config.h264.init_delay;
- MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
- MP4GetTrackDuration(m->file, 1), 0);
- if ( m->job->chapter_markers )
- {
- // apply same edit to chapter track to keep it in sync with video
- MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
- edit_amt,
- MP4GetTrackDuration(m->file, m->chapter_track), 0);
- }
- }
+ // Insert track edit to get A/V back in sync. The edit amount is
+ // the init_delay.
+ int64_t edit_amt = job->config.h264.init_delay;
+ MP4AddTrackEdit(m->file, 1, MP4_INVALID_EDIT_ID, edit_amt,
+ MP4GetTrackDuration(m->file, 1), 0);
+ if ( m->job->chapter_markers )
+ {
+ // apply same edit to chapter track to keep it in sync with video
+ MP4AddTrackEdit(m->file, m->chapter_track, MP4_INVALID_EDIT_ID,
+ edit_amt,
+ MP4GetTrackDuration(m->file, m->chapter_track), 0);
+ }
+ }
+
+ // Check for audio preroll and add edit entries for audio
+ for( i = 0; i < hb_list_count( job->list_audio ); i++ )
+ {
+ hb_audio_t *audio = hb_list_item( job->list_audio, i );
+ hb_mux_data_t *mux_data = audio->priv.mux_data;
+ if (audio->config.out.delay > 0)
+ {
+ int64_t edit_amt = audio->config.out.delay;
+ MP4AddTrackEdit(m->file, mux_data->track, MP4_INVALID_EDIT_ID,
+ edit_amt, MP4GetTrackDuration(m->file, 1), 0);
+ }
+ }
/*
* Write the MP4 iTunes metadata if we have any metadata
@@ -1413,3 +1176,4 @@ hb_mux_object_t * hb_mux_mp4_init( hb_job_t * job )
return m;
}
+#endif // USE_MP4V2
diff --git a/libhb/sync.c b/libhb/sync.c
index a5158f738..e3d44694b 100644
--- a/libhb/sync.c
+++ b/libhb/sync.c
@@ -337,8 +337,14 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
}
}
hb_lock( pv->common->mutex );
- // Tell the audio threads what must be dropped
- pv->common->audio_pts_thresh = next_start + pv->common->video_pts_slip;
+ if (job->frame_to_start > 0)
+ {
+ // When doing frame based p-to-p we must update the audio
+ // start point with each frame skipped.
+ //
+ // Tell the audio threads what must be dropped
+ pv->common->audio_pts_thresh = next->s.start;
+ }
hb_cond_broadcast( pv->common->next_frame );
hb_unlock( pv->common->mutex );
@@ -553,7 +559,7 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
for( i = 0; i < hb_list_count( job->list_subtitle ); i++)
{
- int64_t sub_start, sub_stop, duration;
+ int64_t sub_start, sub_stop, sub_dts, duration;
subtitle = hb_list_item( job->list_subtitle, i );
@@ -563,6 +569,10 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
{
hb_lock( pv->common->mutex );
sub_start = sub->s.start - pv->common->video_pts_slip;
+ if (sub->s.renderOffset != -1)
+ sub_dts = sub->s.renderOffset - pv->common->video_pts_slip;
+ else
+ sub_dts = -1;
hb_unlock( pv->common->mutex );
if (sub->s.stop == -1)
@@ -595,6 +605,7 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
// Need to re-write subtitle timestamps to account
// for any slippage.
sub_stop = -1;
+ duration = -1;
if ( sub->s.stop != -1 )
{
duration = sub->s.stop - sub->s.start;
@@ -602,7 +613,9 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
}
sub->s.start = sub_start;
+ sub->s.renderOffset = sub_dts;
sub->s.stop = sub_stop;
+ sub->s.duration = duration;
hb_fifo_push( subtitle->fifo_out, sub );
}
@@ -626,6 +639,8 @@ int syncVideoWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
sync->cur = cur = next;
cur->sub = NULL;
cur->s.start -= pv->common->video_pts_slip;
+ if (cur->s.renderOffset != -1)
+ cur->s.renderOffset -= pv->common->video_pts_slip;
cur->s.stop -= pv->common->video_pts_slip;
sync->pts_skip = 0;
if ( duration <= 0 )
@@ -758,9 +773,15 @@ static int syncAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_unlock( pv->common->mutex );
}
- /* Wait for start frame if doing point-to-point */
+ // Wait for start frame if doing point-to-point
+ //
+ // When doing p-to-p, video leads the way. The video thead will set
+ // start_found when we have reached the start point.
+ //
+ // When doing frame based p-to-p, as each video frame is processed
+ // it advances audio_pts_thresh which informs us how much audio must
+ // be dropped.
hb_lock( pv->common->mutex );
- start = buf->s.start - pv->common->audio_pts_slip;
while ( !pv->common->start_found && !*w->done )
{
if ( pv->common->audio_pts_thresh < 0 )
@@ -776,6 +797,12 @@ static int syncAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
hb_unlock( pv->common->mutex );
return HB_WORK_OK;
}
+
+ // We should only get here when doing frame based p-to-p.
+ // In frame based p-to-p, the video sync thread updates
+ // audio_pts_thresh as it discards frames. So wait here
+ // until the current audio frame needs to be discarded
+ // or start point is found.
while ( !pv->common->start_found &&
buf->s.start >= pv->common->audio_pts_thresh && !*w->done )
{
@@ -804,15 +831,20 @@ static int syncAudioWork( hb_work_object_t * w, hb_buffer_t ** buf_in,
}
}
}
- start = buf->s.start - pv->common->audio_pts_slip;
}
- if ( start < 0 )
+ start = buf->s.start - pv->common->audio_pts_slip;
+ hb_unlock( pv->common->mutex );
+
+ // When doing p-to-p, video determines when the start point has been
+ // found. Since audio and video are processed asynchronously, there
+ // may yet be audio packets in the pipe that are before the start point.
+ // These packets will have negative start times after applying
+ // audio_pts_slip.
+ if (start < 0)
{
- hb_buffer_close( &buf );
- hb_unlock( pv->common->mutex );
+ hb_buffer_close(&buf);
return HB_WORK_OK;
}
- hb_unlock( pv->common->mutex );
if( job->frame_to_stop && pv->common->count_frames >= job->frame_to_stop )
{
diff --git a/libhb/work.c b/libhb/work.c
index ceab14b1f..df22d791e 100644
--- a/libhb/work.c
+++ b/libhb/work.c
@@ -210,6 +210,7 @@ void hb_display_job_info(hb_job_t *job)
hb_log(" + container: %s", hb_container_get_name(job->mux));
switch (job->mux)
{
+ case HB_MUX_AV_MP4:
case HB_MUX_MP4V2:
if (job->largeFileSize)
hb_log(" + 64-bit chunk offsets");
diff --git a/macosx/Controller.m b/macosx/Controller.m
index d5dd32bff..f5b9d7741 100644
--- a/macosx/Controller.m
+++ b/macosx/Controller.m
@@ -4894,8 +4894,8 @@ bool one_burned = FALSE;
//[self calculateBitrate: sender];
/* We're changing the chapter range - we may need to flip the m4v/mp4 extension */
- if ([fDstFormatPopUp indexOfSelectedItem] == 0)
- [self autoSetM4vExtension: sender];
+ if ([[fDstFormatPopUp selectedItem] tag] & HB_MUX_MASK_MP4)
+ [self autoSetM4vExtension:sender];
}
- (IBAction) startEndSecValueChanged: (id) sender
@@ -5008,6 +5008,7 @@ bool one_burned = FALSE;
{
case HB_MUX_MP4V2:
[fDstMp4LargeFileCheck setHidden:NO];
+ case HB_MUX_AV_MP4:
[fDstMp4HttpOptFileCheck setHidden:NO];
[fDstMp4iPodFileCheck setHidden:NO];
break;
@@ -5051,7 +5052,7 @@ bool one_burned = FALSE;
- (IBAction) autoSetM4vExtension: (id) sender
{
- if ( [fDstFormatPopUp indexOfSelectedItem] )
+ if (!([[fDstFormatPopUp selectedItem] tag] & HB_MUX_MASK_MP4))
return;
NSString * extension = @"mp4";
diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj
index 6762c87e8..9cdb5465f 100644
--- a/macosx/HandBrake.xcodeproj/project.pbxproj
+++ b/macosx/HandBrake.xcodeproj/project.pbxproj
@@ -98,12 +98,8 @@
27D6C75814B102DA00B785E4 /* libfreetype.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73214B102DA00B785E4 /* libfreetype.a */; };
27D6C75914B102DA00B785E4 /* libfribidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73314B102DA00B785E4 /* libfribidi.a */; };
27D6C75A14B102DA00B785E4 /* libfribidi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73314B102DA00B785E4 /* libfribidi.a */; };
- 27D6C75B14B102DA00B785E4 /* libmkv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73414B102DA00B785E4 /* libmkv.a */; };
- 27D6C75C14B102DA00B785E4 /* libmkv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73414B102DA00B785E4 /* libmkv.a */; };
27D6C75E14B102DA00B785E4 /* libmp3lame.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73614B102DA00B785E4 /* libmp3lame.a */; };
27D6C75F14B102DA00B785E4 /* libmp3lame.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73614B102DA00B785E4 /* libmp3lame.a */; };
- 27D6C76014B102DA00B785E4 /* libmp4v2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73714B102DA00B785E4 /* libmp4v2.a */; };
- 27D6C76114B102DA00B785E4 /* libmp4v2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73714B102DA00B785E4 /* libmp4v2.a */; };
27D6C76214B102DA00B785E4 /* libmpeg2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73814B102DA00B785E4 /* libmpeg2.a */; };
27D6C76314B102DA00B785E4 /* libmpeg2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73814B102DA00B785E4 /* libmpeg2.a */; };
27D6C76414B102DA00B785E4 /* libogg.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27D6C73914B102DA00B785E4 /* libogg.a */; };
@@ -295,9 +291,7 @@
27D6C73114B102DA00B785E4 /* libfontconfig.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfontconfig.a; path = external/contrib/lib/libfontconfig.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73214B102DA00B785E4 /* libfreetype.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfreetype.a; path = external/contrib/lib/libfreetype.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73314B102DA00B785E4 /* libfribidi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libfribidi.a; path = external/contrib/lib/libfribidi.a; sourceTree = BUILT_PRODUCTS_DIR; };
- 27D6C73414B102DA00B785E4 /* libmkv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmkv.a; path = external/contrib/lib/libmkv.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73614B102DA00B785E4 /* libmp3lame.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmp3lame.a; path = external/contrib/lib/libmp3lame.a; sourceTree = BUILT_PRODUCTS_DIR; };
- 27D6C73714B102DA00B785E4 /* libmp4v2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmp4v2.a; path = external/contrib/lib/libmp4v2.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73814B102DA00B785E4 /* libmpeg2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libmpeg2.a; path = external/contrib/lib/libmpeg2.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73914B102DA00B785E4 /* libogg.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libogg.a; path = external/contrib/lib/libogg.a; sourceTree = BUILT_PRODUCTS_DIR; };
27D6C73A14B102DA00B785E4 /* libsamplerate.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libsamplerate.a; path = external/contrib/lib/libsamplerate.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -368,9 +362,7 @@
27D6C75614B102DA00B785E4 /* libfontconfig.a in Frameworks */,
27D6C75814B102DA00B785E4 /* libfreetype.a in Frameworks */,
27D6C75A14B102DA00B785E4 /* libfribidi.a in Frameworks */,
- 27D6C75C14B102DA00B785E4 /* libmkv.a in Frameworks */,
27D6C75F14B102DA00B785E4 /* libmp3lame.a in Frameworks */,
- 27D6C76114B102DA00B785E4 /* libmp4v2.a in Frameworks */,
27D6C76314B102DA00B785E4 /* libmpeg2.a in Frameworks */,
27D6C76514B102DA00B785E4 /* libogg.a in Frameworks */,
27D6C76714B102DA00B785E4 /* libsamplerate.a in Frameworks */,
@@ -410,9 +402,7 @@
27D6C75514B102DA00B785E4 /* libfontconfig.a in Frameworks */,
27D6C75714B102DA00B785E4 /* libfreetype.a in Frameworks */,
27D6C75914B102DA00B785E4 /* libfribidi.a in Frameworks */,
- 27D6C75B14B102DA00B785E4 /* libmkv.a in Frameworks */,
27D6C75E14B102DA00B785E4 /* libmp3lame.a in Frameworks */,
- 27D6C76014B102DA00B785E4 /* libmp4v2.a in Frameworks */,
27D6C76214B102DA00B785E4 /* libmpeg2.a in Frameworks */,
27D6C76414B102DA00B785E4 /* libogg.a in Frameworks */,
27D6C76614B102DA00B785E4 /* libsamplerate.a in Frameworks */,
@@ -444,9 +434,7 @@
27D6C73114B102DA00B785E4 /* libfontconfig.a */,
27D6C73214B102DA00B785E4 /* libfreetype.a */,
27D6C73314B102DA00B785E4 /* libfribidi.a */,
- 27D6C73414B102DA00B785E4 /* libmkv.a */,
27D6C73614B102DA00B785E4 /* libmp3lame.a */,
- 27D6C73714B102DA00B785E4 /* libmp4v2.a */,
27D6C73814B102DA00B785E4 /* libmpeg2.a */,
27D6C73914B102DA00B785E4 /* libogg.a */,
27D6C73A14B102DA00B785E4 /* libsamplerate.a */,
diff --git a/macosx/module.defs b/macosx/module.defs
index 1a2b6f81d..5d22164f6 100644
--- a/macosx/module.defs
+++ b/macosx/module.defs
@@ -32,6 +32,14 @@ endif
ifeq (1,$(FEATURE.faac))
extra_libs += $(abspath $(BUILD))/contrib/lib/libfaac.a
endif
+
+ifeq (1,$(FEATURE.mp4v2))
+ extra_libs += $(abspath $(BUILD))/contrib/lib/libmp4v2.a
+endif
+
+ifeq (1,$(FEATURE.libmkv))
+ extra_libs += $(abspath $(BUILD))/contrib/lib/libmkv.a
+endif
MACOSX.extra_ldflags = OTHER_LDFLAGS='$(extra_libs)'
## xcconfig: must be one of macosx/xcconfig/*.xcconfig
diff --git a/make/configure.py b/make/configure.py
index af893ddae..9a98017ce 100644
--- a/make/configure.py
+++ b/make/configure.py
@@ -1188,6 +1188,18 @@ def createCLI():
grp.add_option( '--enable-faac', dest="enable_faac", default=False, action='store_true', help=h )
grp.add_option( '--disable-faac', dest="enable_faac", action='store_false' )
+ h = IfHost( 'enable use of mp4v2 muxer', '*-*-*', none=optparse.SUPPRESS_HELP ).value
+ grp.add_option( '--enable-mp4v2', dest="enable_mp4v2", default=True, action='store_true', help=h )
+ grp.add_option( '--disable-mp4v2', dest="enable_mp4v2", action='store_false' )
+
+ h = IfHost( 'enable use of libmkv muxer', '*-*-*', none=optparse.SUPPRESS_HELP ).value
+ grp.add_option( '--enable-libmkv', dest="enable_libmkv", default=True, action='store_true', help=h )
+ grp.add_option( '--disable-libmkv', dest="enable_libmkv", action='store_false' )
+
+ h = IfHost( 'enable use of avformat muxer', '*-*-*', none=optparse.SUPPRESS_HELP ).value
+ grp.add_option( '--enable-avformat', dest="enable_avformat", default=True, action='store_true', help=h )
+ grp.add_option( '--disable-avformat', dest="enable_avformat", action='store_false' )
+
cli.add_option_group( grp )
## add launch options
@@ -1630,6 +1642,9 @@ int main ()
doc.add( 'FEATURE.fdk_aac', int( options.enable_fdk_aac ))
doc.add( 'FEATURE.libav_aac', int( options.enable_libav_aac ))
doc.add( 'FEATURE.faac', int( options.enable_faac ))
+ doc.add( 'FEATURE.mp4v2', int( options.enable_mp4v2 ))
+ doc.add( 'FEATURE.libmkv', int( options.enable_libmkv ))
+ doc.add( 'FEATURE.avformat', int( options.enable_avformat ))
doc.add( 'FEATURE.xcode', int( not (Tools.xcodebuild.fail or options.disable_xcode or options.cross) ))
if not Tools.xcodebuild.fail and not options.disable_xcode:
diff --git a/make/include/main.defs b/make/include/main.defs
index 08901bd27..2554c11e7 100644
--- a/make/include/main.defs
+++ b/make/include/main.defs
@@ -47,13 +47,19 @@ ifeq (1,$(FEATURE.faac))
MODULES += contrib/faac
endif
+ifeq (1,$(FEATURE.mp4v2))
+ MODULES += contrib/mp4v2
+endif
+
+ifeq (1,$(FEATURE.libmkv))
+ MODULES += contrib/libmkv
+endif
+
MODULES += contrib/lame
MODULES += contrib/ffmpeg
MODULES += contrib/libdvdread
MODULES += contrib/libdvdnav
MODULES += contrib/libbluray
-MODULES += contrib/libmkv
-MODULES += contrib/mp4v2
MODULES += contrib/mpeg2dec
ifneq (,$(filter $(BUILD.system),mingw))
diff --git a/test/module.defs b/test/module.defs
index 2150ed906..590c49ea2 100644
--- a/test/module.defs
+++ b/test/module.defs
@@ -15,7 +15,7 @@ TEST.libs = $(LIBHB.a)
TEST.GCC.l = \
a52 ass avcodec avformat avutil avresample dvdnav dvdread \
- fontconfig freetype fribidi mkv mpeg2 mp3lame mp4v2 ogg \
+ fontconfig freetype fribidi mpeg2 mp3lame ogg \
samplerate swscale theoraenc theoradec vorbis vorbisenc x264 \
bluray xml2 bz2 z
@@ -27,6 +27,14 @@ ifeq (1,$(FEATURE.faac))
TEST.GCC.l += faac
endif
+ifeq (1,$(FEATURE.mp4v2))
+TEST.GCC.l += mp4v2
+endif
+
+ifeq (1,$(FEATURE.libmkv))
+TEST.GCC.l += mkv
+endif
+
TEST.install.exe = $(DESTDIR)$(PREFIX/)bin/$(notdir $(TEST.exe))
###############################################################################
diff --git a/test/test.c b/test/test.c
index aaa68045e..e76258977 100644
--- a/test/test.c
+++ b/test/test.c
@@ -2712,7 +2712,7 @@ static int HandleEvents( hb_handle_t * h )
}
sub_config = subtitle->config;
- if( mux == HB_MUX_MKV || subtitle->format == TEXTSUB)
+ if ((mux & HB_MUX_MASK_MKV) || subtitle->format == TEXTSUB)
{
sub_config.dest = PASSTHRUSUB;
}