1
2/*
3 * Copyright (C) Igor Sysoev
4 * Copyright (C) Nginx, Inc.
5 */
6
7#include <ngx_config.h>
8#include <ngx_core.h>
9#include <ngx_http.h>
10
11
12#define NGX_HTTP_MP4_TRAK_ATOM     0
13#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
39
40#define NGX_HTTP_MP4_LAST_ATOM    NGX_HTTP_MP4_CO64_DATA
41
42
43typedef struct {
44    size_t                buffer_size;
45    size_t                max_buffer_size;
46} ngx_http_mp4_conf_t;
47
48
49typedef struct {
50    u_char                chunk[4];
51    u_char                samples[4];
52    u_char                id[4];
53} ngx_mp4_stsc_entry_t;
54
55
56typedef struct {
57    uint32_t              timescale;
58    uint32_t              time_to_sample_entries;
59    uint32_t              sample_to_chunk_entries;
60    uint32_t              sync_samples_entries;
61    uint32_t              composition_offset_entries;
62    uint32_t              sample_sizes_entries;
63    uint32_t              chunks;
64
65    ngx_uint_t            start_sample;
66    ngx_uint_t            end_sample;
67    ngx_uint_t            start_chunk;
68    ngx_uint_t            end_chunk;
69    ngx_uint_t            start_chunk_samples;
70    ngx_uint_t            end_chunk_samples;
71    uint64_t              start_chunk_samples_size;
72    uint64_t              end_chunk_samples_size;
73    off_t                 start_offset;
74    off_t                 end_offset;
75
76    size_t                tkhd_size;
77    size_t                mdhd_size;
78    size_t                hdlr_size;
79    size_t                vmhd_size;
80    size_t                smhd_size;
81    size_t                dinf_size;
82    size_t                size;
83
84    ngx_chain_t           out[NGX_HTTP_MP4_LAST_ATOM + 1];
85
86    ngx_buf_t             trak_atom_buf;
87    ngx_buf_t             tkhd_atom_buf;
88    ngx_buf_t             mdia_atom_buf;
89    ngx_buf_t             mdhd_atom_buf;
90    ngx_buf_t             hdlr_atom_buf;
91    ngx_buf_t             minf_atom_buf;
92    ngx_buf_t             vmhd_atom_buf;
93    ngx_buf_t             smhd_atom_buf;
94    ngx_buf_t             dinf_atom_buf;
95    ngx_buf_t             stbl_atom_buf;
96    ngx_buf_t             stsd_atom_buf;
97    ngx_buf_t             stts_atom_buf;
98    ngx_buf_t             stts_data_buf;
99    ngx_buf_t             stss_atom_buf;
100    ngx_buf_t             stss_data_buf;
101    ngx_buf_t             ctts_atom_buf;
102    ngx_buf_t             ctts_data_buf;
103    ngx_buf_t             stsc_atom_buf;
104    ngx_buf_t             stsc_start_chunk_buf;
105    ngx_buf_t             stsc_end_chunk_buf;
106    ngx_buf_t             stsc_data_buf;
107    ngx_buf_t             stsz_atom_buf;
108    ngx_buf_t             stsz_data_buf;
109    ngx_buf_t             stco_atom_buf;
110    ngx_buf_t             stco_data_buf;
111    ngx_buf_t             co64_atom_buf;
112    ngx_buf_t             co64_data_buf;
113
114    ngx_mp4_stsc_entry_t  stsc_start_chunk_entry;
115    ngx_mp4_stsc_entry_t  stsc_end_chunk_entry;
116} ngx_http_mp4_trak_t;
117
118
119typedef struct {
120    ngx_file_t            file;
121
122    u_char               *buffer;
123    u_char               *buffer_start;
124    u_char               *buffer_pos;
125    u_char               *buffer_end;
126    size_t                buffer_size;
127
128    off_t                 offset;
129    off_t                 end;
130    off_t                 content_length;
131    ngx_uint_t            start;
132    ngx_uint_t            length;
133    uint32_t              timescale;
134    ngx_http_request_t   *request;
135    ngx_array_t           trak;
136    ngx_http_mp4_trak_t   traks[2];
137
138    size_t                ftyp_size;
139    size_t                moov_size;
140
141    ngx_chain_t          *out;
142    ngx_chain_t           ftyp_atom;
143    ngx_chain_t           moov_atom;
144    ngx_chain_t           mvhd_atom;
145    ngx_chain_t           mdat_atom;
146    ngx_chain_t           mdat_data;
147
148    ngx_buf_t             ftyp_atom_buf;
149    ngx_buf_t             moov_atom_buf;
150    ngx_buf_t             mvhd_atom_buf;
151    ngx_buf_t             mdat_atom_buf;
152    ngx_buf_t             mdat_data_buf;
153
154    u_char                moov_atom_header[8];
155    u_char                mdat_atom_header[16];
156} ngx_http_mp4_file_t;
157
158
159typedef struct {
160    char                 *name;
161    ngx_int_t           (*handler)(ngx_http_mp4_file_t *mp4,
162                                   uint64_t atom_data_size);
163} ngx_http_mp4_atom_handler_t;
164
165
166#define ngx_mp4_atom_header(mp4)   (mp4->buffer_pos - 8)
167#define ngx_mp4_atom_data(mp4)     mp4->buffer_pos
168#define ngx_mp4_atom_data_size(t)  (uint64_t) (sizeof(t) - 8)
169
170
171#define ngx_mp4_atom_next(mp4, n)                                             \
172    mp4->buffer_pos += (size_t) n;                                            \
173    mp4->offset += n
174
175
176#define ngx_mp4_set_atom_name(p, n1, n2, n3, n4)                              \
177    ((u_char *) (p))[4] = n1;                                                 \
178    ((u_char *) (p))[5] = n2;                                                 \
179    ((u_char *) (p))[6] = n3;                                                 \
180    ((u_char *) (p))[7] = n4
181
182#define ngx_mp4_get_32value(p)                                                \
183    ( ((uint32_t) ((u_char *) (p))[0] << 24)                                  \
184    + (           ((u_char *) (p))[1] << 16)                                  \
185    + (           ((u_char *) (p))[2] << 8)                                   \
186    + (           ((u_char *) (p))[3]) )
187
188#define ngx_mp4_set_32value(p, n)                                             \
189    ((u_char *) (p))[0] = (u_char) ((n) >> 24);                               \
190    ((u_char *) (p))[1] = (u_char) ((n) >> 16);                               \
191    ((u_char *) (p))[2] = (u_char) ((n) >> 8);                                \
192    ((u_char *) (p))[3] = (u_char)  (n)
193
194#define ngx_mp4_get_64value(p)                                                \
195    ( ((uint64_t) ((u_char *) (p))[0] << 56)                                  \
196    + ((uint64_t) ((u_char *) (p))[1] << 48)                                  \
197    + ((uint64_t) ((u_char *) (p))[2] << 40)                                  \
198    + ((uint64_t) ((u_char *) (p))[3] << 32)                                  \
199    + ((uint64_t) ((u_char *) (p))[4] << 24)                                  \
200    + (           ((u_char *) (p))[5] << 16)                                  \
201    + (           ((u_char *) (p))[6] << 8)                                   \
202    + (           ((u_char *) (p))[7]) )
203
204#define ngx_mp4_set_64value(p, n)                                             \
205    ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56);                    \
206    ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48);                    \
207    ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40);                    \
208    ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32);                    \
209    ((u_char *) (p))[4] = (u_char) (           (n) >> 24);                    \
210    ((u_char *) (p))[5] = (u_char) (           (n) >> 16);                    \
211    ((u_char *) (p))[6] = (u_char) (           (n) >> 8);                     \
212    ((u_char *) (p))[7] = (u_char)             (n)
213
214#define ngx_mp4_last_trak(mp4)                                                \
215    &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1]
216
217
218static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r);
219static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point);
220
221static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4);
222static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
223    ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size);
224static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size);
225static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4,
226    uint64_t atom_data_size);
227static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4,
228    uint64_t atom_data_size);
229static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4,
230    uint64_t atom_data_size);
231static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4,
232    off_t start_offset, off_t end_offset);
233static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4,
234    uint64_t atom_data_size);
235static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4,
236    uint64_t atom_data_size);
237static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
238    ngx_http_mp4_trak_t *trak);
239static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4,
240    uint64_t atom_data_size);
241static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4,
242    uint64_t atom_data_size);
243static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4,
244    uint64_t atom_data_size);
245static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
246    ngx_http_mp4_trak_t *trak);
247static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4,
248    uint64_t atom_data_size);
249static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4,
250    uint64_t atom_data_size);
251static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4,
252    uint64_t atom_data_size);
253static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
254    ngx_http_mp4_trak_t *trak);
255static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4,
256    uint64_t atom_data_size);
257static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4,
258    uint64_t atom_data_size);
259static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4,
260    uint64_t atom_data_size);
261static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4,
262    uint64_t atom_data_size);
263static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
264    ngx_http_mp4_trak_t *trak);
265static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4,
266    uint64_t atom_data_size);
267static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4,
268    uint64_t atom_data_size);
269static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
270    ngx_http_mp4_trak_t *trak);
271static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
272    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
273static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
274    uint64_t atom_data_size);
275static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
276    ngx_http_mp4_trak_t *trak);
277static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
278    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
279static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4,
280    uint64_t atom_data_size);
281static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
282    ngx_http_mp4_trak_t *trak);
283static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
284    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
285static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4,
286    uint64_t atom_data_size);
287static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
288    ngx_http_mp4_trak_t *trak);
289static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
290    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
291static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4,
292    uint64_t atom_data_size);
293static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
294    ngx_http_mp4_trak_t *trak);
295static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4,
296    uint64_t atom_data_size);
297static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
298    ngx_http_mp4_trak_t *trak);
299static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
300    ngx_http_mp4_trak_t *trak, int32_t adjustment);
301static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4,
302    uint64_t atom_data_size);
303static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
304    ngx_http_mp4_trak_t *trak);
305static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
306    ngx_http_mp4_trak_t *trak, off_t adjustment);
307
308static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
309static void *ngx_http_mp4_create_conf(ngx_conf_t *cf);
310static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child);
311
312
313static ngx_command_t  ngx_http_mp4_commands[] = {
314
315    { ngx_string("mp4"),
316      NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
317      ngx_http_mp4,
318      0,
319      0,
320      NULL },
321
322    { ngx_string("mp4_buffer_size"),
323      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
324      ngx_conf_set_size_slot,
325      NGX_HTTP_LOC_CONF_OFFSET,
326      offsetof(ngx_http_mp4_conf_t, buffer_size),
327      NULL },
328
329    { ngx_string("mp4_max_buffer_size"),
330      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
331      ngx_conf_set_size_slot,
332      NGX_HTTP_LOC_CONF_OFFSET,
333      offsetof(ngx_http_mp4_conf_t, max_buffer_size),
334      NULL },
335
336      ngx_null_command
337};
338
339
340static ngx_http_module_t  ngx_http_mp4_module_ctx = {
341    NULL,                          /* preconfiguration */
342    NULL,                          /* postconfiguration */
343
344    NULL,                          /* create main configuration */
345    NULL,                          /* init main configuration */
346
347    NULL,                          /* create server configuration */
348    NULL,                          /* merge server configuration */
349
350    ngx_http_mp4_create_conf,      /* create location configuration */
351    ngx_http_mp4_merge_conf        /* merge location configuration */
352};
353
354
355ngx_module_t  ngx_http_mp4_module = {
356    NGX_MODULE_V1,
357    &ngx_http_mp4_module_ctx,      /* module context */
358    ngx_http_mp4_commands,         /* module directives */
359    NGX_HTTP_MODULE,               /* module type */
360    NULL,                          /* init master */
361    NULL,                          /* init module */
362    NULL,                          /* init process */
363    NULL,                          /* init thread */
364    NULL,                          /* exit thread */
365    NULL,                          /* exit process */
366    NULL,                          /* exit master */
367    NGX_MODULE_V1_PADDING
368};
369
370
371static ngx_http_mp4_atom_handler_t  ngx_http_mp4_atoms[] = {
372    { "ftyp", ngx_http_mp4_read_ftyp_atom },
373    { "moov", ngx_http_mp4_read_moov_atom },
374    { "mdat", ngx_http_mp4_read_mdat_atom },
375    { NULL, NULL }
376};
377
378static ngx_http_mp4_atom_handler_t  ngx_http_mp4_moov_atoms[] = {
379    { "mvhd", ngx_http_mp4_read_mvhd_atom },
380    { "trak", ngx_http_mp4_read_trak_atom },
381    { "cmov", ngx_http_mp4_read_cmov_atom },
382    { NULL, NULL }
383};
384
385static ngx_http_mp4_atom_handler_t  ngx_http_mp4_trak_atoms[] = {
386    { "tkhd", ngx_http_mp4_read_tkhd_atom },
387    { "mdia", ngx_http_mp4_read_mdia_atom },
388    { NULL, NULL }
389};
390
391static ngx_http_mp4_atom_handler_t  ngx_http_mp4_mdia_atoms[] = {
392    { "mdhd", ngx_http_mp4_read_mdhd_atom },
393    { "hdlr", ngx_http_mp4_read_hdlr_atom },
394    { "minf", ngx_http_mp4_read_minf_atom },
395    { NULL, NULL }
396};
397
398static ngx_http_mp4_atom_handler_t  ngx_http_mp4_minf_atoms[] = {
399    { "vmhd", ngx_http_mp4_read_vmhd_atom },
400    { "smhd", ngx_http_mp4_read_smhd_atom },
401    { "dinf", ngx_http_mp4_read_dinf_atom },
402    { "stbl", ngx_http_mp4_read_stbl_atom },
403    { NULL, NULL }
404};
405
406static ngx_http_mp4_atom_handler_t  ngx_http_mp4_stbl_atoms[] = {
407    { "stsd", ngx_http_mp4_read_stsd_atom },
408    { "stts", ngx_http_mp4_read_stts_atom },
409    { "stss", ngx_http_mp4_read_stss_atom },
410    { "ctts", ngx_http_mp4_read_ctts_atom },
411    { "stsc", ngx_http_mp4_read_stsc_atom },
412    { "stsz", ngx_http_mp4_read_stsz_atom },
413    { "stco", ngx_http_mp4_read_stco_atom },
414    { "co64", ngx_http_mp4_read_co64_atom },
415    { NULL, NULL }
416};
417
418
419static ngx_int_t
420ngx_http_mp4_handler(ngx_http_request_t *r)
421{
422    u_char                    *last;
423    size_t                     root;
424    ngx_int_t                  rc, start, end;
425    ngx_uint_t                 level, length;
426    ngx_str_t                  path, value;
427    ngx_log_t                 *log;
428    ngx_buf_t                 *b;
429    ngx_chain_t                out;
430    ngx_http_mp4_file_t       *mp4;
431    ngx_open_file_info_t       of;
432    ngx_http_core_loc_conf_t  *clcf;
433
434    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
435        return NGX_HTTP_NOT_ALLOWED;
436    }
437
438    if (r->uri.data[r->uri.len - 1] == '/') {
439        return NGX_DECLINED;
440    }
441
442    rc = ngx_http_discard_request_body(r);
443
444    if (rc != NGX_OK) {
445        return rc;
446    }
447
448    last = ngx_http_map_uri_to_path(r, &path, &root, 0);
449    if (last == NULL) {
450        return NGX_HTTP_INTERNAL_SERVER_ERROR;
451    }
452
453    log = r->connection->log;
454
455    path.len = last - path.data;
456
457    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
458                   "http mp4 filename: \"%V\"", &path);
459
460    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
461
462    ngx_memzero(&of, sizeof(ngx_open_file_info_t));
463
464    of.read_ahead = clcf->read_ahead;
465    of.directio = NGX_MAX_OFF_T_VALUE;
466    of.valid = clcf->open_file_cache_valid;
467    of.min_uses = clcf->open_file_cache_min_uses;
468    of.errors = clcf->open_file_cache_errors;
469    of.events = clcf->open_file_cache_events;
470
471    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
472        return NGX_HTTP_INTERNAL_SERVER_ERROR;
473    }
474
475    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
476        != NGX_OK)
477    {
478        switch (of.err) {
479
480        case 0:
481            return NGX_HTTP_INTERNAL_SERVER_ERROR;
482
483        case NGX_ENOENT:
484        case NGX_ENOTDIR:
485        case NGX_ENAMETOOLONG:
486
487            level = NGX_LOG_ERR;
488            rc = NGX_HTTP_NOT_FOUND;
489            break;
490
491        case NGX_EACCES:
492#if (NGX_HAVE_OPENAT)
493        case NGX_EMLINK:
494        case NGX_ELOOP:
495#endif
496
497            level = NGX_LOG_ERR;
498            rc = NGX_HTTP_FORBIDDEN;
499            break;
500
501        default:
502
503            level = NGX_LOG_CRIT;
504            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
505            break;
506        }
507
508        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
509            ngx_log_error(level, log, of.err,
510                          "%s \"%s\" failed", of.failed, path.data);
511        }
512
513        return rc;
514    }
515
516    if (!of.is_file) {
517
518        if (ngx_close_file(of.fd) == NGX_FILE_ERROR) {
519            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
520                          ngx_close_file_n " \"%s\" failed", path.data);
521        }
522
523        return NGX_DECLINED;
524    }
525
526    r->root_tested = !r->error_page;
527    r->allow_ranges = 1;
528
529    start = -1;
530    length = 0;
531    r->headers_out.content_length_n = of.size;
532    mp4 = NULL;
533    b = NULL;
534
535    if (r->args.len) {
536
537        if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
538
539            /*
540             * A Flash player may send start value with a lot of digits
541             * after dot so a custom function is used instead of ngx_atofp().
542             */
543
544            start = ngx_http_mp4_atofp(value.data, value.len, 3);
545        }
546
547        if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {
548
549            end = ngx_http_mp4_atofp(value.data, value.len, 3);
550
551            if (end > 0) {
552                if (start < 0) {
553                    start = 0;
554                }
555
556                if (end > start) {
557                    length = end - start;
558                }
559            }
560        }
561    }
562
563    if (start >= 0) {
564        r->single_range = 1;
565
566        mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t));
567        if (mp4 == NULL) {
568            return NGX_HTTP_INTERNAL_SERVER_ERROR;
569        }
570
571        mp4->file.fd = of.fd;
572        mp4->file.name = path;
573        mp4->file.log = r->connection->log;
574        mp4->end = of.size;
575        mp4->start = (ngx_uint_t) start;
576        mp4->length = length;
577        mp4->request = r;
578
579        switch (ngx_http_mp4_process(mp4)) {
580
581        case NGX_DECLINED:
582            if (mp4->buffer) {
583                ngx_pfree(r->pool, mp4->buffer);
584            }
585
586            ngx_pfree(r->pool, mp4);
587            mp4 = NULL;
588
589            break;
590
591        case NGX_OK:
592            r->headers_out.content_length_n = mp4->content_length;
593            break;
594
595        default: /* NGX_ERROR */
596            if (mp4->buffer) {
597                ngx_pfree(r->pool, mp4->buffer);
598            }
599
600            ngx_pfree(r->pool, mp4);
601
602            return NGX_HTTP_INTERNAL_SERVER_ERROR;
603        }
604    }
605
606    log->action = "sending mp4 to client";
607
608    if (clcf->directio <= of.size) {
609
610        /*
611         * DIRECTIO is set on transfer only
612         * to allow kernel to cache "moov" atom
613         */
614
615        if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) {
616            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
617                          ngx_directio_on_n " \"%s\" failed", path.data);
618        }
619
620        of.is_directio = 1;
621
622        if (mp4) {
623            mp4->file.directio = 1;
624        }
625    }
626
627    r->headers_out.status = NGX_HTTP_OK;
628    r->headers_out.last_modified_time = of.mtime;
629
630    if (ngx_http_set_etag(r) != NGX_OK) {
631        return NGX_HTTP_INTERNAL_SERVER_ERROR;
632    }
633
634    if (ngx_http_set_content_type(r) != NGX_OK) {
635        return NGX_HTTP_INTERNAL_SERVER_ERROR;
636    }
637
638    if (mp4 == NULL) {
639        b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
640        if (b == NULL) {
641            return NGX_HTTP_INTERNAL_SERVER_ERROR;
642        }
643
644        b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
645        if (b->file == NULL) {
646            return NGX_HTTP_INTERNAL_SERVER_ERROR;
647        }
648    }
649
650    rc = ngx_http_send_header(r);
651
652    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
653        return rc;
654    }
655
656    if (mp4) {
657        return ngx_http_output_filter(r, mp4->out);
658    }
659
660    b->file_pos = 0;
661    b->file_last = of.size;
662
663    b->in_file = b->file_last ? 1 : 0;
664    b->last_buf = (r == r->main) ? 1 : 0;
665    b->last_in_chain = 1;
666
667    b->file->fd = of.fd;
668    b->file->name = path;
669    b->file->log = log;
670    b->file->directio = of.is_directio;
671
672    out.buf = b;
673    out.next = NULL;
674
675    return ngx_http_output_filter(r, &out);
676}
677
678
679static ngx_int_t
680ngx_http_mp4_atofp(u_char *line, size_t n, size_t point)
681{
682    ngx_int_t   value, cutoff, cutlim;
683    ngx_uint_t  dot;
684
685    /* same as ngx_atofp(), but allows additional digits */
686
687    if (n == 0) {
688        return NGX_ERROR;
689    }
690
691    cutoff = NGX_MAX_INT_T_VALUE / 10;
692    cutlim = NGX_MAX_INT_T_VALUE % 10;
693
694    dot = 0;
695
696    for (value = 0; n--; line++) {
697
698        if (*line == '.') {
699            if (dot) {
700                return NGX_ERROR;
701            }
702
703            dot = 1;
704            continue;
705        }
706
707        if (*line < '0' || *line > '9') {
708            return NGX_ERROR;
709        }
710
711        if (point == 0) {
712            continue;
713        }
714
715        if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
716            return NGX_ERROR;
717        }
718
719        value = value * 10 + (*line - '0');
720        point -= dot;
721    }
722
723    while (point--) {
724        if (value > cutoff) {
725            return NGX_ERROR;
726        }
727
728        value = value * 10;
729    }
730
731    return value;
732}
733
734
735static ngx_int_t
736ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
737{
738    off_t                  start_offset, end_offset, adjustment;
739    ngx_int_t              rc;
740    ngx_uint_t             i, j;
741    ngx_chain_t          **prev;
742    ngx_http_mp4_trak_t   *trak;
743    ngx_http_mp4_conf_t   *conf;
744
745    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
746                   "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
747
748    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
749
750    mp4->buffer_size = conf->buffer_size;
751
752    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end);
753    if (rc != NGX_OK) {
754        return rc;
755    }
756
757    if (mp4->trak.nelts == 0) {
758        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
759                      "no mp4 trak atoms were found in \"%s\"",
760                      mp4->file.name.data);
761        return NGX_ERROR;
762    }
763
764    if (mp4->mdat_atom.buf == NULL) {
765        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
766                      "no mp4 mdat atom was found in \"%s\"",
767                      mp4->file.name.data);
768        return NGX_ERROR;
769    }
770
771    prev = &mp4->out;
772
773    if (mp4->ftyp_atom.buf) {
774        *prev = &mp4->ftyp_atom;
775        prev = &mp4->ftyp_atom.next;
776    }
777
778    *prev = &mp4->moov_atom;
779    prev = &mp4->moov_atom.next;
780
781    if (mp4->mvhd_atom.buf) {
782        mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos;
783        *prev = &mp4->mvhd_atom;
784        prev = &mp4->mvhd_atom.next;
785    }
786
787    start_offset = mp4->end;
788    end_offset = 0;
789    trak = mp4->trak.elts;
790
791    for (i = 0; i < mp4->trak.nelts; i++) {
792
793        if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) {
794            return NGX_ERROR;
795        }
796
797        if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) {
798            return NGX_ERROR;
799        }
800
801        ngx_http_mp4_update_ctts_atom(mp4, &trak[i]);
802
803        if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) {
804            return NGX_ERROR;
805        }
806
807        if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) {
808            return NGX_ERROR;
809        }
810
811        if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
812            if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) {
813                return NGX_ERROR;
814            }
815
816        } else {
817            if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) {
818                return NGX_ERROR;
819            }
820        }
821
822        ngx_http_mp4_update_stbl_atom(mp4, &trak[i]);
823        ngx_http_mp4_update_minf_atom(mp4, &trak[i]);
824        trak[i].size += trak[i].mdhd_size;
825        trak[i].size += trak[i].hdlr_size;
826        ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
827        trak[i].size += trak[i].tkhd_size;
828        ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
829
830        mp4->moov_size += trak[i].size;
831
832        if (start_offset > trak[i].start_offset) {
833            start_offset = trak[i].start_offset;
834        }
835
836        if (end_offset < trak[i].end_offset) {
837            end_offset = trak[i].end_offset;
838        }
839
840        *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
841        prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
842
843        for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) {
844            if (trak[i].out[j].buf) {
845                *prev = &trak[i].out[j];
846                prev = &trak[i].out[j].next;
847            }
848        }
849    }
850
851    if (end_offset < start_offset) {
852        end_offset = start_offset;
853    }
854
855    mp4->moov_size += 8;
856
857    ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size);
858    ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v');
859    mp4->content_length += mp4->moov_size;
860
861    *prev = &mp4->mdat_atom;
862
863    if (start_offset > mp4->mdat_data.buf->file_last) {
864        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
865                      "start time is out mp4 mdat atom in \"%s\"",
866                      mp4->file.name.data);
867        return NGX_ERROR;
868    }
869
870    adjustment = mp4->ftyp_size + mp4->moov_size
871                 + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset)
872                 - start_offset;
873
874    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
875                   "mp4 adjustment:%O", adjustment);
876
877    for (i = 0; i < mp4->trak.nelts; i++) {
878        if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
879            ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment);
880        } else {
881            ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment);
882        }
883    }
884
885    return NGX_OK;
886}
887
888
889typedef struct {
890    u_char    size[4];
891    u_char    name[4];
892} ngx_mp4_atom_header_t;
893
894typedef struct {
895    u_char    size[4];
896    u_char    name[4];
897    u_char    size64[8];
898} ngx_mp4_atom_header64_t;
899
900
901static ngx_int_t
902ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
903    ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size)
904{
905    off_t        end;
906    size_t       atom_header_size;
907    u_char      *atom_header, *atom_name;
908    uint64_t     atom_size;
909    ngx_int_t    rc;
910    ngx_uint_t   n;
911
912    end = mp4->offset + atom_data_size;
913
914    while (mp4->offset < end) {
915
916        if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) {
917            return NGX_ERROR;
918        }
919
920        atom_header = mp4->buffer_pos;
921        atom_size = ngx_mp4_get_32value(atom_header);
922        atom_header_size = sizeof(ngx_mp4_atom_header_t);
923
924        if (atom_size == 0) {
925            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
926                           "mp4 atom end");
927            return NGX_OK;
928        }
929
930        if (atom_size < sizeof(ngx_mp4_atom_header_t)) {
931
932            if (atom_size == 1) {
933
934                if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t))
935                    != NGX_OK)
936                {
937                    return NGX_ERROR;
938                }
939
940                /* 64-bit atom size */
941                atom_header = mp4->buffer_pos;
942                atom_size = ngx_mp4_get_64value(atom_header + 8);
943                atom_header_size = sizeof(ngx_mp4_atom_header64_t);
944
945            } else {
946                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
947                              "\"%s\" mp4 atom is too small:%uL",
948                              mp4->file.name.data, atom_size);
949                return NGX_ERROR;
950            }
951        }
952
953        if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) {
954            return NGX_ERROR;
955        }
956
957        atom_header = mp4->buffer_pos;
958        atom_name = atom_header + sizeof(uint32_t);
959
960        ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
961                       "mp4 atom: %*s @%O:%uL",
962                       (size_t) 4, atom_name, mp4->offset, atom_size);
963
964        if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset)
965            || mp4->offset + (off_t) atom_size > end)
966        {
967            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
968                          "\"%s\" mp4 atom too large:%uL",
969                          mp4->file.name.data, atom_size);
970            return NGX_ERROR;
971        }
972
973        for (n = 0; atom[n].name; n++) {
974
975            if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) {
976
977                ngx_mp4_atom_next(mp4, atom_header_size);
978
979                rc = atom[n].handler(mp4, atom_size - atom_header_size);
980                if (rc != NGX_OK) {
981                    return rc;
982                }
983
984                goto next;
985            }
986        }
987
988        ngx_mp4_atom_next(mp4, atom_size);
989
990    next:
991        continue;
992    }
993
994    return NGX_OK;
995}
996
997
998static ngx_int_t
999ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size)
1000{
1001    ssize_t  n;
1002
1003    if (mp4->buffer_pos + size <= mp4->buffer_end) {
1004        return NGX_OK;
1005    }
1006
1007    if (mp4->offset + (off_t) mp4->buffer_size > mp4->end) {
1008        mp4->buffer_size = (size_t) (mp4->end - mp4->offset);
1009    }
1010
1011    if (mp4->buffer_size < size) {
1012        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1013                      "\"%s\" mp4 file truncated", mp4->file.name.data);
1014        return NGX_ERROR;
1015    }
1016
1017    if (mp4->buffer == NULL) {
1018        mp4->buffer = ngx_palloc(mp4->request->pool, mp4->buffer_size);
1019        if (mp4->buffer == NULL) {
1020            return NGX_ERROR;
1021        }
1022
1023        mp4->buffer_start = mp4->buffer;
1024    }
1025
1026    n = ngx_read_file(&mp4->file, mp4->buffer_start, mp4->buffer_size,
1027                      mp4->offset);
1028
1029    if (n == NGX_ERROR) {
1030        return NGX_ERROR;
1031    }
1032
1033    if ((size_t) n != mp4->buffer_size) {
1034        ngx_log_error(NGX_LOG_CRIT, mp4->file.log, 0,
1035                      ngx_read_file_n " read only %z of %z from \"%s\"",
1036                      n, mp4->buffer_size, mp4->file.name.data);
1037        return NGX_ERROR;
1038    }
1039
1040    mp4->buffer_pos = mp4->buffer_start;
1041    mp4->buffer_end = mp4->buffer_start + mp4->buffer_size;
1042
1043    return NGX_OK;
1044}
1045
1046
1047static ngx_int_t
1048ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1049{
1050    u_char     *ftyp_atom;
1051    size_t      atom_size;
1052    ngx_buf_t  *atom;
1053
1054    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ftyp atom");
1055
1056    if (atom_data_size > 1024
1057        || ngx_mp4_atom_data(mp4) + (size_t) atom_data_size > mp4->buffer_end)
1058    {
1059        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1060                      "\"%s\" mp4 ftyp atom is too large:%uL",
1061                      mp4->file.name.data, atom_data_size);
1062        return NGX_ERROR;
1063    }
1064
1065    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1066
1067    ftyp_atom = ngx_palloc(mp4->request->pool, atom_size);
1068    if (ftyp_atom == NULL) {
1069        return NGX_ERROR;
1070    }
1071
1072    ngx_mp4_set_32value(ftyp_atom, atom_size);
1073    ngx_mp4_set_atom_name(ftyp_atom, 'f', 't', 'y', 'p');
1074
1075    /*
1076     * only moov atom content is guaranteed to be in mp4->buffer
1077     * during sending response, so ftyp atom content should be copied
1078     */
1079    ngx_memcpy(ftyp_atom + sizeof(ngx_mp4_atom_header_t),
1080               ngx_mp4_atom_data(mp4), (size_t) atom_data_size);
1081
1082    atom = &mp4->ftyp_atom_buf;
1083    atom->temporary = 1;
1084    atom->pos = ftyp_atom;
1085    atom->last = ftyp_atom + atom_size;
1086
1087    mp4->ftyp_atom.buf = atom;
1088    mp4->ftyp_size = atom_size;
1089    mp4->content_length = atom_size;
1090
1091    ngx_mp4_atom_next(mp4, atom_data_size);
1092
1093    return NGX_OK;
1094}
1095
1096
1097/*
1098 * Small excess buffer to process atoms after moov atom, mp4->buffer_start
1099 * will be set to this buffer part after moov atom processing.
1100 */
1101#define NGX_HTTP_MP4_MOOV_BUFFER_EXCESS  (4 * 1024)
1102
1103static ngx_int_t
1104ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1105{
1106    ngx_int_t             rc;
1107    ngx_uint_t            no_mdat;
1108    ngx_buf_t            *atom;
1109    ngx_http_mp4_conf_t  *conf;
1110
1111    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom");
1112
1113    no_mdat = (mp4->mdat_atom.buf == NULL);
1114
1115    if (no_mdat && mp4->start == 0 && mp4->length == 0) {
1116        /*
1117         * send original file if moov atom resides before
1118         * mdat atom and client requests integral file
1119         */
1120        return NGX_DECLINED;
1121    }
1122
1123    conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
1124
1125    if (atom_data_size > mp4->buffer_size) {
1126
1127        if (atom_data_size > conf->max_buffer_size) {
1128            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1129                          "\"%s\" mp4 moov atom is too large:%uL, "
1130                          "you may want to increase mp4_max_buffer_size",
1131                          mp4->file.name.data, atom_data_size);
1132            return NGX_ERROR;
1133        }
1134
1135        ngx_pfree(mp4->request->pool, mp4->buffer);
1136        mp4->buffer = NULL;
1137        mp4->buffer_pos = NULL;
1138        mp4->buffer_end = NULL;
1139
1140        mp4->buffer_size = (size_t) atom_data_size
1141                         + NGX_HTTP_MP4_MOOV_BUFFER_EXCESS * no_mdat;
1142    }
1143
1144    if (ngx_http_mp4_read(mp4, (size_t) atom_data_size) != NGX_OK) {
1145        return NGX_ERROR;
1146    }
1147
1148    mp4->trak.elts = &mp4->traks;
1149    mp4->trak.size = sizeof(ngx_http_mp4_trak_t);
1150    mp4->trak.nalloc = 2;
1151    mp4->trak.pool = mp4->request->pool;
1152
1153    atom = &mp4->moov_atom_buf;
1154    atom->temporary = 1;
1155    atom->pos = mp4->moov_atom_header;
1156    atom->last = mp4->moov_atom_header + 8;
1157
1158    mp4->moov_atom.buf = &mp4->moov_atom_buf;
1159
1160    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_moov_atoms, atom_data_size);
1161
1162    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 moov atom done");
1163
1164    if (no_mdat) {
1165        mp4->buffer_start = mp4->buffer_pos;
1166        mp4->buffer_size = NGX_HTTP_MP4_MOOV_BUFFER_EXCESS;
1167
1168        if (mp4->buffer_start + mp4->buffer_size > mp4->buffer_end) {
1169            mp4->buffer = NULL;
1170            mp4->buffer_pos = NULL;
1171            mp4->buffer_end = NULL;
1172        }
1173
1174    } else {
1175        /* skip atoms after moov atom */
1176        mp4->offset = mp4->end;
1177    }
1178
1179    return rc;
1180}
1181
1182
1183static ngx_int_t
1184ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1185{
1186    ngx_buf_t  *data;
1187
1188    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdat atom");
1189
1190    data = &mp4->mdat_data_buf;
1191    data->file = &mp4->file;
1192    data->in_file = 1;
1193    data->last_buf = (mp4->request == mp4->request->main) ? 1 : 0;
1194    data->last_in_chain = 1;
1195    data->file_last = mp4->offset + atom_data_size;
1196
1197    mp4->mdat_atom.buf = &mp4->mdat_atom_buf;
1198    mp4->mdat_atom.next = &mp4->mdat_data;
1199    mp4->mdat_data.buf = data;
1200
1201    if (mp4->trak.nelts) {
1202        /* skip atoms after mdat atom */
1203        mp4->offset = mp4->end;
1204
1205    } else {
1206        ngx_mp4_atom_next(mp4, atom_data_size);
1207    }
1208
1209    return NGX_OK;
1210}
1211
1212
1213static size_t
1214ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset,
1215    off_t end_offset)
1216{
1217    off_t       atom_data_size;
1218    u_char     *atom_header;
1219    uint32_t    atom_header_size;
1220    uint64_t    atom_size;
1221    ngx_buf_t  *atom;
1222
1223    atom_data_size = end_offset - start_offset;
1224    mp4->mdat_data.buf->file_pos = start_offset;
1225    mp4->mdat_data.buf->file_last = end_offset;
1226
1227    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1228                   "mdat new offset @%O:%O", start_offset, atom_data_size);
1229
1230    atom_header = mp4->mdat_atom_header;
1231
1232    if ((uint64_t) atom_data_size
1233        > (uint64_t) 0xffffffff - sizeof(ngx_mp4_atom_header_t))
1234    {
1235        atom_size = 1;
1236        atom_header_size = sizeof(ngx_mp4_atom_header64_t);
1237        ngx_mp4_set_64value(atom_header + sizeof(ngx_mp4_atom_header_t),
1238                            sizeof(ngx_mp4_atom_header64_t) + atom_data_size);
1239    } else {
1240        atom_size = sizeof(ngx_mp4_atom_header_t) + atom_data_size;
1241        atom_header_size = sizeof(ngx_mp4_atom_header_t);
1242    }
1243
1244    mp4->content_length += atom_header_size + atom_data_size;
1245
1246    ngx_mp4_set_32value(atom_header, atom_size);
1247    ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'a', 't');
1248
1249    atom = &mp4->mdat_atom_buf;
1250    atom->temporary = 1;
1251    atom->pos = atom_header;
1252    atom->last = atom_header + atom_header_size;
1253
1254    return atom_header_size;
1255}
1256
1257
1258typedef struct {
1259    u_char    size[4];
1260    u_char    name[4];
1261    u_char    version[1];
1262    u_char    flags[3];
1263    u_char    creation_time[4];
1264    u_char    modification_time[4];
1265    u_char    timescale[4];
1266    u_char    duration[4];
1267    u_char    rate[4];
1268    u_char    volume[2];
1269    u_char    reserved[10];
1270    u_char    matrix[36];
1271    u_char    preview_time[4];
1272    u_char    preview_duration[4];
1273    u_char    poster_time[4];
1274    u_char    selection_time[4];
1275    u_char    selection_duration[4];
1276    u_char    current_time[4];
1277    u_char    next_track_id[4];
1278} ngx_mp4_mvhd_atom_t;
1279
1280typedef struct {
1281    u_char    size[4];
1282    u_char    name[4];
1283    u_char    version[1];
1284    u_char    flags[3];
1285    u_char    creation_time[8];
1286    u_char    modification_time[8];
1287    u_char    timescale[4];
1288    u_char    duration[8];
1289    u_char    rate[4];
1290    u_char    volume[2];
1291    u_char    reserved[10];
1292    u_char    matrix[36];
1293    u_char    preview_time[4];
1294    u_char    preview_duration[4];
1295    u_char    poster_time[4];
1296    u_char    selection_time[4];
1297    u_char    selection_duration[4];
1298    u_char    current_time[4];
1299    u_char    next_track_id[4];
1300} ngx_mp4_mvhd64_atom_t;
1301
1302
1303static ngx_int_t
1304ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1305{
1306    u_char                 *atom_header;
1307    size_t                  atom_size;
1308    uint32_t                timescale;
1309    uint64_t                duration, start_time, length_time;
1310    ngx_buf_t              *atom;
1311    ngx_mp4_mvhd_atom_t    *mvhd_atom;
1312    ngx_mp4_mvhd64_atom_t  *mvhd64_atom;
1313
1314    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mvhd atom");
1315
1316    atom_header = ngx_mp4_atom_header(mp4);
1317    mvhd_atom = (ngx_mp4_mvhd_atom_t *) atom_header;
1318    mvhd64_atom = (ngx_mp4_mvhd64_atom_t *) atom_header;
1319    ngx_mp4_set_atom_name(atom_header, 'm', 'v', 'h', 'd');
1320
1321    if (ngx_mp4_atom_data_size(ngx_mp4_mvhd_atom_t) > atom_data_size) {
1322        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1323                      "\"%s\" mp4 mvhd atom too small", mp4->file.name.data);
1324        return NGX_ERROR;
1325    }
1326
1327    if (mvhd_atom->version[0] == 0) {
1328        /* version 0: 32-bit duration */
1329        timescale = ngx_mp4_get_32value(mvhd_atom->timescale);
1330        duration = ngx_mp4_get_32value(mvhd_atom->duration);
1331
1332    } else {
1333        /* version 1: 64-bit duration */
1334
1335        if (ngx_mp4_atom_data_size(ngx_mp4_mvhd64_atom_t) > atom_data_size) {
1336            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1337                          "\"%s\" mp4 mvhd atom too small",
1338                          mp4->file.name.data);
1339            return NGX_ERROR;
1340        }
1341
1342        timescale = ngx_mp4_get_32value(mvhd64_atom->timescale);
1343        duration = ngx_mp4_get_64value(mvhd64_atom->duration);
1344    }
1345
1346    mp4->timescale = timescale;
1347
1348    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1349                   "mvhd timescale:%uD, duration:%uL, time:%.3fs",
1350                   timescale, duration, (double) duration / timescale);
1351
1352    start_time = (uint64_t) mp4->start * timescale / 1000;
1353
1354    if (duration < start_time) {
1355        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1356                      "\"%s\" mp4 start time exceeds file duration",
1357                      mp4->file.name.data);
1358        return NGX_ERROR;
1359    }
1360
1361    duration -= start_time;
1362
1363    if (mp4->length) {
1364        length_time = (uint64_t) mp4->length * timescale / 1000;
1365
1366        if (duration > length_time) {
1367            duration = length_time;
1368        }
1369    }
1370
1371    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1372                   "mvhd new duration:%uL, time:%.3fs",
1373                   duration, (double) duration / timescale);
1374
1375    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1376    ngx_mp4_set_32value(mvhd_atom->size, atom_size);
1377
1378    if (mvhd_atom->version[0] == 0) {
1379        ngx_mp4_set_32value(mvhd_atom->duration, duration);
1380
1381    } else {
1382        ngx_mp4_set_64value(mvhd64_atom->duration, duration);
1383    }
1384
1385    atom = &mp4->mvhd_atom_buf;
1386    atom->temporary = 1;
1387    atom->pos = atom_header;
1388    atom->last = atom_header + atom_size;
1389
1390    mp4->mvhd_atom.buf = atom;
1391
1392    ngx_mp4_atom_next(mp4, atom_data_size);
1393
1394    return NGX_OK;
1395}
1396
1397
1398static ngx_int_t
1399ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1400{
1401    u_char               *atom_header, *atom_end;
1402    off_t                 atom_file_end;
1403    ngx_int_t             rc;
1404    ngx_buf_t            *atom;
1405    ngx_http_mp4_trak_t  *trak;
1406
1407    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 trak atom");
1408
1409    trak = ngx_array_push(&mp4->trak);
1410    if (trak == NULL) {
1411        return NGX_ERROR;
1412    }
1413
1414    ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
1415
1416    atom_header = ngx_mp4_atom_header(mp4);
1417    ngx_mp4_set_atom_name(atom_header, 't', 'r', 'a', 'k');
1418
1419    atom = &trak->trak_atom_buf;
1420    atom->temporary = 1;
1421    atom->pos = atom_header;
1422    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
1423
1424    trak->out[NGX_HTTP_MP4_TRAK_ATOM].buf = atom;
1425
1426    atom_end = mp4->buffer_pos + (size_t) atom_data_size;
1427    atom_file_end = mp4->offset + atom_data_size;
1428
1429    rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_trak_atoms, atom_data_size);
1430
1431    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1432                   "mp4 trak atom: %i", rc);
1433
1434    if (rc == NGX_DECLINED) {
1435        /* skip this trak */
1436        ngx_memzero(trak, sizeof(ngx_http_mp4_trak_t));
1437        mp4->trak.nelts--;
1438        mp4->buffer_pos = atom_end;
1439        mp4->offset = atom_file_end;
1440        return NGX_OK;
1441    }
1442
1443    return rc;
1444}
1445
1446
1447static void
1448ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
1449    ngx_http_mp4_trak_t *trak)
1450{
1451    ngx_buf_t  *atom;
1452
1453    trak->size += sizeof(ngx_mp4_atom_header_t);
1454    atom = &trak->trak_atom_buf;
1455    ngx_mp4_set_32value(atom->pos, trak->size);
1456}
1457
1458
1459static ngx_int_t
1460ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1461{
1462    ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1463                  "\"%s\" mp4 compressed moov atom (cmov) is not supported",
1464                  mp4->file.name.data);
1465
1466    return NGX_ERROR;
1467}
1468
1469
1470typedef struct {
1471    u_char    size[4];
1472    u_char    name[4];
1473    u_char    version[1];
1474    u_char    flags[3];
1475    u_char    creation_time[4];
1476    u_char    modification_time[4];
1477    u_char    track_id[4];
1478    u_char    reserved1[4];
1479    u_char    duration[4];
1480    u_char    reserved2[8];
1481    u_char    layer[2];
1482    u_char    group[2];
1483    u_char    volume[2];
1484    u_char    reserved3[2];
1485    u_char    matrix[36];
1486    u_char    width[4];
1487    u_char    height[4];
1488} ngx_mp4_tkhd_atom_t;
1489
1490typedef struct {
1491    u_char    size[4];
1492    u_char    name[4];
1493    u_char    version[1];
1494    u_char    flags[3];
1495    u_char    creation_time[8];
1496    u_char    modification_time[8];
1497    u_char    track_id[4];
1498    u_char    reserved1[4];
1499    u_char    duration[8];
1500    u_char    reserved2[8];
1501    u_char    layer[2];
1502    u_char    group[2];
1503    u_char    volume[2];
1504    u_char    reserved3[2];
1505    u_char    matrix[36];
1506    u_char    width[4];
1507    u_char    height[4];
1508} ngx_mp4_tkhd64_atom_t;
1509
1510
1511static ngx_int_t
1512ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1513{
1514    u_char                 *atom_header;
1515    size_t                  atom_size;
1516    uint64_t                duration, start_time, length_time;
1517    ngx_buf_t              *atom;
1518    ngx_http_mp4_trak_t    *trak;
1519    ngx_mp4_tkhd_atom_t    *tkhd_atom;
1520    ngx_mp4_tkhd64_atom_t  *tkhd64_atom;
1521
1522    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 tkhd atom");
1523
1524    atom_header = ngx_mp4_atom_header(mp4);
1525    tkhd_atom = (ngx_mp4_tkhd_atom_t *) atom_header;
1526    tkhd64_atom = (ngx_mp4_tkhd64_atom_t *) atom_header;
1527    ngx_mp4_set_atom_name(tkhd_atom, 't', 'k', 'h', 'd');
1528
1529    if (ngx_mp4_atom_data_size(ngx_mp4_tkhd_atom_t) > atom_data_size) {
1530        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1531                      "\"%s\" mp4 tkhd atom too small", mp4->file.name.data);
1532        return NGX_ERROR;
1533    }
1534
1535    if (tkhd_atom->version[0] == 0) {
1536        /* version 0: 32-bit duration */
1537        duration = ngx_mp4_get_32value(tkhd_atom->duration);
1538
1539    } else {
1540        /* version 1: 64-bit duration */
1541
1542        if (ngx_mp4_atom_data_size(ngx_mp4_tkhd64_atom_t) > atom_data_size) {
1543            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1544                          "\"%s\" mp4 tkhd atom too small",
1545                          mp4->file.name.data);
1546            return NGX_ERROR;
1547        }
1548
1549        duration = ngx_mp4_get_64value(tkhd64_atom->duration);
1550    }
1551
1552    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1553                   "tkhd duration:%uL, time:%.3fs",
1554                   duration, (double) duration / mp4->timescale);
1555
1556    start_time = (uint64_t) mp4->start * mp4->timescale / 1000;
1557
1558    if (duration <= start_time) {
1559        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1560                       "tkhd duration is less than start time");
1561        return NGX_DECLINED;
1562    }
1563
1564    duration -= start_time;
1565
1566    if (mp4->length) {
1567        length_time = (uint64_t) mp4->length * mp4->timescale / 1000;
1568
1569        if (duration > length_time) {
1570            duration = length_time;
1571        }
1572    }
1573
1574    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1575                   "tkhd new duration:%uL, time:%.3fs",
1576                   duration, (double) duration / mp4->timescale);
1577
1578    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1579
1580    trak = ngx_mp4_last_trak(mp4);
1581    trak->tkhd_size = atom_size;
1582
1583    ngx_mp4_set_32value(tkhd_atom->size, atom_size);
1584
1585    if (tkhd_atom->version[0] == 0) {
1586        ngx_mp4_set_32value(tkhd_atom->duration, duration);
1587
1588    } else {
1589        ngx_mp4_set_64value(tkhd64_atom->duration, duration);
1590    }
1591
1592    atom = &trak->tkhd_atom_buf;
1593    atom->temporary = 1;
1594    atom->pos = atom_header;
1595    atom->last = atom_header + atom_size;
1596
1597    trak->out[NGX_HTTP_MP4_TKHD_ATOM].buf = atom;
1598
1599    ngx_mp4_atom_next(mp4, atom_data_size);
1600
1601    return NGX_OK;
1602}
1603
1604
1605static ngx_int_t
1606ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1607{
1608    u_char               *atom_header;
1609    ngx_buf_t            *atom;
1610    ngx_http_mp4_trak_t  *trak;
1611
1612    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process mdia atom");
1613
1614    atom_header = ngx_mp4_atom_header(mp4);
1615    ngx_mp4_set_atom_name(atom_header, 'm', 'd', 'i', 'a');
1616
1617    trak = ngx_mp4_last_trak(mp4);
1618
1619    atom = &trak->mdia_atom_buf;
1620    atom->temporary = 1;
1621    atom->pos = atom_header;
1622    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
1623
1624    trak->out[NGX_HTTP_MP4_MDIA_ATOM].buf = atom;
1625
1626    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_mdia_atoms, atom_data_size);
1627}
1628
1629
1630static void
1631ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
1632    ngx_http_mp4_trak_t *trak)
1633{
1634    ngx_buf_t  *atom;
1635
1636    trak->size += sizeof(ngx_mp4_atom_header_t);
1637    atom = &trak->mdia_atom_buf;
1638    ngx_mp4_set_32value(atom->pos, trak->size);
1639}
1640
1641
1642typedef struct {
1643    u_char    size[4];
1644    u_char    name[4];
1645    u_char    version[1];
1646    u_char    flags[3];
1647    u_char    creation_time[4];
1648    u_char    modification_time[4];
1649    u_char    timescale[4];
1650    u_char    duration[4];
1651    u_char    language[2];
1652    u_char    quality[2];
1653} ngx_mp4_mdhd_atom_t;
1654
1655typedef struct {
1656    u_char    size[4];
1657    u_char    name[4];
1658    u_char    version[1];
1659    u_char    flags[3];
1660    u_char    creation_time[8];
1661    u_char    modification_time[8];
1662    u_char    timescale[4];
1663    u_char    duration[8];
1664    u_char    language[2];
1665    u_char    quality[2];
1666} ngx_mp4_mdhd64_atom_t;
1667
1668
1669static ngx_int_t
1670ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1671{
1672    u_char                 *atom_header;
1673    size_t                  atom_size;
1674    uint32_t                timescale;
1675    uint64_t                duration, start_time, length_time;
1676    ngx_buf_t              *atom;
1677    ngx_http_mp4_trak_t    *trak;
1678    ngx_mp4_mdhd_atom_t    *mdhd_atom;
1679    ngx_mp4_mdhd64_atom_t  *mdhd64_atom;
1680
1681    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 mdhd atom");
1682
1683    atom_header = ngx_mp4_atom_header(mp4);
1684    mdhd_atom = (ngx_mp4_mdhd_atom_t *) atom_header;
1685    mdhd64_atom = (ngx_mp4_mdhd64_atom_t *) atom_header;
1686    ngx_mp4_set_atom_name(mdhd_atom, 'm', 'd', 'h', 'd');
1687
1688    if (ngx_mp4_atom_data_size(ngx_mp4_mdhd_atom_t) > atom_data_size) {
1689        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1690                      "\"%s\" mp4 mdhd atom too small", mp4->file.name.data);
1691        return NGX_ERROR;
1692    }
1693
1694    if (mdhd_atom->version[0] == 0) {
1695        /* version 0: everything is 32-bit */
1696        timescale = ngx_mp4_get_32value(mdhd_atom->timescale);
1697        duration = ngx_mp4_get_32value(mdhd_atom->duration);
1698
1699    } else {
1700        /* version 1: 64-bit duration and 32-bit timescale */
1701
1702        if (ngx_mp4_atom_data_size(ngx_mp4_mdhd64_atom_t) > atom_data_size) {
1703            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
1704                          "\"%s\" mp4 mdhd atom too small",
1705                          mp4->file.name.data);
1706            return NGX_ERROR;
1707        }
1708
1709        timescale = ngx_mp4_get_32value(mdhd64_atom->timescale);
1710        duration = ngx_mp4_get_64value(mdhd64_atom->duration);
1711    }
1712
1713    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1714                   "mdhd timescale:%uD, duration:%uL, time:%.3fs",
1715                   timescale, duration, (double) duration / timescale);
1716
1717    start_time = (uint64_t) mp4->start * timescale / 1000;
1718
1719    if (duration <= start_time) {
1720        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1721                       "mdhd duration is less than start time");
1722        return NGX_DECLINED;
1723    }
1724
1725    duration -= start_time;
1726
1727    if (mp4->length) {
1728        length_time = (uint64_t) mp4->length * timescale / 1000;
1729
1730        if (duration > length_time) {
1731            duration = length_time;
1732        }
1733    }
1734
1735    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
1736                   "mdhd new duration:%uL, time:%.3fs",
1737                   duration, (double) duration / timescale);
1738
1739    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1740
1741    trak = ngx_mp4_last_trak(mp4);
1742    trak->mdhd_size = atom_size;
1743    trak->timescale = timescale;
1744
1745    ngx_mp4_set_32value(mdhd_atom->size, atom_size);
1746
1747    if (mdhd_atom->version[0] == 0) {
1748        ngx_mp4_set_32value(mdhd_atom->duration, duration);
1749
1750    } else {
1751        ngx_mp4_set_64value(mdhd64_atom->duration, duration);
1752    }
1753
1754    atom = &trak->mdhd_atom_buf;
1755    atom->temporary = 1;
1756    atom->pos = atom_header;
1757    atom->last = atom_header + atom_size;
1758
1759    trak->out[NGX_HTTP_MP4_MDHD_ATOM].buf = atom;
1760
1761    ngx_mp4_atom_next(mp4, atom_data_size);
1762
1763    return NGX_OK;
1764}
1765
1766
1767static ngx_int_t
1768ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1769{
1770    u_char              *atom_header;
1771    size_t               atom_size;
1772    ngx_buf_t            *atom;
1773    ngx_http_mp4_trak_t  *trak;
1774
1775    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 hdlr atom");
1776
1777    atom_header = ngx_mp4_atom_header(mp4);
1778    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1779    ngx_mp4_set_32value(atom_header, atom_size);
1780    ngx_mp4_set_atom_name(atom_header, 'h', 'd', 'l', 'r');
1781
1782    trak = ngx_mp4_last_trak(mp4);
1783
1784    atom = &trak->hdlr_atom_buf;
1785    atom->temporary = 1;
1786    atom->pos = atom_header;
1787    atom->last = atom_header + atom_size;
1788
1789    trak->hdlr_size = atom_size;
1790    trak->out[NGX_HTTP_MP4_HDLR_ATOM].buf = atom;
1791
1792    ngx_mp4_atom_next(mp4, atom_data_size);
1793
1794    return NGX_OK;
1795}
1796
1797
1798static ngx_int_t
1799ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1800{
1801    u_char               *atom_header;
1802    ngx_buf_t            *atom;
1803    ngx_http_mp4_trak_t  *trak;
1804
1805    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process minf atom");
1806
1807    atom_header = ngx_mp4_atom_header(mp4);
1808    ngx_mp4_set_atom_name(atom_header, 'm', 'i', 'n', 'f');
1809
1810    trak = ngx_mp4_last_trak(mp4);
1811
1812    atom = &trak->minf_atom_buf;
1813    atom->temporary = 1;
1814    atom->pos = atom_header;
1815    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
1816
1817    trak->out[NGX_HTTP_MP4_MINF_ATOM].buf = atom;
1818
1819    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_minf_atoms, atom_data_size);
1820}
1821
1822
1823static void
1824ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
1825    ngx_http_mp4_trak_t *trak)
1826{
1827    ngx_buf_t  *atom;
1828
1829    trak->size += sizeof(ngx_mp4_atom_header_t)
1830               + trak->vmhd_size
1831               + trak->smhd_size
1832               + trak->dinf_size;
1833    atom = &trak->minf_atom_buf;
1834    ngx_mp4_set_32value(atom->pos, trak->size);
1835}
1836
1837
1838static ngx_int_t
1839ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1840{
1841    u_char              *atom_header;
1842    size_t               atom_size;
1843    ngx_buf_t            *atom;
1844    ngx_http_mp4_trak_t  *trak;
1845
1846    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 vmhd atom");
1847
1848    atom_header = ngx_mp4_atom_header(mp4);
1849    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1850    ngx_mp4_set_32value(atom_header, atom_size);
1851    ngx_mp4_set_atom_name(atom_header, 'v', 'm', 'h', 'd');
1852
1853    trak = ngx_mp4_last_trak(mp4);
1854
1855    atom = &trak->vmhd_atom_buf;
1856    atom->temporary = 1;
1857    atom->pos = atom_header;
1858    atom->last = atom_header + atom_size;
1859
1860    trak->vmhd_size += atom_size;
1861    trak->out[NGX_HTTP_MP4_VMHD_ATOM].buf = atom;
1862
1863    ngx_mp4_atom_next(mp4, atom_data_size);
1864
1865    return NGX_OK;
1866}
1867
1868
1869static ngx_int_t
1870ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1871{
1872    u_char              *atom_header;
1873    size_t               atom_size;
1874    ngx_buf_t            *atom;
1875    ngx_http_mp4_trak_t  *trak;
1876
1877    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 smhd atom");
1878
1879    atom_header = ngx_mp4_atom_header(mp4);
1880    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1881    ngx_mp4_set_32value(atom_header, atom_size);
1882    ngx_mp4_set_atom_name(atom_header, 's', 'm', 'h', 'd');
1883
1884    trak = ngx_mp4_last_trak(mp4);
1885
1886    atom = &trak->smhd_atom_buf;
1887    atom->temporary = 1;
1888    atom->pos = atom_header;
1889    atom->last = atom_header + atom_size;
1890
1891    trak->smhd_size += atom_size;
1892    trak->out[NGX_HTTP_MP4_SMHD_ATOM].buf = atom;
1893
1894    ngx_mp4_atom_next(mp4, atom_data_size);
1895
1896    return NGX_OK;
1897}
1898
1899
1900static ngx_int_t
1901ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1902{
1903    u_char              *atom_header;
1904    size_t               atom_size;
1905    ngx_buf_t            *atom;
1906    ngx_http_mp4_trak_t  *trak;
1907
1908    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 dinf atom");
1909
1910    atom_header = ngx_mp4_atom_header(mp4);
1911    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1912    ngx_mp4_set_32value(atom_header, atom_size);
1913    ngx_mp4_set_atom_name(atom_header, 'd', 'i', 'n', 'f');
1914
1915    trak = ngx_mp4_last_trak(mp4);
1916
1917    atom = &trak->dinf_atom_buf;
1918    atom->temporary = 1;
1919    atom->pos = atom_header;
1920    atom->last = atom_header + atom_size;
1921
1922    trak->dinf_size += atom_size;
1923    trak->out[NGX_HTTP_MP4_DINF_ATOM].buf = atom;
1924
1925    ngx_mp4_atom_next(mp4, atom_data_size);
1926
1927    return NGX_OK;
1928}
1929
1930
1931static ngx_int_t
1932ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1933{
1934    u_char               *atom_header;
1935    ngx_buf_t            *atom;
1936    ngx_http_mp4_trak_t  *trak;
1937
1938    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "process stbl atom");
1939
1940    atom_header = ngx_mp4_atom_header(mp4);
1941    ngx_mp4_set_atom_name(atom_header, 's', 't', 'b', 'l');
1942
1943    trak = ngx_mp4_last_trak(mp4);
1944
1945    atom = &trak->stbl_atom_buf;
1946    atom->temporary = 1;
1947    atom->pos = atom_header;
1948    atom->last = atom_header + sizeof(ngx_mp4_atom_header_t);
1949
1950    trak->out[NGX_HTTP_MP4_STBL_ATOM].buf = atom;
1951
1952    return ngx_http_mp4_read_atom(mp4, ngx_http_mp4_stbl_atoms, atom_data_size);
1953}
1954
1955
1956static void
1957ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
1958    ngx_http_mp4_trak_t *trak)
1959{
1960    ngx_buf_t  *atom;
1961
1962    trak->size += sizeof(ngx_mp4_atom_header_t);
1963    atom = &trak->stbl_atom_buf;
1964    ngx_mp4_set_32value(atom->pos, trak->size);
1965}
1966
1967
1968typedef struct {
1969    u_char    size[4];
1970    u_char    name[4];
1971    u_char    version[1];
1972    u_char    flags[3];
1973    u_char    entries[4];
1974
1975    u_char    media_size[4];
1976    u_char    media_name[4];
1977} ngx_mp4_stsd_atom_t;
1978
1979
1980static ngx_int_t
1981ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
1982{
1983    u_char               *atom_header, *atom_table;
1984    size_t                atom_size;
1985    ngx_buf_t            *atom;
1986    ngx_mp4_stsd_atom_t  *stsd_atom;
1987    ngx_http_mp4_trak_t  *trak;
1988
1989    /* sample description atom */
1990
1991    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsd atom");
1992
1993    atom_header = ngx_mp4_atom_header(mp4);
1994    stsd_atom = (ngx_mp4_stsd_atom_t *) atom_header;
1995    atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
1996    atom_table = atom_header + atom_size;
1997    ngx_mp4_set_32value(stsd_atom->size, atom_size);
1998    ngx_mp4_set_atom_name(stsd_atom, 's', 't', 's', 'd');
1999
2000    if (ngx_mp4_atom_data_size(ngx_mp4_stsd_atom_t) > atom_data_size) {
2001        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2002                      "\"%s\" mp4 stsd atom too small", mp4->file.name.data);
2003        return NGX_ERROR;
2004    }
2005
2006    ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2007                   "stsd entries:%uD, media:%*s",
2008                   ngx_mp4_get_32value(stsd_atom->entries),
2009                   (size_t) 4, stsd_atom->media_name);
2010
2011    trak = ngx_mp4_last_trak(mp4);
2012
2013    atom = &trak->stsd_atom_buf;
2014    atom->temporary = 1;
2015    atom->pos = atom_header;
2016    atom->last = atom_table;
2017
2018    trak->out[NGX_HTTP_MP4_STSD_ATOM].buf = atom;
2019    trak->size += atom_size;
2020
2021    ngx_mp4_atom_next(mp4, atom_data_size);
2022
2023    return NGX_OK;
2024}
2025
2026
2027typedef struct {
2028    u_char    size[4];
2029    u_char    name[4];
2030    u_char    version[1];
2031    u_char    flags[3];
2032    u_char    entries[4];
2033} ngx_mp4_stts_atom_t;
2034
2035typedef struct {
2036    u_char    count[4];
2037    u_char    duration[4];
2038} ngx_mp4_stts_entry_t;
2039
2040
2041static ngx_int_t
2042ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
2043{
2044    u_char               *atom_header, *atom_table, *atom_end;
2045    uint32_t              entries;
2046    ngx_buf_t            *atom, *data;
2047    ngx_mp4_stts_atom_t  *stts_atom;
2048    ngx_http_mp4_trak_t  *trak;
2049
2050    /* time-to-sample atom */
2051
2052    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stts atom");
2053
2054    atom_header = ngx_mp4_atom_header(mp4);
2055    stts_atom = (ngx_mp4_stts_atom_t *) atom_header;
2056    ngx_mp4_set_atom_name(stts_atom, 's', 't', 't', 's');
2057
2058    if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t) > atom_data_size) {
2059        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2060                      "\"%s\" mp4 stts atom too small", mp4->file.name.data);
2061        return NGX_ERROR;
2062    }
2063
2064    entries = ngx_mp4_get_32value(stts_atom->entries);
2065
2066    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2067                   "mp4 time-to-sample entries:%uD", entries);
2068
2069    if (ngx_mp4_atom_data_size(ngx_mp4_stts_atom_t)
2070        + entries * sizeof(ngx_mp4_stts_entry_t) > atom_data_size)
2071    {
2072        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2073                      "\"%s\" mp4 stts atom too small", mp4->file.name.data);
2074        return NGX_ERROR;
2075    }
2076
2077    atom_table = atom_header + sizeof(ngx_mp4_stts_atom_t);
2078    atom_end = atom_table + entries * sizeof(ngx_mp4_stts_entry_t);
2079
2080    trak = ngx_mp4_last_trak(mp4);
2081    trak->time_to_sample_entries = entries;
2082
2083    atom = &trak->stts_atom_buf;
2084    atom->temporary = 1;
2085    atom->pos = atom_header;
2086    atom->last = atom_table;
2087
2088    data = &trak->stts_data_buf;
2089    data->temporary = 1;
2090    data->pos = atom_table;
2091    data->last = atom_end;
2092
2093    trak->out[NGX_HTTP_MP4_STTS_ATOM].buf = atom;
2094    trak->out[NGX_HTTP_MP4_STTS_DATA].buf = data;
2095
2096    ngx_mp4_atom_next(mp4, atom_data_size);
2097
2098    return NGX_OK;
2099}
2100
2101
2102static ngx_int_t
2103ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
2104    ngx_http_mp4_trak_t *trak)
2105{
2106    size_t                atom_size;
2107    ngx_buf_t            *atom, *data;
2108    ngx_mp4_stts_atom_t  *stts_atom;
2109
2110    /*
2111     * mdia.minf.stbl.stts updating requires trak->timescale
2112     * from mdia.mdhd atom which may reside after mdia.minf
2113     */
2114
2115    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2116                   "mp4 stts atom update");
2117
2118    data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
2119
2120    if (data == NULL) {
2121        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2122                      "no mp4 stts atoms were found in \"%s\"",
2123                      mp4->file.name.data);
2124        return NGX_ERROR;
2125    }
2126
2127    if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) {
2128        return NGX_ERROR;
2129    }
2130
2131    if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) {
2132        return NGX_ERROR;
2133    }
2134
2135    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2136                   "time-to-sample entries:%uD", trak->time_to_sample_entries);
2137
2138    atom_size = sizeof(ngx_mp4_stts_atom_t) + (data->last - data->pos);
2139    trak->size += atom_size;
2140
2141    atom = trak->out[NGX_HTTP_MP4_STTS_ATOM].buf;
2142    stts_atom = (ngx_mp4_stts_atom_t *) atom->pos;
2143    ngx_mp4_set_32value(stts_atom->size, atom_size);
2144    ngx_mp4_set_32value(stts_atom->entries, trak->time_to_sample_entries);
2145
2146    return NGX_OK;
2147}
2148
2149
2150static ngx_int_t
2151ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
2152    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
2153{
2154    uint32_t               count, duration, rest;
2155    uint64_t               start_time;
2156    ngx_buf_t             *data;
2157    ngx_uint_t             start_sample, entries, start_sec;
2158    ngx_mp4_stts_entry_t  *entry, *end;
2159
2160    if (start) {
2161        start_sec = mp4->start;
2162
2163        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2164                       "mp4 stts crop start_time:%ui", start_sec);
2165
2166    } else if (mp4->length) {
2167        start_sec = mp4->length;
2168
2169        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2170                       "mp4 stts crop end_time:%ui", start_sec);
2171
2172    } else {
2173        return NGX_OK;
2174    }
2175
2176    data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
2177
2178    start_time = (uint64_t) start_sec * trak->timescale / 1000;
2179
2180    entries = trak->time_to_sample_entries;
2181    start_sample = 0;
2182    entry = (ngx_mp4_stts_entry_t *) data->pos;
2183    end = (ngx_mp4_stts_entry_t *) data->last;
2184
2185    while (entry < end) {
2186        count = ngx_mp4_get_32value(entry->count);
2187        duration = ngx_mp4_get_32value(entry->duration);
2188
2189        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2190                       "time:%uL, count:%uD, duration:%uD",
2191                       start_time, count, duration);
2192
2193        if (start_time < (uint64_t) count * duration) {
2194            start_sample += (ngx_uint_t) (start_time / duration);
2195            rest = (uint32_t) (start_time / duration);
2196            goto found;
2197        }
2198
2199        start_sample += count;
2200        start_time -= count * duration;
2201        entries--;
2202        entry++;
2203    }
2204
2205    if (start) {
2206        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2207                      "start time is out mp4 stts samples in \"%s\"",
2208                      mp4->file.name.data);
2209
2210        return NGX_ERROR;
2211
2212    } else {
2213        trak->end_sample = trak->start_sample + start_sample;
2214
2215        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2216                       "end_sample:%ui", trak->end_sample);
2217
2218        return NGX_OK;
2219    }
2220
2221found:
2222
2223    if (start) {
2224        ngx_mp4_set_32value(entry->count, count - rest);
2225        data->pos = (u_char *) entry;
2226        trak->time_to_sample_entries = entries;
2227        trak->start_sample = start_sample;
2228
2229        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2230                       "start_sample:%ui, new count:%uD",
2231                       trak->start_sample, count - rest);
2232
2233    } else {
2234        ngx_mp4_set_32value(entry->count, rest);
2235        data->last = (u_char *) (entry + 1);
2236        trak->time_to_sample_entries -= entries - 1;
2237        trak->end_sample = trak->start_sample + start_sample;
2238
2239        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2240                       "end_sample:%ui, new count:%uD",
2241                       trak->end_sample, rest);
2242    }
2243
2244    return NGX_OK;
2245}
2246
2247
2248typedef struct {
2249    u_char    size[4];
2250    u_char    name[4];
2251    u_char    version[1];
2252    u_char    flags[3];
2253    u_char    entries[4];
2254} ngx_http_mp4_stss_atom_t;
2255
2256
2257static ngx_int_t
2258ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
2259{
2260    u_char                    *atom_header, *atom_table, *atom_end;
2261    uint32_t                   entries;
2262    ngx_buf_t                 *atom, *data;
2263    ngx_http_mp4_trak_t       *trak;
2264    ngx_http_mp4_stss_atom_t  *stss_atom;
2265
2266    /* sync samples atom */
2267
2268    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stss atom");
2269
2270    atom_header = ngx_mp4_atom_header(mp4);
2271    stss_atom = (ngx_http_mp4_stss_atom_t *) atom_header;
2272    ngx_mp4_set_atom_name(stss_atom, 's', 't', 's', 's');
2273
2274    if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t) > atom_data_size) {
2275        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2276                      "\"%s\" mp4 stss atom too small", mp4->file.name.data);
2277        return NGX_ERROR;
2278    }
2279
2280    entries = ngx_mp4_get_32value(stss_atom->entries);
2281
2282    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2283                   "sync sample entries:%uD", entries);
2284
2285    trak = ngx_mp4_last_trak(mp4);
2286    trak->sync_samples_entries = entries;
2287
2288    atom_table = atom_header + sizeof(ngx_http_mp4_stss_atom_t);
2289
2290    atom = &trak->stss_atom_buf;
2291    atom->temporary = 1;
2292    atom->pos = atom_header;
2293    atom->last = atom_table;
2294
2295    if (ngx_mp4_atom_data_size(ngx_http_mp4_stss_atom_t)
2296        + entries * sizeof(uint32_t) > atom_data_size)
2297    {
2298        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2299                      "\"%s\" mp4 stss atom too small", mp4->file.name.data);
2300        return NGX_ERROR;
2301    }
2302
2303    atom_end = atom_table + entries * sizeof(uint32_t);
2304
2305    data = &trak->stss_data_buf;
2306    data->temporary = 1;
2307    data->pos = atom_table;
2308    data->last = atom_end;
2309
2310    trak->out[NGX_HTTP_MP4_STSS_ATOM].buf = atom;
2311    trak->out[NGX_HTTP_MP4_STSS_DATA].buf = data;
2312
2313    ngx_mp4_atom_next(mp4, atom_data_size);
2314
2315    return NGX_OK;
2316}
2317
2318
2319static ngx_int_t
2320ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
2321    ngx_http_mp4_trak_t *trak)
2322{
2323    size_t                     atom_size;
2324    uint32_t                   sample, start_sample, *entry, *end;
2325    ngx_buf_t                 *atom, *data;
2326    ngx_http_mp4_stss_atom_t  *stss_atom;
2327
2328    /*
2329     * mdia.minf.stbl.stss updating requires trak->start_sample
2330     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
2331     * atom which may reside after mdia.minf
2332     */
2333
2334    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2335                   "mp4 stss atom update");
2336
2337    data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
2338
2339    if (data == NULL) {
2340        return NGX_OK;
2341    }
2342
2343    ngx_http_mp4_crop_stss_data(mp4, trak, 1);
2344    ngx_http_mp4_crop_stss_data(mp4, trak, 0);
2345
2346    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2347                   "sync sample entries:%uD", trak->sync_samples_entries);
2348
2349    if (trak->sync_samples_entries) {
2350        entry = (uint32_t *) data->pos;
2351        end = (uint32_t *) data->last;
2352
2353        start_sample = trak->start_sample;
2354
2355        while (entry < end) {
2356            sample = ngx_mp4_get_32value(entry);
2357            sample -= start_sample;
2358            ngx_mp4_set_32value(entry, sample);
2359            entry++;
2360        }
2361
2362    } else {
2363        trak->out[NGX_HTTP_MP4_STSS_DATA].buf = NULL;
2364    }
2365
2366    atom_size = sizeof(ngx_http_mp4_stss_atom_t) + (data->last - data->pos);
2367    trak->size += atom_size;
2368
2369    atom = trak->out[NGX_HTTP_MP4_STSS_ATOM].buf;
2370    stss_atom = (ngx_http_mp4_stss_atom_t *) atom->pos;
2371
2372    ngx_mp4_set_32value(stss_atom->size, atom_size);
2373    ngx_mp4_set_32value(stss_atom->entries, trak->sync_samples_entries);
2374
2375    return NGX_OK;
2376}
2377
2378
2379static void
2380ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
2381    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
2382{
2383    uint32_t     sample, start_sample, *entry, *end;
2384    ngx_buf_t   *data;
2385    ngx_uint_t   entries;
2386
2387    /* sync samples starts from 1 */
2388
2389    if (start) {
2390        start_sample = trak->start_sample + 1;
2391
2392        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2393                       "mp4 stss crop start_sample:%uD", start_sample);
2394
2395    } else if (mp4->length) {
2396        start_sample = trak->end_sample + 1;
2397
2398        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2399                       "mp4 stss crop end_sample:%uD", start_sample);
2400
2401    } else {
2402        return;
2403    }
2404
2405    data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
2406
2407    entries = trak->sync_samples_entries;
2408    entry = (uint32_t *) data->pos;
2409    end = (uint32_t *) data->last;
2410
2411    while (entry < end) {
2412        sample = ngx_mp4_get_32value(entry);
2413
2414        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2415                       "sync:%uD", sample);
2416
2417        if (sample >= start_sample) {
2418            goto found;
2419        }
2420
2421        entries--;
2422        entry++;
2423    }
2424
2425    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2426                   "sample is out of mp4 stss atom");
2427
2428found:
2429
2430    if (start) {
2431        data->pos = (u_char *) entry;
2432        trak->sync_samples_entries = entries;
2433
2434    } else {
2435        data->last = (u_char *) entry;
2436        trak->sync_samples_entries -= entries;
2437    }
2438}
2439
2440
2441typedef struct {
2442    u_char    size[4];
2443    u_char    name[4];
2444    u_char    version[1];
2445    u_char    flags[3];
2446    u_char    entries[4];
2447} ngx_mp4_ctts_atom_t;
2448
2449typedef struct {
2450    u_char    count[4];
2451    u_char    offset[4];
2452} ngx_mp4_ctts_entry_t;
2453
2454
2455static ngx_int_t
2456ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
2457{
2458    u_char               *atom_header, *atom_table, *atom_end;
2459    uint32_t              entries;
2460    ngx_buf_t            *atom, *data;
2461    ngx_mp4_ctts_atom_t  *ctts_atom;
2462    ngx_http_mp4_trak_t  *trak;
2463
2464    /* composition offsets atom */
2465
2466    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 ctts atom");
2467
2468    atom_header = ngx_mp4_atom_header(mp4);
2469    ctts_atom = (ngx_mp4_ctts_atom_t *) atom_header;
2470    ngx_mp4_set_atom_name(ctts_atom, 'c', 't', 't', 's');
2471
2472    if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t) > atom_data_size) {
2473        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2474                      "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
2475        return NGX_ERROR;
2476    }
2477
2478    entries = ngx_mp4_get_32value(ctts_atom->entries);
2479
2480    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2481                   "composition offset entries:%uD", entries);
2482
2483    trak = ngx_mp4_last_trak(mp4);
2484    trak->composition_offset_entries = entries;
2485
2486    atom_table = atom_header + sizeof(ngx_mp4_ctts_atom_t);
2487
2488    atom = &trak->ctts_atom_buf;
2489    atom->temporary = 1;
2490    atom->pos = atom_header;
2491    atom->last = atom_table;
2492
2493    if (ngx_mp4_atom_data_size(ngx_mp4_ctts_atom_t)
2494        + entries * sizeof(ngx_mp4_ctts_entry_t) > atom_data_size)
2495    {
2496        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2497                      "\"%s\" mp4 ctts atom too small", mp4->file.name.data);
2498        return NGX_ERROR;
2499    }
2500
2501    atom_end = atom_table + entries * sizeof(ngx_mp4_ctts_entry_t);
2502
2503    data = &trak->ctts_data_buf;
2504    data->temporary = 1;
2505    data->pos = atom_table;
2506    data->last = atom_end;
2507
2508    trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = atom;
2509    trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = data;
2510
2511    ngx_mp4_atom_next(mp4, atom_data_size);
2512
2513    return NGX_OK;
2514}
2515
2516
2517static void
2518ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
2519    ngx_http_mp4_trak_t *trak)
2520{
2521    size_t                atom_size;
2522    ngx_buf_t            *atom, *data;
2523    ngx_mp4_ctts_atom_t  *ctts_atom;
2524
2525    /*
2526     * mdia.minf.stbl.ctts updating requires trak->start_sample
2527     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
2528     * atom which may reside after mdia.minf
2529     */
2530
2531    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2532                   "mp4 ctts atom update");
2533
2534    data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
2535
2536    if (data == NULL) {
2537        return;
2538    }
2539
2540    ngx_http_mp4_crop_ctts_data(mp4, trak, 1);
2541    ngx_http_mp4_crop_ctts_data(mp4, trak, 0);
2542
2543    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2544                   "composition offset entries:%uD",
2545                   trak->composition_offset_entries);
2546
2547    if (trak->composition_offset_entries == 0) {
2548        trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL;
2549        trak->out[NGX_HTTP_MP4_CTTS_DATA].buf = NULL;
2550        return;
2551    }
2552
2553    atom_size = sizeof(ngx_mp4_ctts_atom_t) + (data->last - data->pos);
2554    trak->size += atom_size;
2555
2556    atom = trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf;
2557    ctts_atom = (ngx_mp4_ctts_atom_t *) atom->pos;
2558
2559    ngx_mp4_set_32value(ctts_atom->size, atom_size);
2560    ngx_mp4_set_32value(ctts_atom->entries, trak->composition_offset_entries);
2561
2562    return;
2563}
2564
2565
2566static void
2567ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
2568    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
2569{
2570    uint32_t               count, start_sample, rest;
2571    ngx_buf_t             *data;
2572    ngx_uint_t             entries;
2573    ngx_mp4_ctts_entry_t  *entry, *end;
2574
2575    /* sync samples starts from 1 */
2576
2577    if (start) {
2578        start_sample = trak->start_sample + 1;
2579
2580        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2581                       "mp4 ctts crop start_sample:%uD", start_sample);
2582
2583    } else if (mp4->length) {
2584        start_sample = trak->end_sample - trak->start_sample + 1;
2585
2586        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2587                       "mp4 ctts crop end_sample:%uD", start_sample);
2588
2589    } else {
2590        return;
2591    }
2592
2593    data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
2594
2595    entries = trak->composition_offset_entries;
2596    entry = (ngx_mp4_ctts_entry_t *) data->pos;
2597    end = (ngx_mp4_ctts_entry_t *) data->last;
2598
2599    while (entry < end) {
2600        count = ngx_mp4_get_32value(entry->count);
2601
2602        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2603                       "sample:%uD, count:%uD, offset:%uD",
2604                       start_sample, count, ngx_mp4_get_32value(entry->offset));
2605
2606        if (start_sample <= count) {
2607            rest = start_sample - 1;
2608            goto found;
2609        }
2610
2611        start_sample -= count;
2612        entries--;
2613        entry++;
2614    }
2615
2616    if (start) {
2617        data->pos = (u_char *) end;
2618        trak->composition_offset_entries = 0;
2619    }
2620
2621    return;
2622
2623found:
2624
2625    if (start) {
2626        ngx_mp4_set_32value(entry->count, count - rest);
2627        data->pos = (u_char *) entry;
2628        trak->composition_offset_entries = entries;
2629
2630    } else {
2631        ngx_mp4_set_32value(entry->count, rest);
2632        data->last = (u_char *) (entry + 1);
2633        trak->composition_offset_entries -= entries - 1;
2634    }
2635}
2636
2637
2638typedef struct {
2639    u_char    size[4];
2640    u_char    name[4];
2641    u_char    version[1];
2642    u_char    flags[3];
2643    u_char    entries[4];
2644} ngx_mp4_stsc_atom_t;
2645
2646
2647static ngx_int_t
2648ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
2649{
2650    u_char               *atom_header, *atom_table, *atom_end;
2651    uint32_t              entries;
2652    ngx_buf_t            *atom, *data;
2653    ngx_mp4_stsc_atom_t  *stsc_atom;
2654    ngx_http_mp4_trak_t  *trak;
2655
2656    /* sample-to-chunk atom */
2657
2658    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsc atom");
2659
2660    atom_header = ngx_mp4_atom_header(mp4);
2661    stsc_atom = (ngx_mp4_stsc_atom_t *) atom_header;
2662    ngx_mp4_set_atom_name(stsc_atom, 's', 't', 's', 'c');
2663
2664    if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t) > atom_data_size) {
2665        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2666                      "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
2667        return NGX_ERROR;
2668    }
2669
2670    entries = ngx_mp4_get_32value(stsc_atom->entries);
2671
2672    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2673                   "sample-to-chunk entries:%uD", entries);
2674
2675    if (ngx_mp4_atom_data_size(ngx_mp4_stsc_atom_t)
2676        + entries * sizeof(ngx_mp4_stsc_entry_t) > atom_data_size)
2677    {
2678        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2679                      "\"%s\" mp4 stsc atom too small", mp4->file.name.data);
2680        return NGX_ERROR;
2681    }
2682
2683    atom_table = atom_header + sizeof(ngx_mp4_stsc_atom_t);
2684    atom_end = atom_table + entries * sizeof(ngx_mp4_stsc_entry_t);
2685
2686    trak = ngx_mp4_last_trak(mp4);
2687    trak->sample_to_chunk_entries = entries;
2688
2689    atom = &trak->stsc_atom_buf;
2690    atom->temporary = 1;
2691    atom->pos = atom_header;
2692    atom->last = atom_table;
2693
2694    data = &trak->stsc_data_buf;
2695    data->temporary = 1;
2696    data->pos = atom_table;
2697    data->last = atom_end;
2698
2699    trak->out[NGX_HTTP_MP4_STSC_ATOM].buf = atom;
2700    trak->out[NGX_HTTP_MP4_STSC_DATA].buf = data;
2701
2702    ngx_mp4_atom_next(mp4, atom_data_size);
2703
2704    return NGX_OK;
2705}
2706
2707
2708static ngx_int_t
2709ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
2710    ngx_http_mp4_trak_t *trak)
2711{
2712    size_t                 atom_size;
2713    uint32_t               chunk;
2714    ngx_buf_t             *atom, *data;
2715    ngx_mp4_stsc_atom_t   *stsc_atom;
2716    ngx_mp4_stsc_entry_t  *entry, *end;
2717
2718    /*
2719     * mdia.minf.stbl.stsc updating requires trak->start_sample
2720     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
2721     * atom which may reside after mdia.minf
2722     */
2723
2724    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2725                   "mp4 stsc atom update");
2726
2727    data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
2728
2729    if (data == NULL) {
2730        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2731                      "no mp4 stsc atoms were found in \"%s\"",
2732                      mp4->file.name.data);
2733        return NGX_ERROR;
2734    }
2735
2736    if (trak->sample_to_chunk_entries == 0) {
2737        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2738                      "zero number of entries in stsc atom in \"%s\"",
2739                      mp4->file.name.data);
2740        return NGX_ERROR;
2741    }
2742
2743    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 1) != NGX_OK) {
2744        return NGX_ERROR;
2745    }
2746
2747    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 0) != NGX_OK) {
2748        return NGX_ERROR;
2749    }
2750
2751    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2752                   "sample-to-chunk entries:%uD",
2753                   trak->sample_to_chunk_entries);
2754
2755    entry = (ngx_mp4_stsc_entry_t *) data->pos;
2756    end = (ngx_mp4_stsc_entry_t *) data->last;
2757
2758    while (entry < end) {
2759        chunk = ngx_mp4_get_32value(entry->chunk);
2760        chunk -= trak->start_chunk;
2761        ngx_mp4_set_32value(entry->chunk, chunk);
2762        entry++;
2763    }
2764
2765    atom_size = sizeof(ngx_mp4_stsc_atom_t)
2766                + trak->sample_to_chunk_entries * sizeof(ngx_mp4_stsc_entry_t);
2767
2768    trak->size += atom_size;
2769
2770    atom = trak->out[NGX_HTTP_MP4_STSC_ATOM].buf;
2771    stsc_atom = (ngx_mp4_stsc_atom_t *) atom->pos;
2772
2773    ngx_mp4_set_32value(stsc_atom->size, atom_size);
2774    ngx_mp4_set_32value(stsc_atom->entries, trak->sample_to_chunk_entries);
2775
2776    return NGX_OK;
2777}
2778
2779
2780static ngx_int_t
2781ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
2782    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
2783{
2784    uint32_t               start_sample, chunk, samples, id, next_chunk, n,
2785                           prev_samples;
2786    ngx_buf_t             *data, *buf;
2787    ngx_uint_t             entries, target_chunk, chunk_samples;
2788    ngx_mp4_stsc_entry_t  *entry, *end, *first;
2789
2790    entries = trak->sample_to_chunk_entries - 1;
2791
2792    if (start) {
2793        start_sample = (uint32_t) trak->start_sample;
2794
2795        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2796                       "mp4 stsc crop start_sample:%uD", start_sample);
2797
2798    } else if (mp4->length) {
2799        start_sample = (uint32_t) (trak->end_sample - trak->start_sample);
2800        samples = 0;
2801
2802        data = trak->out[NGX_HTTP_MP4_STSC_START].buf;
2803
2804        if (data) {
2805            entry = (ngx_mp4_stsc_entry_t *) data->pos;
2806            samples = ngx_mp4_get_32value(entry->samples);
2807            entries--;
2808
2809            if (samples > start_sample) {
2810                samples = start_sample;
2811                ngx_mp4_set_32value(entry->samples, samples);
2812            }
2813
2814            start_sample -= samples;
2815        }
2816
2817        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2818                       "mp4 stsc crop end_sample:%uD, ext_samples:%uD",
2819                       start_sample, samples);
2820
2821    } else {
2822        return NGX_OK;
2823    }
2824
2825    data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
2826
2827    entry = (ngx_mp4_stsc_entry_t *) data->pos;
2828    end = (ngx_mp4_stsc_entry_t *) data->last;
2829
2830    chunk = ngx_mp4_get_32value(entry->chunk);
2831    samples = ngx_mp4_get_32value(entry->samples);
2832    id = ngx_mp4_get_32value(entry->id);
2833    prev_samples = 0;
2834    entry++;
2835
2836    while (entry < end) {
2837
2838        next_chunk = ngx_mp4_get_32value(entry->chunk);
2839
2840        ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2841                       "sample:%uD, chunk:%uD, chunks:%uD, "
2842                       "samples:%uD, id:%uD",
2843                       start_sample, chunk, next_chunk - chunk, samples, id);
2844
2845        n = (next_chunk - chunk) * samples;
2846
2847        if (start_sample < n) {
2848            goto found;
2849        }
2850
2851        start_sample -= n;
2852
2853        prev_samples = samples;
2854        chunk = next_chunk;
2855        samples = ngx_mp4_get_32value(entry->samples);
2856        id = ngx_mp4_get_32value(entry->id);
2857        entries--;
2858        entry++;
2859    }
2860
2861    next_chunk = trak->chunks + 1;
2862
2863    ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2864                   "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD",
2865                   start_sample, chunk, next_chunk - chunk, samples);
2866
2867    n = (next_chunk - chunk) * samples;
2868
2869    if (start_sample > n) {
2870        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2871                      "%s time is out mp4 stsc chunks in \"%s\"",
2872                      start ? "start" : "end", mp4->file.name.data);
2873        return NGX_ERROR;
2874    }
2875
2876found:
2877
2878    entries++;
2879    entry--;
2880
2881    if (samples == 0) {
2882        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
2883                      "zero number of samples in \"%s\"",
2884                      mp4->file.name.data);
2885        return NGX_ERROR;
2886    }
2887
2888    target_chunk = chunk - 1;
2889    target_chunk += start_sample / samples;
2890    chunk_samples = start_sample % samples;
2891
2892    if (start) {
2893        data->pos = (u_char *) entry;
2894
2895        trak->sample_to_chunk_entries = entries;
2896        trak->start_chunk = target_chunk;
2897        trak->start_chunk_samples = chunk_samples;
2898
2899        ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1);
2900
2901        samples -= chunk_samples;
2902
2903        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2904                       "start_chunk:%ui, start_chunk_samples:%ui",
2905                       trak->start_chunk, trak->start_chunk_samples);
2906
2907    } else {
2908        if (start_sample) {
2909            data->last = (u_char *) (entry + 1);
2910            trak->sample_to_chunk_entries -= entries - 1;
2911            trak->end_chunk_samples = samples;
2912
2913        } else {
2914            data->last = (u_char *) entry;
2915            trak->sample_to_chunk_entries -= entries;
2916            trak->end_chunk_samples = prev_samples;
2917        }
2918
2919        if (chunk_samples) {
2920            trak->end_chunk = target_chunk + 1;
2921            trak->end_chunk_samples = chunk_samples;
2922
2923        } else {
2924            trak->end_chunk = target_chunk;
2925        }
2926
2927        samples = chunk_samples;
2928        next_chunk = chunk + 1;
2929
2930        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
2931                       "end_chunk:%ui, end_chunk_samples:%ui",
2932                       trak->end_chunk, trak->end_chunk_samples);
2933    }
2934
2935    if (chunk_samples && next_chunk - target_chunk == 2) {
2936
2937        ngx_mp4_set_32value(entry->samples, samples);
2938
2939    } else if (chunk_samples && start) {
2940
2941        first = &trak->stsc_start_chunk_entry;
2942        ngx_mp4_set_32value(first->chunk, 1);
2943        ngx_mp4_set_32value(first->samples, samples);
2944        ngx_mp4_set_32value(first->id, id);
2945
2946        buf = &trak->stsc_start_chunk_buf;
2947        buf->temporary = 1;
2948        buf->pos = (u_char *) first;
2949        buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
2950
2951        trak->out[NGX_HTTP_MP4_STSC_START].buf = buf;
2952
2953        ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 2);
2954
2955        trak->sample_to_chunk_entries++;
2956
2957    } else if (chunk_samples) {
2958
2959        first = &trak->stsc_end_chunk_entry;
2960        ngx_mp4_set_32value(first->chunk, trak->end_chunk - trak->start_chunk);
2961        ngx_mp4_set_32value(first->samples, samples);
2962        ngx_mp4_set_32value(first->id, id);
2963
2964        buf = &trak->stsc_end_chunk_buf;
2965        buf->temporary = 1;
2966        buf->pos = (u_char *) first;
2967        buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
2968
2969        trak->out[NGX_HTTP_MP4_STSC_END].buf = buf;
2970
2971        trak->sample_to_chunk_entries++;
2972    }
2973
2974    return NGX_OK;
2975}
2976
2977
2978typedef struct {
2979    u_char    size[4];
2980    u_char    name[4];
2981    u_char    version[1];
2982    u_char    flags[3];
2983    u_char    uniform_size[4];
2984    u_char    entries[4];
2985} ngx_mp4_stsz_atom_t;
2986
2987
2988static ngx_int_t
2989ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
2990{
2991    u_char               *atom_header, *atom_table, *atom_end;
2992    size_t                atom_size;
2993    uint32_t              entries, size;
2994    ngx_buf_t            *atom, *data;
2995    ngx_mp4_stsz_atom_t  *stsz_atom;
2996    ngx_http_mp4_trak_t  *trak;
2997
2998    /* sample sizes atom */
2999
3000    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stsz atom");
3001
3002    atom_header = ngx_mp4_atom_header(mp4);
3003    stsz_atom = (ngx_mp4_stsz_atom_t *) atom_header;
3004    ngx_mp4_set_atom_name(stsz_atom, 's', 't', 's', 'z');
3005
3006    if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t) > atom_data_size) {
3007        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3008                      "\"%s\" mp4 stsz atom too small", mp4->file.name.data);
3009        return NGX_ERROR;
3010    }
3011
3012    size = ngx_mp4_get_32value(stsz_atom->uniform_size);
3013    entries = ngx_mp4_get_32value(stsz_atom->entries);
3014
3015    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3016                   "sample uniform size:%uD, entries:%uD", size, entries);
3017
3018    trak = ngx_mp4_last_trak(mp4);
3019    trak->sample_sizes_entries = entries;
3020
3021    atom_table = atom_header + sizeof(ngx_mp4_stsz_atom_t);
3022
3023    atom = &trak->stsz_atom_buf;
3024    atom->temporary = 1;
3025    atom->pos = atom_header;
3026    atom->last = atom_table;
3027
3028    trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf = atom;
3029
3030    if (size == 0) {
3031        if (ngx_mp4_atom_data_size(ngx_mp4_stsz_atom_t)
3032            + entries * sizeof(uint32_t) > atom_data_size)
3033        {
3034            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3035                          "\"%s\" mp4 stsz atom too small",
3036                          mp4->file.name.data);
3037            return NGX_ERROR;
3038        }
3039
3040        atom_end = atom_table + entries * sizeof(uint32_t);
3041
3042        data = &trak->stsz_data_buf;
3043        data->temporary = 1;
3044        data->pos = atom_table;
3045        data->last = atom_end;
3046
3047        trak->out[NGX_HTTP_MP4_STSZ_DATA].buf = data;
3048
3049    } else {
3050        /* if size != 0 then all samples are the same size */
3051        /* TODO : chunk samples */
3052        atom_size = sizeof(ngx_mp4_atom_header_t) + (size_t) atom_data_size;
3053        ngx_mp4_set_32value(atom_header, atom_size);
3054        trak->size += atom_size;
3055    }
3056
3057    ngx_mp4_atom_next(mp4, atom_data_size);
3058
3059    return NGX_OK;
3060}
3061
3062
3063static ngx_int_t
3064ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
3065    ngx_http_mp4_trak_t *trak)
3066{
3067    size_t                atom_size;
3068    uint32_t             *pos, *end, entries;
3069    ngx_buf_t            *atom, *data;
3070    ngx_mp4_stsz_atom_t  *stsz_atom;
3071
3072    /*
3073     * mdia.minf.stbl.stsz updating requires trak->start_sample
3074     * from mdia.minf.stbl.stts which depends on value from mdia.mdhd
3075     * atom which may reside after mdia.minf
3076     */
3077
3078    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3079                   "mp4 stsz atom update");
3080
3081    data = trak->out[NGX_HTTP_MP4_STSZ_DATA].buf;
3082
3083    if (data) {
3084        entries = trak->sample_sizes_entries;
3085
3086        if (trak->start_sample > entries) {
3087            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3088                          "start time is out mp4 stsz samples in \"%s\"",
3089                          mp4->file.name.data);
3090            return NGX_ERROR;
3091        }
3092
3093        entries -= trak->start_sample;
3094        data->pos += trak->start_sample * sizeof(uint32_t);
3095        end = (uint32_t *) data->pos;
3096
3097        for (pos = end - trak->start_chunk_samples; pos < end; pos++) {
3098            trak->start_chunk_samples_size += ngx_mp4_get_32value(pos);
3099        }
3100
3101        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3102                       "chunk samples sizes:%uL",
3103                       trak->start_chunk_samples_size);
3104
3105        if (mp4->length) {
3106            if (trak->end_sample - trak->start_sample > entries) {
3107                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3108                              "end time is out mp4 stsz samples in \"%s\"",
3109                              mp4->file.name.data);
3110                return NGX_ERROR;
3111            }
3112
3113            entries = trak->end_sample - trak->start_sample;
3114            data->last = data->pos + entries * sizeof(uint32_t);
3115            end = (uint32_t *) data->last;
3116
3117            for (pos = end - trak->end_chunk_samples; pos < end; pos++) {
3118                trak->end_chunk_samples_size += ngx_mp4_get_32value(pos);
3119            }
3120
3121            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3122                           "mp4 stsz end_chunk_samples_size:%uL",
3123                           trak->end_chunk_samples_size);
3124        }
3125
3126        atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos);
3127        trak->size += atom_size;
3128
3129        atom = trak->out[NGX_HTTP_MP4_STSZ_ATOM].buf;
3130        stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos;
3131
3132        ngx_mp4_set_32value(stsz_atom->size, atom_size);
3133        ngx_mp4_set_32value(stsz_atom->entries, entries);
3134    }
3135
3136    return NGX_OK;
3137}
3138
3139
3140typedef struct {
3141    u_char    size[4];
3142    u_char    name[4];
3143    u_char    version[1];
3144    u_char    flags[3];
3145    u_char    entries[4];
3146} ngx_mp4_stco_atom_t;
3147
3148
3149static ngx_int_t
3150ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
3151{
3152    u_char               *atom_header, *atom_table, *atom_end;
3153    uint32_t              entries;
3154    ngx_buf_t            *atom, *data;
3155    ngx_mp4_stco_atom_t  *stco_atom;
3156    ngx_http_mp4_trak_t  *trak;
3157
3158    /* chunk offsets atom */
3159
3160    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 stco atom");
3161
3162    atom_header = ngx_mp4_atom_header(mp4);
3163    stco_atom = (ngx_mp4_stco_atom_t *) atom_header;
3164    ngx_mp4_set_atom_name(stco_atom, 's', 't', 'c', 'o');
3165
3166    if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t) > atom_data_size) {
3167        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3168                      "\"%s\" mp4 stco atom too small", mp4->file.name.data);
3169        return NGX_ERROR;
3170    }
3171
3172    entries = ngx_mp4_get_32value(stco_atom->entries);
3173
3174    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
3175
3176    if (ngx_mp4_atom_data_size(ngx_mp4_stco_atom_t)
3177        + entries * sizeof(uint32_t) > atom_data_size)
3178    {
3179        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3180                      "\"%s\" mp4 stco atom too small", mp4->file.name.data);
3181        return NGX_ERROR;
3182    }
3183
3184    atom_table = atom_header + sizeof(ngx_mp4_stco_atom_t);
3185    atom_end = atom_table + entries * sizeof(uint32_t);
3186
3187    trak = ngx_mp4_last_trak(mp4);
3188    trak->chunks = entries;
3189
3190    atom = &trak->stco_atom_buf;
3191    atom->temporary = 1;
3192    atom->pos = atom_header;
3193    atom->last = atom_table;
3194
3195    data = &trak->stco_data_buf;
3196    data->temporary = 1;
3197    data->pos = atom_table;
3198    data->last = atom_end;
3199
3200    trak->out[NGX_HTTP_MP4_STCO_ATOM].buf = atom;
3201    trak->out[NGX_HTTP_MP4_STCO_DATA].buf = data;
3202
3203    ngx_mp4_atom_next(mp4, atom_data_size);
3204
3205    return NGX_OK;
3206}
3207
3208
3209static ngx_int_t
3210ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
3211    ngx_http_mp4_trak_t *trak)
3212{
3213    size_t                atom_size;
3214    uint32_t              entries;
3215    ngx_buf_t            *atom, *data;
3216    ngx_mp4_stco_atom_t  *stco_atom;
3217
3218    /*
3219     * mdia.minf.stbl.stco updating requires trak->start_chunk
3220     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
3221     * atom which may reside after mdia.minf
3222     */
3223
3224    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3225                   "mp4 stco atom update");
3226
3227    data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
3228
3229    if (data == NULL) {
3230        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3231                      "no mp4 stco atoms were found in \"%s\"",
3232                      mp4->file.name.data);
3233        return NGX_ERROR;
3234    }
3235
3236    if (trak->start_chunk > trak->chunks) {
3237        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3238                      "start time is out mp4 stco chunks in \"%s\"",
3239                      mp4->file.name.data);
3240        return NGX_ERROR;
3241    }
3242
3243    data->pos += trak->start_chunk * sizeof(uint32_t);
3244
3245    trak->start_offset = ngx_mp4_get_32value(data->pos);
3246    trak->start_offset += trak->start_chunk_samples_size;
3247    ngx_mp4_set_32value(data->pos, trak->start_offset);
3248
3249    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3250                   "start chunk offset:%O", trak->start_offset);
3251
3252    if (mp4->length) {
3253
3254        if (trak->end_chunk > trak->chunks) {
3255            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3256                          "end time is out mp4 stco chunks in \"%s\"",
3257                          mp4->file.name.data);
3258            return NGX_ERROR;
3259        }
3260
3261        entries = trak->end_chunk - trak->start_chunk;
3262        data->last = data->pos + entries * sizeof(uint32_t);
3263
3264        if (entries) {
3265            trak->end_offset =
3266                            ngx_mp4_get_32value(data->last - sizeof(uint32_t));
3267            trak->end_offset += trak->end_chunk_samples_size;
3268
3269            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3270                           "end chunk offset:%O", trak->end_offset);
3271        }
3272
3273    } else {
3274        entries = trak->chunks - trak->start_chunk;
3275        trak->end_offset = mp4->mdat_data.buf->file_last;
3276    }
3277
3278    if (entries == 0) {
3279        trak->start_offset = mp4->end;
3280        trak->end_offset = 0;
3281    }
3282
3283    atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos);
3284    trak->size += atom_size;
3285
3286    atom = trak->out[NGX_HTTP_MP4_STCO_ATOM].buf;
3287    stco_atom = (ngx_mp4_stco_atom_t *) atom->pos;
3288
3289    ngx_mp4_set_32value(stco_atom->size, atom_size);
3290    ngx_mp4_set_32value(stco_atom->entries, entries);
3291
3292    return NGX_OK;
3293}
3294
3295
3296static void
3297ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
3298    ngx_http_mp4_trak_t *trak, int32_t adjustment)
3299{
3300    uint32_t    offset, *entry, *end;
3301    ngx_buf_t  *data;
3302
3303    /*
3304     * moov.trak.mdia.minf.stbl.stco adjustment requires
3305     * minimal start offset of all traks and new moov atom size
3306     */
3307
3308    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3309                   "mp4 stco atom adjustment");
3310
3311    data = trak->out[NGX_HTTP_MP4_STCO_DATA].buf;
3312    entry = (uint32_t *) data->pos;
3313    end = (uint32_t *) data->last;
3314
3315    while (entry < end) {
3316        offset = ngx_mp4_get_32value(entry);
3317        offset += adjustment;
3318        ngx_mp4_set_32value(entry, offset);
3319        entry++;
3320    }
3321}
3322
3323
3324typedef struct {
3325    u_char    size[4];
3326    u_char    name[4];
3327    u_char    version[1];
3328    u_char    flags[3];
3329    u_char    entries[4];
3330} ngx_mp4_co64_atom_t;
3331
3332
3333static ngx_int_t
3334ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4, uint64_t atom_data_size)
3335{
3336    u_char               *atom_header, *atom_table, *atom_end;
3337    uint32_t              entries;
3338    ngx_buf_t            *atom, *data;
3339    ngx_mp4_co64_atom_t  *co64_atom;
3340    ngx_http_mp4_trak_t  *trak;
3341
3342    /* chunk offsets atom */
3343
3344    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "mp4 co64 atom");
3345
3346    atom_header = ngx_mp4_atom_header(mp4);
3347    co64_atom = (ngx_mp4_co64_atom_t *) atom_header;
3348    ngx_mp4_set_atom_name(co64_atom, 'c', 'o', '6', '4');
3349
3350    if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t) > atom_data_size) {
3351        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3352                      "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
3353        return NGX_ERROR;
3354    }
3355
3356    entries = ngx_mp4_get_32value(co64_atom->entries);
3357
3358    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "chunks:%uD", entries);
3359
3360    if (ngx_mp4_atom_data_size(ngx_mp4_co64_atom_t)
3361        + entries * sizeof(uint64_t) > atom_data_size)
3362    {
3363        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3364                      "\"%s\" mp4 co64 atom too small", mp4->file.name.data);
3365        return NGX_ERROR;
3366    }
3367
3368    atom_table = atom_header + sizeof(ngx_mp4_co64_atom_t);
3369    atom_end = atom_table + entries * sizeof(uint64_t);
3370
3371    trak = ngx_mp4_last_trak(mp4);
3372    trak->chunks = entries;
3373
3374    atom = &trak->co64_atom_buf;
3375    atom->temporary = 1;
3376    atom->pos = atom_header;
3377    atom->last = atom_table;
3378
3379    data = &trak->co64_data_buf;
3380    data->temporary = 1;
3381    data->pos = atom_table;
3382    data->last = atom_end;
3383
3384    trak->out[NGX_HTTP_MP4_CO64_ATOM].buf = atom;
3385    trak->out[NGX_HTTP_MP4_CO64_DATA].buf = data;
3386
3387    ngx_mp4_atom_next(mp4, atom_data_size);
3388
3389    return NGX_OK;
3390}
3391
3392
3393static ngx_int_t
3394ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
3395    ngx_http_mp4_trak_t *trak)
3396{
3397    size_t                atom_size;
3398    uint64_t              entries;
3399    ngx_buf_t            *atom, *data;
3400    ngx_mp4_co64_atom_t  *co64_atom;
3401
3402    /*
3403     * mdia.minf.stbl.co64 updating requires trak->start_chunk
3404     * from mdia.minf.stbl.stsc which depends on value from mdia.mdhd
3405     * atom which may reside after mdia.minf
3406     */
3407
3408    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3409                   "mp4 co64 atom update");
3410
3411    data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
3412
3413    if (data == NULL) {
3414        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3415                      "no mp4 co64 atoms were found in \"%s\"",
3416                      mp4->file.name.data);
3417        return NGX_ERROR;
3418    }
3419
3420    if (trak->start_chunk > trak->chunks) {
3421        ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3422                      "start time is out mp4 co64 chunks in \"%s\"",
3423                      mp4->file.name.data);
3424        return NGX_ERROR;
3425    }
3426
3427    data->pos += trak->start_chunk * sizeof(uint64_t);
3428
3429    trak->start_offset = ngx_mp4_get_64value(data->pos);
3430    trak->start_offset += trak->start_chunk_samples_size;
3431    ngx_mp4_set_64value(data->pos, trak->start_offset);
3432
3433    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3434                   "start chunk offset:%O", trak->start_offset);
3435
3436    if (mp4->length) {
3437
3438        if (trak->end_chunk > trak->chunks) {
3439            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
3440                          "end time is out mp4 co64 chunks in \"%s\"",
3441                          mp4->file.name.data);
3442            return NGX_ERROR;
3443        }
3444
3445        entries = trak->end_chunk - trak->start_chunk;
3446        data->last = data->pos + entries * sizeof(uint64_t);
3447
3448        if (entries) {
3449            trak->end_offset =
3450                            ngx_mp4_get_64value(data->last - sizeof(uint64_t));
3451            trak->end_offset += trak->end_chunk_samples_size;
3452
3453            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3454                           "end chunk offset:%O", trak->end_offset);
3455        }
3456
3457    } else {
3458        entries = trak->chunks - trak->start_chunk;
3459        trak->end_offset = mp4->mdat_data.buf->file_last;
3460    }
3461
3462    if (entries == 0) {
3463        trak->start_offset = mp4->end;
3464        trak->end_offset = 0;
3465    }
3466
3467    atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos);
3468    trak->size += atom_size;
3469
3470    atom = trak->out[NGX_HTTP_MP4_CO64_ATOM].buf;
3471    co64_atom = (ngx_mp4_co64_atom_t *) atom->pos;
3472
3473    ngx_mp4_set_32value(co64_atom->size, atom_size);
3474    ngx_mp4_set_32value(co64_atom->entries, entries);
3475
3476    return NGX_OK;
3477}
3478
3479
3480static void
3481ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
3482    ngx_http_mp4_trak_t *trak, off_t adjustment)
3483{
3484    uint64_t    offset, *entry, *end;
3485    ngx_buf_t  *data;
3486
3487    /*
3488     * moov.trak.mdia.minf.stbl.co64 adjustment requires
3489     * minimal start offset of all traks and new moov atom size
3490     */
3491
3492    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
3493                   "mp4 co64 atom adjustment");
3494
3495    data = trak->out[NGX_HTTP_MP4_CO64_DATA].buf;
3496    entry = (uint64_t *) data->pos;
3497    end = (uint64_t *) data->last;
3498
3499    while (entry < end) {
3500        offset = ngx_mp4_get_64value(entry);
3501        offset += adjustment;
3502        ngx_mp4_set_64value(entry, offset);
3503        entry++;
3504    }
3505}
3506
3507
3508static char *
3509ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
3510{
3511    ngx_http_core_loc_conf_t  *clcf;
3512
3513    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
3514    clcf->handler = ngx_http_mp4_handler;
3515
3516    return NGX_CONF_OK;
3517}
3518
3519
3520static void *
3521ngx_http_mp4_create_conf(ngx_conf_t *cf)
3522{
3523    ngx_http_mp4_conf_t  *conf;
3524
3525    conf = ngx_palloc(cf->pool, sizeof(ngx_http_mp4_conf_t));
3526    if (conf == NULL) {
3527        return NULL;
3528    }
3529
3530    conf->buffer_size = NGX_CONF_UNSET_SIZE;
3531    conf->max_buffer_size = NGX_CONF_UNSET_SIZE;
3532
3533    return conf;
3534}
3535
3536
3537static char *
3538ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child)
3539{
3540    ngx_http_mp4_conf_t *prev = parent;
3541    ngx_http_mp4_conf_t *conf = child;
3542
3543    ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, 512 * 1024);
3544    ngx_conf_merge_size_value(conf->max_buffer_size, prev->max_buffer_size,
3545                              10 * 1024 * 1024);
3546
3547    return NGX_CONF_OK;
3548}
3549