[PATCH] Add optional "mp4_exact_start" nginx config off/on to show video between keyframes

Roman Arutyunyan arut at nginx.com
Thu Sep 30 13:48:11 UTC 2021


Hi Tracey,

On Mon, Sep 20, 2021 at 12:39:15PM -0700, Tracey Jaquith wrote:
> Hi Roman,
> 
> I had an idea for considering for the feature name / config flag / function name, in case of interest.
> 
> What about “startfast”?   (or even “faststart”, I suppose)
> 
> That parallels nicely with the `qt-faststart` utility and the `ffmpeg  -movflags faststart`
> where the moov atom is moved to the front of the mp4.
> 
> Since the `mp4` module is already rewriting a smaller moov atom for the desired clip,
> *and* the mp4 module will move the moov atom to the front 
> (in case the source mp4 file has moov atom at the back),
> It seems like “startfast” might convey the moov atom approach *and* the concept
> that we’re going to send early visually undesired frames out at ~30,000 fps :)
> 
> For your consideration, thanks,
> -Tracey

Thanks for your suggestion.  Currently we're considering the name
"mp4_start_key_frame" which has the word "start" in it, which is the
argument that enables the feature.

But there's something more important I want to talk about.  While doing
internal review of the patch, we were conserned with some potential
problems the patch could introduce.  Specifically, when the video track has
B-frames, PTS - DTS delay is stored in the "ctts" atom, which was not changed
by the patch.  This means that some frames from the hidden part of the video
could show up in the visible part of it.  I believe this could be handled
properly, but the solution would be much more sophisticated than just
zeroing out the initial part of ctts.  Another problem is track delay, which
was obvious from the start.  The hidden part of the video still takes
some time to play, which ruins synchronization between tracks.  This may or
may not be noticable in particular cases, but anyway the problem is still there.

I've reimplemented the feature by using mp4 edit lists.  In a nutshell, all
frames up to the latest key frame are included in the video.  Then, the
initial part of the video is hidden from presentation by cutting it with an
edit list.  Looks like this solution does not have the problems I mentioned
above.

Can you try the new patch in your environment?  We would really appreciate
your feedback.

