[nginx] Mp4: mp4_start_key_frame directive.

Roman Arutyunyan arut at nginx.com
Fri Oct 29 15:58:37 UTC 2021


details:   https://hg.nginx.org/nginx/rev/f17ba8ecaaf0
branches:  
changeset: 7945:f17ba8ecaaf0
user:      Roman Arutyunyan <arut at nginx.com>
date:      Thu Oct 28 14:14:25 2021 +0300
description:
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.

Edit lists are currently supported by popular players and browsers such as
Chrome, Safari, QuickTime and ffmpeg.  Among those not supporting them properly
is Firefox[1].

Based on a patch by Tracey Jaquith, Internet Archive.

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1735300

diffstat:

 src/http/modules/ngx_http_mp4_module.c |  221 ++++++++++++++++++++++++++++----
 1 files changed, 194 insertions(+), 27 deletions(-)

diffs (357 lines):

diff -r 24f7904dbfa0 -r f17ba8ecaaf0 src/http/modules/ngx_http_mp4_module.c
--- a/src/http/modules/ngx_http_mp4_module.c	Thu Oct 28 13:11:31 2021 +0300
+++ b/src/http/modules/ngx_http_mp4_module.c	Thu Oct 28 14:14:25 2021 +0300
@@ -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                entries[4];
+    u_char                duration[8];
+    u_char                media_time[8];
+    u_char                media_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;
@@ -71,6 +93,8 @@ typedef struct {
     uint64_t              start_chunk_samples_size;
     uint64_t              end_chunk_samples_size;
     uint64_t              duration;
+    uint64_t              prefix;
+    uint64_t              movie_duration;
     off_t                 start_offset;
     off_t                 end_offset;
 
@@ -86,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;
@@ -112,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;
@@ -187,6 +215,14 @@ typedef struct {
     ((u_char *) (p))[6] = n3;                                                 \
     ((u_char *) (p))[7] = n4
 
+#define ngx_mp4_get_16value(p)                                                \
+    ( ((uint16_t) ((u_char *) (p))[0] << 8)                                   \
+    + (           ((u_char *) (p))[1]) )
+
+#define ngx_mp4_set_16value(p, n)                                             \
+    ((u_char *) (p))[0] = (u_char) ((n) >> 8);                                \
+    ((u_char *) (p))[1] = (u_char)  (n)
+
 #define ngx_mp4_get_32value(p)                                                \
     ( ((uint32_t) ((u_char *) (p))[0] << 24)                                  \
     + (           ((u_char *) (p))[1] << 16)                                  \
@@ -270,6 +306,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,
@@ -280,6 +318,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,
@@ -343,6 +383,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
 };
 
@@ -829,6 +876,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;
@@ -1590,6 +1638,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);
 
@@ -1986,6 +2035,59 @@ 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;
+
+    if (trak->prefix == 0) {
+        return;
+    }
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 edts atom update prefix:%uL", trak->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->entries, 1);
+    ngx_mp4_set_64value(elst_atom->duration, trak->movie_duration);
+    ngx_mp4_set_64value(elst_atom->media_time, trak->prefix);
+    ngx_mp4_set_16value(elst_atom->media_rate, 1);
+    ngx_mp4_set_16value(elst_atom->reserved, 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)
 {
@@ -2183,7 +2285,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;
@@ -2207,7 +2309,7 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4
 
     data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
 
-    start_time = (uint64_t) start_sec * trak->timescale / 1000;
+    start_time = (uint64_t) start_sec * trak->timescale / 1000 + trak->prefix;
 
     entries = trak->time_to_sample_entries;
     start_sample = 0;
@@ -2253,6 +2355,26 @@ 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->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->prefix += key_prefix * duration;
+        trak->duration += trak->prefix;
+        rest -= key_prefix;
+
         ngx_mp4_set_32value(entry->count, count - rest);
         data->pos = (u_char *) entry;
         trak->time_to_sample_entries = entries;
@@ -2277,6 +2399,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];
@@ -3614,6 +3779,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;
 }
@@ -3628,6 +3794,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