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