Skip to content

Commit 7927071

Browse files
committed
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
1 parent 27cdfe3 commit 7927071

File tree

1 file changed

+194
-27
lines changed

1 file changed

+194
-27
lines changed

src/http/modules/ngx_http_mp4_module.c

Lines changed: 194 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,38 +11,41 @@
1111

1212
#define NGX_HTTP_MP4_TRAK_ATOM 0
1313
#define NGX_HTTP_MP4_TKHD_ATOM 1
14-
#define NGX_HTTP_MP4_MDIA_ATOM 2
15-
#define NGX_HTTP_MP4_MDHD_ATOM 3
16-
#define NGX_HTTP_MP4_HDLR_ATOM 4
17-
#define NGX_HTTP_MP4_MINF_ATOM 5
18-
#define NGX_HTTP_MP4_VMHD_ATOM 6
19-
#define NGX_HTTP_MP4_SMHD_ATOM 7
20-
#define NGX_HTTP_MP4_DINF_ATOM 8
21-
#define NGX_HTTP_MP4_STBL_ATOM 9
22-
#define NGX_HTTP_MP4_STSD_ATOM 10
23-
#define NGX_HTTP_MP4_STTS_ATOM 11
24-
#define NGX_HTTP_MP4_STTS_DATA 12
25-
#define NGX_HTTP_MP4_STSS_ATOM 13
26-
#define NGX_HTTP_MP4_STSS_DATA 14
27-
#define NGX_HTTP_MP4_CTTS_ATOM 15
28-
#define NGX_HTTP_MP4_CTTS_DATA 16
29-
#define NGX_HTTP_MP4_STSC_ATOM 17
30-
#define NGX_HTTP_MP4_STSC_START 18
31-
#define NGX_HTTP_MP4_STSC_DATA 19
32-
#define NGX_HTTP_MP4_STSC_END 20
33-
#define NGX_HTTP_MP4_STSZ_ATOM 21
34-
#define NGX_HTTP_MP4_STSZ_DATA 22
35-
#define NGX_HTTP_MP4_STCO_ATOM 23
36-
#define NGX_HTTP_MP4_STCO_DATA 24
37-
#define NGX_HTTP_MP4_CO64_ATOM 25
38-
#define NGX_HTTP_MP4_CO64_DATA 26
14+
#define NGX_HTTP_MP4_EDTS_ATOM 2
15+
#define NGX_HTTP_MP4_ELST_ATOM 3
16+
#define NGX_HTTP_MP4_MDIA_ATOM 4
17+
#define NGX_HTTP_MP4_MDHD_ATOM 5
18+
#define NGX_HTTP_MP4_HDLR_ATOM 6
19+
#define NGX_HTTP_MP4_MINF_ATOM 7
20+
#define NGX_HTTP_MP4_VMHD_ATOM 8
21+
#define NGX_HTTP_MP4_SMHD_ATOM 9
22+
#define NGX_HTTP_MP4_DINF_ATOM 10
23+
#define NGX_HTTP_MP4_STBL_ATOM 11
24+
#define NGX_HTTP_MP4_STSD_ATOM 12
25+
#define NGX_HTTP_MP4_STTS_ATOM 13
26+
#define NGX_HTTP_MP4_STTS_DATA 14
27+
#define NGX_HTTP_MP4_STSS_ATOM 15
28+
#define NGX_HTTP_MP4_STSS_DATA 16
29+
#define NGX_HTTP_MP4_CTTS_ATOM 17
30+
#define NGX_HTTP_MP4_CTTS_DATA 18
31+
#define NGX_HTTP_MP4_STSC_ATOM 19
32+
#define NGX_HTTP_MP4_STSC_START 20
33+
#define NGX_HTTP_MP4_STSC_DATA 21
34+
#define NGX_HTTP_MP4_STSC_END 22
35+
#define NGX_HTTP_MP4_STSZ_ATOM 23
36+
#define NGX_HTTP_MP4_STSZ_DATA 24
37+
#define NGX_HTTP_MP4_STCO_ATOM 25
38+
#define NGX_HTTP_MP4_STCO_DATA 26
39+
#define NGX_HTTP_MP4_CO64_ATOM 27
40+
#define NGX_HTTP_MP4_CO64_DATA 28
3941