> > On Jun 28, 2021, at 2:53 AM, Roman Arutyunyan <arut at nginx.com> wrote:
> > 
> > Hi Tracey,
> > 
> > On Tue, Jun 15, 2021 at 03:49:48PM -0700, Tracey Jaquith wrote:
> >> # HG changeset patch
> >> # User Tracey Jaquith <tracey at archive.org <mailto:tracey at archive.org>>
> >> # Date 1623797180 0
> >> #      Tue Jun 15 22:46:20 2021 +0000
> >> # Node ID 1879d49fe0cf739f48287b5a38a83d3a1adab939
> >> # Parent  5f765427c17ac8cf753967387562201cf4f78dc4
> >> Add optional "mp4_exact_start" nginx config off/on to show video between keyframes.
> > 
> > I've been thinking about a better name for this, but came up with nothing so
> > far.  I feel like this name does not give the right clue to the user.
> > Moreover, when this feature is on, the start is not quite "exact", but shifted
> > a few milliseconds into the past.
> > 
> >> archive.org has been using mod_h264_streaming with a similar "exact start" patch from me since 2013.
> >> We just moved to nginx mp4 module and are using this patch.
> >> The technique is to find the video keyframe just before the desired "start" time, and send
> >> that down the wire so video playback can start immediately.
> >> Next calculate how many video samples are between the keyframe and desired "start" time
> >> and update the STTS atom where those samples move the duration from (typically) 1001 to 1.
> >> This way, initial unwanted video frames play at ~1/30,000s -- so visually the
> >> video & audio start playing immediately.
> >> 
> >> You can see an example before/after here (nginx binary built with mp4 module + patch):
> >> 
> >> https://pi.archive.org/0/items/CSPAN_20160425_022500_2011_White_House_Correspondents_Dinner.mp4?start=12&end=30
> >> https://pi.archive.org/0/items/CSPAN_20160425_022500_2011_White_House_Correspondents_Dinner.mp4?start=12&end=30&exact=1
> >> 
> >> Tested on linux and macosx.
> >> 
> >> (this is me: https://github.com/traceypooh )
> > 
> > We have a few rules about patches and commit messages like 67-character limit
> > for the first line etc:
> > 
> > http://nginx.org/en/docs/contributing_changes.html <http://nginx.org/en/docs/contributing_changes.html>
> > 
> >> diff -r 5f765427c17a -r 1879d49fe0cf src/http/modules/ngx_http_mp4_module.c
> >> --- a/src/http/modules/ngx_http_mp4_module.c    Tue Jun 01 17:37:51 2021 +0300
> >> +++ b/src/http/modules/ngx_http_mp4_module.c    Tue Jun 15 22:46:20 2021 +0000
> >> @@ -43,6 +43,7 @@
> >> typedef struct {
> >>     size_t                buffer_size;
> >>     size_t                max_buffer_size;
> >> +    ngx_flag_t            exact_start;
> >> } ngx_http_mp4_conf_t;
> >> 
> >> 
> >> @@ -340,6 +341,13 @@
> >>       offsetof(ngx_http_mp4_conf_t, max_buffer_size),
> >>       NULL },
> >> 
> >> +    { ngx_string("mp4_exact_start"),
> >> +      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
> > 
> > NGX_CONF_TAKE1 -> NGX_CONF_FLAG
> > 
> >> +      ngx_conf_set_flag_slot,
> >> +      NGX_HTTP_LOC_CONF_OFFSET,
> >> +      offsetof(ngx_http_mp4_conf_t, exact_start),
> >> +      NULL },
> >> +
> >>       ngx_null_command
> >> };
> >> 
> >> @@ -2156,6 +2164,83 @@
> >> 
> >> 
> >> static ngx_int_t
> >> +ngx_http_mp4_exact_start_video(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak)
> >> +{
> >> +    uint32_t               n, speedup_samples, current_count;
> >> +    ngx_uint_t             sample_keyframe, start_sample_exact;
> >> +    ngx_mp4_stts_entry_t  *entry, *entries_array;
> >> +    ngx_buf_t             *data;
> >> +
> >> +    data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
> >> +
> >> +    // Find the keyframe just before the desired start time - so that we can emit an mp4
> >> +    // where the first frame is a keyframe.  We'll "speed up" the first frames to 1000x
> >> +    // normal speed (typically), so they won't be noticed.  But this way, perceptively,
> >> +    // playback of the _video_ track can start immediately
> >> +    // (and not have to wait until the keyframe _after_ the desired starting time frame).
> >> +    start_sample_exact = trak->start_sample;
> >> +    for (n = 0; n < trak->sync_samples_entries; n++) {
> >> +        // each element of array is the sample number of a keyframe
> >> +        // sync samples starts from 1 -- so subtract 1
> >> +        sample_keyframe = ngx_mp4_get_32value(trak->stss_data_buf.pos + (n * 4)) - 1;
> > 
> > This can be simplified by introducing entry/end variables like we usually do.
> > 
> > Also, we don't access trak->stss_data_buf directly, but prefer
> > trak->out[NGX_HTTP_MP4_STSS_ATOM].buf.
> > 
> > ngx_http_mp4_crop_stss_data() provides an example of iterating over stss atom.
> > 
> >> +        if (sample_keyframe <= trak->start_sample) {
> >> +            start_sample_exact = sample_keyframe;
> >> +        }
> >> +        if (sample_keyframe >= trak->start_sample) {
> >> +            break;
> >> +        }
> >> +    }
> >> +
> >> +    if (start_sample_exact < trak->start_sample) {
> >> +        // We're going to prepend an entry with duration=1 for the frames we want to "not see".
> >> +        // MOST of the time (eg: constant video framerate),
> >> +        // we're taking a single element entry array and making it two.
> >> +        speedup_samples = trak->start_sample - start_sample_exact;
> >> +
> >> +        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
> >> +                       "exact trak start_sample move %l to %l (speed up %d samples)\n",
> >> +                       trak->start_sample, start_sample_exact, speedup_samples);
> >> +
> >> +        entries_array = ngx_palloc(mp4->request->pool,
> >> +            (1 + trak->time_to_sample_entries) * sizeof(ngx_mp4_stts_entry_t));
> >> +        if (entries_array == NULL) {
> >> +            return NGX_ERROR;
> >> +        }
> >> +        entry = &(entries_array[1]);
> >> +        ngx_memcpy(entry, (ngx_mp4_stts_entry_t *)data->pos,
> >> +                   trak->time_to_sample_entries * sizeof(ngx_mp4_stts_entry_t));
> > 
> > This reallocation can be avoided.  Look at NGX_HTTP_MP4_STSC_START buffer
> > as an example of that.  A new 1-element optional buffer NGX_HTTP_MP4_STTS_START
> > can be introduced right before the stts atom data.
> > 
> >> +        current_count = ngx_mp4_get_32value(entry->count);
> >> +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
> >> +                       "exact split in 2 video STTS entry from count:%d", current_count);
> >> +
> >> +        if (current_count <= speedup_samples) {
> >> +            return NGX_ERROR;
> >> +        }
> >> +
> >> +        ngx_mp4_set_32value(entry->count, current_count - speedup_samples);
> >> +        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
> >> +                       "exact split new[1]: count:%d duration:%d",
> >> +                       ngx_mp4_get_32value(entry->count),
> >> +                       ngx_mp4_get_32value(entry->duration));
> >> +        entry--;
> >> +        ngx_mp4_set_32value(entry->count, speedup_samples);
> >> +        ngx_mp4_set_32value(entry->duration, 1);
> >> +        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
> >> +                       "exact split new[0]: count:%d duration:1",
> >> +                       ngx_mp4_get_32value(entry->count));
> >> +
> >> +        data->pos = (u_char *) entry;
> >> +        trak->time_to_sample_entries++;
> >> +        trak->start_sample = start_sample_exact;
> >> +        data->last = (u_char *) (entry + trak->time_to_sample_entries);
> >> +    }
> >> +
> >> +    return NGX_OK;
> >> +}
> >> +
> >> +
> >> +static ngx_int_t
> >> ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
> >>     ngx_http_mp4_trak_t *trak, ngx_uint_t start)
> >> {
> >> @@ -2164,6 +2249,8 @@
> >>     ngx_buf_t             *data;
> >>     ngx_uint_t             start_sample, entries, start_sec;
> >>     ngx_mp4_stts_entry_t  *entry, *end;
> >> +    ngx_http_mp4_conf_t   *conf;
> >> +
> > 
> > No need for a new empty line here.
> > 
> >>     if (start) {
> >>         start_sec = mp4->start;
> >> @@ -2238,6 +2325,10 @@
> >>                        "start_sample:%ui, new count:%uD",
> >>                        trak->start_sample, count - rest);
> >> 
> >> +        conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
> >> +        if (conf->exact_start) {
> >> +            ngx_http_mp4_exact_start_video(mp4, trak);
> >> +        }
> >>     } else {
> >>         ngx_mp4_set_32value(entry->count, rest);
> >>         data->last = (u_char *) (entry + 1);
> >> @@ -3590,6 +3681,7 @@
> >> 
> >>     conf->buffer_size = NGX_CONF_UNSET_SIZE;
> >>     conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
> >> +    conf->exact_start = NGX_CONF_UNSET;
> > 
> > This is not enough, a merge is needed too.
> > 
> >> 
> >>     return conf;
> >> }
> >> _______________________________________________
> >> nginx-devel mailing list
> >> nginx-devel at nginx.org <mailto:nginx-devel at nginx.org>
> >> http://mailman.nginx.org/mailman/listinfo/nginx-devel <http://mailman.nginx.org/mailman/listinfo/nginx-devel>
> > 
> > I've made a POC patch which incorporates the issues I've mentioned.
> > I didn't test is properly and the directive name is still not perfect.
> > 
> > -- 
> > Roman Arutyunyan
> > <mp4-exact-arut.txt>_______________________________________________
> > nginx-devel mailing list
> > nginx-devel at nginx.org <mailto:nginx-devel at nginx.org>
> > http://mailman.nginx.org/mailman/listinfo/nginx-devel <http://mailman.nginx.org/mailman/listinfo/nginx-devel>
> -Tracey
> @tracey_pooh
> TV Architect  https://archive.org/tv <https://archive.org/tv>
> 
> 
> 
> 
> 

