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