4042
#define NGX_HTTP_MP4_LAST_ATOM NGX_HTTP_MP4_CO64_DATA
4143

4244

4345
typedef struct {
4446
size_t buffer_size;
4547
size_t max_buffer_size;
48+
ngx_flag_t start_key_frame;
4649
} ngx_http_mp4_conf_t;
4750

4851

@@ -53,6 +56,25 @@ typedef struct {
5356
} ngx_mp4_stsc_entry_t;
5457

5558

59+
typedef struct {
60+
u_char size[4];
61+
u_char name[4];
62+
} ngx_mp4_edts_atom_t;
63+
64+
65+
typedef struct {
66+
u_char size[4];
67+
u_char name[4];
68+
u_char version[1];
69+
u_char flags[3];
70+
u_char entries[4];
71+
u_char duration[8];
72+
u_char media_time[8];
73+
u_char media_rate[2];
74+
u_char reserved[2];
75+
} ngx_mp4_elst_atom_t;
76+
77+
5678
typedef struct {
5779
uint32_t timescale;
5880
uint32_t time_to_sample_entries;
@@ -71,6 +93,8 @@ typedef struct {
7193
uint64_t start_chunk_samples_size;
7294
uint64_t end_chunk_samples_size;
7395
uint64_t duration;
96+
uint64_t prefix;
97+
uint64_t movie_duration;
7498
off_t start_offset;
7599
off_t end_offset;
76100

@@ -86,6 +110,8 @@ typedef struct {
86110

87111
ngx_buf_t trak_atom_buf;
88112
ngx_buf_t tkhd_atom_buf;
113+
ngx_buf_t edts_atom_buf;
114+
ngx_buf_t elst_atom_buf;
89115
ngx_buf_t mdia_atom_buf;
90116
ngx_buf_t mdhd_atom_buf;
91117
ngx_buf_t hdlr_atom_buf;
@@ -112,6 +138,8 @@ typedef struct {
112138
ngx_buf_t co64_atom_buf;
113139
ngx_buf_t co64_data_buf;
114140

141+
ngx_mp4_edts_atom_t edts_atom;
142+
ngx_mp4_elst_atom_t elst_atom;
115143
ngx_mp4_stsc_entry_t stsc_start_chunk_entry;
116144
ngx_mp4_stsc_entry_t stsc_end_chunk_entry;
117145
} ngx_http_mp4_trak_t;
@@ -187,6 +215,14 @@ typedef struct {
187215
((u_char *) (p))[6] = n3; \
188216
((u_char *) (p))[7] = n4
189217

218+
#define ngx_mp4_get_16value(p) \
219+
( ((uint16_t) ((u_char *) (p))[0] << 8) \
220+
+ ( ((u_char *) (p))[1]) )
221+
222+
#define ngx_mp4_set_16value(p, n) \
223+
((u_char *) (p))[0] = (u_char) ((n) >> 8); \
224+
((u_char *) (p))[1] = (u_char) (n)
225+
190226
#define ngx_mp4_get_32value(p) \
191227
( ((uint32_t) ((u_char *) (p))[0] << 24) \
192228
+ ( ((u_char *) (p))[1] << 16) \
@@ -270,6 +306,8 @@ static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4,
270306
uint64_t atom_data_size);
271307
static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4,
272308
uint64_t atom_data_size);
309+
static void ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4,
310+
ngx_http_mp4_trak_t *trak);
273311
static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
274312
ngx_http_mp4_trak_t *trak);
275313
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_stts_atom(ngx_http_mp4_file_t *mp4,
280318
ngx_http_mp4_trak_t *trak);
281319
static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
282320
ngx_http_mp4_trak_t *trak, ngx_uint_t start);
321+
static uint32_t ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4,
322+
ngx_http_mp4_trak_t *trak, uint32_t start_sample);
283323
static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
284324
uint64_t atom_data_size);
285325
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_commands[] = {
343383
offsetof(ngx_http_mp4_conf_t, max_buffer_size),
344384
NULL },
345385