> _______________________________________________
> nginx-devel mailing list
> nginx-devel at nginx.org
> http://mailman.nginx.org/mailman/listinfo/nginx-devel


-- 
Roman Arutyunyan
-------------- next part --------------
# HG changeset patch
# User Roman Arutyunyan <arut at nginx.com>
# Date 1633003526 -10800
#      Thu Sep 30 15:05:26 2021 +0300
# Node ID 7527700ec8b19e3dd59561ee9b5a2cd87b07d5cd
# Parent  97cf8284fd19b30169231e43831e7787baba72f2
Mp4: mp4_start_key_frame directive.

The directive enables including all frames from start time to the most recent
key frame in the result.  Those frames are removed from presentation timeline
using mp4 edit lists.

Based on a patch by Tracey Jaquith, Internet Archive.

diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c
--- a/src/http/modules/ngx_http_mp4_module.c
+++ b/src/http/modules/ngx_http_mp4_module.c
@@ -11,31 +11,33 @@
 
 #define NGX_HTTP_MP4_TRAK_ATOM     0
 #define NGX_HTTP_MP4_TKHD_ATOM     1
-#define NGX_HTTP_MP4_MDIA_ATOM     2
-#define NGX_HTTP_MP4_MDHD_ATOM     3
-#define NGX_HTTP_MP4_HDLR_ATOM     4
-#define NGX_HTTP_MP4_MINF_ATOM     5
-#define NGX_HTTP_MP4_VMHD_ATOM     6
-#define NGX_HTTP_MP4_SMHD_ATOM     7
-#define NGX_HTTP_MP4_DINF_ATOM     8
-#define NGX_HTTP_MP4_STBL_ATOM     9
-#define NGX_HTTP_MP4_STSD_ATOM    10
-#define NGX_HTTP_MP4_STTS_ATOM    11
-#define NGX_HTTP_MP4_STTS_DATA    12
-#define NGX_HTTP_MP4_STSS_ATOM    13
-#define NGX_HTTP_MP4_STSS_DATA    14
-#define NGX_HTTP_MP4_CTTS_ATOM    15
-#define NGX_HTTP_MP4_CTTS_DATA    16
-#define NGX_HTTP_MP4_STSC_ATOM    17
-#define NGX_HTTP_MP4_STSC_START   18
-#define NGX_HTTP_MP4_STSC_DATA    19
-#define NGX_HTTP_MP4_STSC_END     20
-#define NGX_HTTP_MP4_STSZ_ATOM    21
-#define NGX_HTTP_MP4_STSZ_DATA    22
-#define NGX_HTTP_MP4_STCO_ATOM    23
-#define NGX_HTTP_MP4_STCO_DATA    24
-#define NGX_HTTP_MP4_CO64_ATOM    25
-#define NGX_HTTP_MP4_CO64_DATA    26
+#define NGX_HTTP_MP4_EDTS_ATOM     2
+#define NGX_HTTP_MP4_ELST_ATOM     3
+#define NGX_HTTP_MP4_MDIA_ATOM     4
+#define NGX_HTTP_MP4_MDHD_ATOM     5
+#define NGX_HTTP_MP4_HDLR_ATOM     6
+#define NGX_HTTP_MP4_MINF_ATOM     7
+#define NGX_HTTP_MP4_VMHD_ATOM     8
+#define NGX_HTTP_MP4_SMHD_ATOM     9
+#define NGX_HTTP_MP4_DINF_ATOM    10
+#define NGX_HTTP_MP4_STBL_ATOM    11
+#define NGX_HTTP_MP4_STSD_ATOM    12
+#define NGX_HTTP_MP4_STTS_ATOM    13
+#define NGX_HTTP_MP4_STTS_DATA    14
+#define NGX_HTTP_MP4_STSS_ATOM    15
+#define NGX_HTTP_MP4_STSS_DATA    16
+#define NGX_HTTP_MP4_CTTS_ATOM    17
+#define NGX_HTTP_MP4_CTTS_DATA    18
+#define NGX_HTTP_MP4_STSC_ATOM    19
+#define NGX_HTTP_MP4_STSC_START   20
+#define NGX_HTTP_MP4_STSC_DATA    21
+#define NGX_HTTP_MP4_STSC_END     22
+#define NGX_HTTP_MP4_STSZ_ATOM    23
+#define NGX_HTTP_MP4_STSZ_DATA    24
+#define NGX_HTTP_MP4_STCO_ATOM    25
+#define NGX_HTTP_MP4_STCO_DATA    26
+#define NGX_HTTP_MP4_CO64_ATOM    27
+#define NGX_HTTP_MP4_CO64_DATA    28
 
 #define NGX_HTTP_MP4_LAST_ATOM    NGX_HTTP_MP4_CO64_DATA
 
