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