386+
{ ngx_string("mp4_start_key_frame"),
387+
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
388+
ngx_conf_set_flag_slot,
389+
NGX_HTTP_LOC_CONF_OFFSET,
390+
offsetof(ngx_http_mp4_conf_t, start_key_frame),
391+
NULL },
392+
346393
ngx_null_command
347394
};
348395

@@ -829,6 +876,7 @@ ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
829876
trak[i].size += trak[i].hdlr_size;
830877
ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
831878
trak[i].size += trak[i].tkhd_size;
879+
ngx_http_mp4_update_edts_atom(mp4, &trak[i]);
832880
ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
833881

834882
mp4->moov_size += trak[i].size;
@@ -1590,6 +1638,7 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
15901638

15911639
trak = ngx_mp4_last_trak(mp4);
15921640
trak->tkhd_size = atom_size;
1641+
trak->movie_duration = duration;
15931642

15941643
ngx_mp4_set_32value(tkhd_atom->size, atom_size);
15951644

@@ -1985,6 +2034,59 @@ ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
19852034
}
19862035

19872036

2037+
static void
2038+
ngx_http_mp4_update_edts_atom(ngx_http_mp4_file_t *mp4,
2039+
ngx_http_mp4_trak_t *trak)
2040+
{
2041+
ngx_buf_t *atom;
2042+
ngx_mp4_elst_atom_t *elst_atom;
2043+
ngx_mp4_edts_atom_t *edts_atom;
2044+
2045+
if (trak->prefix == 0) {
2046+
return;
2047+
}
2048+
2049+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2050+
"mp4 edts atom update prefix:%uL", trak->prefix);
2051+
2052+
edts_atom = &trak->edts_atom;
2053+
ngx_mp4_set_32value(edts_atom->size, sizeof(ngx_mp4_edts_atom_t)
2054+
+ sizeof(ngx_mp4_elst_atom_t));
2055+
ngx_mp4_set_atom_name(edts_atom, 'e', 'd', 't', 's');
2056+
2057+
atom = &trak->edts_atom_buf;
2058+
atom->temporary = 1;
2059+
atom->pos = (u_char *) edts_atom;
2060+
atom->last = (u_char *) edts_atom + sizeof(ngx_mp4_edts_atom_t);
2061+
2062+
trak->out[NGX_HTTP_MP4_EDTS_ATOM].buf = atom;
2063+
2064+
elst_atom = &trak->elst_atom;
2065+
ngx_mp4_set_32value(elst_atom->size, sizeof(ngx_mp4_elst_atom_t));
2066+
ngx_mp4_set_atom_name(elst_atom, 'e', 'l', 's', 't');
2067+
2068+
elst_atom->version[0] = 1;
2069+
elst_atom->flags[0] = 0;
2070+
elst_atom->flags[1] = 0;
2071+
elst_atom->flags[2] = 0;
2072+
2073+
ngx_mp4_set_32value(elst_atom->entries, 1);
2074+
ngx_mp4_set_64value(elst_atom->duration, trak->movie_duration);
2075+
ngx_mp4_set_64value(elst_atom->media_time, trak->prefix);
2076+
ngx_mp4_set_16value(elst_atom->media_rate, 1);
2077+
ngx_mp4_set_16value(elst_atom->reserved, 0);
2078+
2079+
atom = &trak->elst_atom_buf;
2080+
atom->temporary = 1;
2081+
atom->pos = (u_char *) elst_atom;
2082+
atom->last = (u_char *) elst_atom + sizeof(ngx_mp4_elst_atom_t);
2083+
2084+
trak->out[NGX_HTTP_MP4_ELST_ATOM].buf = atom;
2085+
2086+
trak->size += sizeof(ngx_mp4_edts_atom_t) + sizeof(ngx_mp4_elst_atom_t);
2087+
}
2088+
2089+
19882090
static void
19892091
ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
19902092
ngx_http_mp4_trak_t *trak)
@@ -2183,7 +2285,7 @@ static ngx_int_t
21832285
ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
21842286
ngx_http_mp4_trak_t *trak, ngx_uint_t start)
21852287
{
2186-
uint32_t count, duration, rest;
2288+
uint32_t count, duration, rest, key_prefix;
21872289
uint64_t start_time;
21882290
ngx_buf_t *data;
21892291
ngx_uint_t start_sample, entries, start_sec;
@@ -2207,7 +2309,7 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
22072309

22082310
data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
22092311

2210-
start_time = (uint64_t) start_sec * trak->timescale / 1000;
2312+
start_time = (uint64_t) start_sec * trak->timescale / 1000 + trak->prefix;
22112313

22122314
entries = trak->time_to_sample_entries;
22132315
start_sample = 0;
@@ -2253,6 +2355,26 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
22532355
found:
22542356

22552357
if (start) {
2358+
key_prefix = ngx_http_mp4_seek_key_frame(mp4, trak, start_sample);
2359+
2360+
start_sample -= key_prefix;
2361+
2362+
while (rest < key_prefix) {
2363+
trak->prefix += rest * duration;
2364+
key_prefix -= rest;
2365+
2366+
entry--;
2367+
entries++;
2368+
2369+
count = ngx_mp4_get_32value(entry->count);
2370+
duration = ngx_mp4_get_32value(entry->duration);
2371+
rest = count;
2372+
}
2373+
2374+
trak->prefix += key_prefix * duration;
2375+
trak->duration += trak->prefix;
2376+
rest -= key_prefix;
2377+
22562378
ngx_mp4_set_32value(entry->count, count - rest);
22572379
data->pos = (u_char *) entry;
22582380
trak->time_to_sample_entries = entries;
@@ -2277,6 +2399,49 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
22772399
}
22782400

22792401

2402+
static uint32_t
2403+
ngx_http_mp4_seek_key_frame(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak,
2404+
uint32_t start_sample)
2405+
{
2406+
uint32_t key_prefix, sample, *entry, *end;
2407+
ngx_buf_t *data;
2408+
ngx_http_mp4_conf_t *conf;
2409+
2410+
conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
2411+
if (!conf->start_key_frame) {
2412+
return 0;
2413+
}
2414+
2415+
data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
2416+
if (data == NULL) {
2417+
return 0;
2418+
}
2419+
2420+
entry = (uint32_t *) data->pos;
2421+
end = (uint32_t *) data->last;
2422+
2423+
/* sync samples starts from 1 */
2424+
start_sample++;
2425+
2426+
key_prefix = 0;
2427+
2428+
while (entry < end) {
2429+
sample = ngx_mp4_get_32value(entry);
2430+
if (sample > start_sample) {
2431+
break;
2432+
}
2433+
2434+
key_prefix = start_sample - sample;
2435+
entry++;
2436+
}
2437+
2438+
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2439+
"mp4 key frame prefix:%uD", key_prefix);
2440+
2441+
return key_prefix;
2442+
}
2443+
2444+
22802445
typedef struct {
22812446
u_char size[4];
22822447
u_char name[4];
@@ -3614,6 +3779,7 @@ ngx_http_mp4_create_conf(ngx_conf_t *cf)
36143779

36153780
conf->buffer_size = NGX_CONF_UNSET_SIZE;
36163781
conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
3782+
conf->start_key_frame = NGX_CONF_UNSET;
36173783

36183784
return conf;
36193785
}
@@ -3628,6 +3794,7 @@ ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child)
36283794
ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024);
36293795
ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size,
36303796
10 * 1024 * 1024);
3797+
ngx_conf_merge_value(conf->start_key_frame, prev->start_key_frame, 0);
36313798

36323799
return NGX_CONF_OK;
36333800
}

0 commit comments

Comments
 (0)