@@ -43,6 +45,7 @@
 typedef struct {
     size_t                buffer_size;
     size_t                max_buffer_size;
+    ngx_flag_t            start_key_frame;
 } ngx_http_mp4_conf_t;
 
 
@@ -54,6 +57,25 @@ typedef struct {
 
 
 typedef struct {
+    u_char                size[4];
+    u_char                name[4];
+} ngx_mp4_edts_atom_t;
+
+
+typedef struct {
+    u_char                size[4];
+    u_char                name[4];
+    u_char                version[1];
+    u_char                flags[3];
+    u_char                count[4];
+    u_char                duration[8];
+    u_char                time[8];
+    u_char                rate[2];
+    u_char                reserved[2];
+} ngx_mp4_elst_atom_t;
+
+
+typedef struct {
     uint32_t              timescale;
     uint32_t              time_to_sample_entries;
     uint32_t              sample_to_chunk_entries;
@@ -70,6 +92,9 @@ typedef struct {
     ngx_uint_t            end_chunk_samples;
     uint64_t              start_chunk_samples_size;
     uint64_t              end_chunk_samples_size;
+    uint64_t              movie_duration;
+    uint64_t              media_duration;
+    uint64_t              media_prefix;
     off_t                 start_offset;
     off_t                 end_offset;
 
@@ -85,6 +110,8 @@ typedef struct {
 
     ngx_buf_t             trak_atom_buf;
     ngx_buf_t             tkhd_atom_buf;
+    ngx_buf_t             edts_atom_buf;
+    ngx_buf_t             elst_atom_buf;
     ngx_buf_t             mdia_atom_buf;
     ngx_buf_t             mdhd_atom_buf;
     ngx_buf_t             hdlr_atom_buf;
@@ -111,6 +138,8 @@ typedef struct {
     ngx_buf_t             co64_atom_buf;
     ngx_buf_t             co64_data_buf;
 
+    ngx_mp4_edts_atom_t   edts_atom;
+    ngx_mp4_elst_atom_t   elst_atom;
     ngx_mp4_stsc_entry_t  stsc_start_chunk_entry;
     ngx_mp4_stsc_entry_t  stsc_end_chunk_entry;
 } ngx_http_mp4_trak_t;
@@ -267,6 +296,8 @@ static ngx_int_t ngx_http_mp4_read_smhd_
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
+static void ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak);
 static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak);
 static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4,
@@ -277,6 +308,8 @@ static ngx_int_t ngx_http_mp4_update_stt
     ngx_http_mp4_trak_t *trak);
 static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak, ngx_uint_t start);
+static uint32_t ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak, uint32_t start_sample);
 static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
@@ -340,6 +373,13 @@ static ngx_command_t  ngx_http_mp4_comma
       offsetof(ngx_http_mp4_conf_t, max_buffer_size),
       NULL },
 
+    { ngx_string("mp4_start_key_frame"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+      ngx_conf_set_flag_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_mp4_conf_t, start_key_frame),
+      NULL },
+
       ngx_null_command
 };
 
@@ -826,6 +866,7 @@ ngx_http_mp4_process(ngx_http_mp4_file_t
         trak[i].size += trak[i].hdlr_size;
         ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
         trak[i].size += trak[i].tkhd_size;
+        ngx_http_mp4_update_edts_atom(mp4, &trak[i]);
         ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
 
         mp4->moov_size += trak[i].size;
@@ -1587,6 +1628,7 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4
 
     trak = ngx_mp4_last_trak(mp4);
     trak->tkhd_size = atom_size;
+    trak->movie_duration = duration;
 
     ngx_mp4_set_32value(tkhd_atom->size, atom_size);
 
@@ -1749,6 +1791,7 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4
     trak = ngx_mp4_last_trak(mp4);
     trak->mdhd_size = atom_size;
     trak->timescale = timescale;
+    trak->media_duration = duration;
 
     ngx_mp4_set_32value(mdhd_atom->size, atom_size);
 
@@ -1962,6 +2005,77 @@ ngx_http_mp4_read_stbl_atom(ngx_http_mp4
 
 
 static void
+ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4,
+    ngx_http_mp4_trak_t *trak)
+{
+    ngx_buf_t              *atom;
+    ngx_mp4_elst_atom_t    *elst_atom;
+    ngx_mp4_edts_atom_t    *edts_atom;
+    ngx_mp4_mdhd_atom_t    *mdhd_atom;
+    ngx_mp4_mdhd64_atom_t  *mdhd64_atom;
+
+    if (trak->media_prefix == 0) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 edts atom update cut:%uL", trak->media_prefix);
+
+    atom = &trak->mdhd_atom_buf;
+    mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom->pos;
+    mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom->pos;
+
+    if (mdhd_atom->version[0] == 0) {
+        ngx_mp4_set_32value(mdhd_atom->duration,
+                            trak->media_duration + trak->media_prefix);
+
+    } else {
+        ngx_mp4_set_64value(mdhd64_atom->duration,
+                            trak->media_duration + trak->media_prefix);
+    }
+
+    edts_atom = &trak->edts_atom;
+    ngx_mp4_set_32value(edts_atom->size, sizeof(ngx_mp4_edts_atom_t)
+                                         + sizeof(ngx_mp4_elst_atom_t));
+    ngx_mp4_set_atom_name(edts_atom, 'e', 'd', 't', 's');
+
+    atom = &trak->edts_atom_buf;
+    atom->temporary = 1;
+    atom->pos = (u_char *) edts_atom;
+    atom->last = (u_char *) edts_atom + sizeof(ngx_mp4_edts_atom_t);
+
+    trak->out[NGX_HTTP_MP4_EDTS_ATOM].buf = atom;
+
+    elst_atom = &trak->elst_atom;
+    ngx_mp4_set_32value(elst_atom->size, sizeof(ngx_mp4_elst_atom_t));
+    ngx_mp4_set_atom_name(elst_atom, 'e', 'l', 's', 't');
+
+    elst_atom->version[0] = 1;
+    elst_atom->flags[0] = 0;
+    elst_atom->flags[1] = 0;
+    elst_atom->flags[2] = 0;
+
+    ngx_mp4_set_32value(elst_atom->count, 1);
+    ngx_mp4_set_64value(elst_atom->duration, trak->movie_duration);
+    ngx_mp4_set_64value(elst_atom->time, trak->media_prefix);
+
+    elst_atom->rate[0] = 0;
+    elst_atom->rate[1] = 1;
+    elst_atom->reserved[0] = 0;
+    elst_atom->reserved[1] = 0;
+
+    atom = &trak->elst_atom_buf;
+    atom->temporary = 1;
+    atom->pos = (u_char *) elst_atom;
+    atom->last = (u_char *) elst_atom + sizeof(ngx_mp4_elst_atom_t);
+
+    trak->out[NGX_HTTP_MP4_ELST_ATOM].buf = atom;
+
+    trak->size += sizeof(ngx_mp4_edts_atom_t) + sizeof(ngx_mp4_elst_atom_t);
+}
+
+
+static void
 ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak)
 {
@@ -2159,7 +2273,7 @@ static ngx_int_t
 ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak, ngx_uint_t start)
 {
-    uint32_t               count, duration, rest;
+    uint32_t               count, duration, rest, key_prefix;
     uint64_t               start_time;
     ngx_buf_t             *data;
     ngx_uint_t             start_sample, entries, start_sec;
@@ -2229,6 +2343,25 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4
 found:
 
     if (start) {
+        key_prefix = ngx_http_mp4_seek_key_frame(mp4, trak, start_sample);
+
+        start_sample -= key_prefix;
+
+        while (rest < key_prefix) {
+            trak->media_prefix += rest * duration;
+            key_prefix -= rest;
+
+            entry--;
+            entries++;
+
+            count = ngx_mp4_get_32value(entry->count);
+            duration = ngx_mp4_get_32value(entry->duration);
+            rest = count;
+        }
+
+        trak->media_prefix += key_prefix * duration;
+        rest -= key_prefix;
+
         ngx_mp4_set_32value(entry->count, count - rest);
         data->pos = (u_char *) entry;
         trak->time_to_sample_entries = entries;
@@ -2253,6 +2386,49 @@ found:
 }
 
 
+static uint32_t
+ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak,
+    uint32_t start_sample)
+{
+    uint32_t              key_prefix, sample, *entry, *end;
+    ngx_buf_t            *data;
+    ngx_http_mp4_conf_t  *conf;
+
+    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
+    if (!conf->start_key_frame) {
+        return 0;
+    }
+
+    data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
+    if (data == NULL) {
+        return 0;
+    }
+
+    entry = (uint32_t *) data->pos;
+    end = (uint32_t *) data->last;
+
+    /* sync samples starts from 1 */
+    start_sample++;
+
+    key_prefix = 0;
+
+    while (entry < end) {
+        sample = ngx_mp4_get_32value(entry);
+        if (sample > start_sample) {
+            break;
+        }
+
+        key_prefix = start_sample - sample;
+        entry++;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 key frame prefix:%uD", key_prefix);
+
+    return key_prefix;
+}
+
+
 typedef struct {
     u_char    size[4];
     u_char    name[4];
@@ -3590,6 +3766,7 @@ ngx_http_mp4_create_conf(ngx_conf_t *cf)
 
     conf->buffer_size = NGX_CONF_UNSET_SIZE;
     conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
+    conf->start_key_frame = NGX_CONF_UNSET;
 
     return conf;
 }
@@ -3604,6 +3781,7 @@ ngx_http_mp4_merge_conf(ngx_conf_t *cf, 
     ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024);
     ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size,
                               10 * 1024 * 1024);
+    ngx_conf_merge_value(conf->start_key_frame, prev->start_key_frame, 0);
 
     return NGX_CONF_OK;
 }


More information about the nginx-devel mailing list