Back to home page

Nginx displayed by LXR

Source navigation ]
Diff markup ]
Identifier search ]
general search ]
 
 
Version: nginx-1.15.12 ]​[ nginx-1.16.0 ]​

0001 
0002 /*
0003  * Copyright (C) Igor Sysoev
0004  * Copyright (C) Nginx, Inc.
0005  */
0006 
0007 #include <ngx_config.h>
0008 #include <ngx_core.h>
0009 #include <ngx_http.h>
0010 
0011 
0012 #define NGX_HTTP_MP4_TRAK_ATOM     0
0013 #define NGX_HTTP_MP4_TKHD_ATOM     1
0014 #define NGX_HTTP_MP4_MDIA_ATOM     2
0015 #define NGX_HTTP_MP4_MDHD_ATOM     3
0016 #define NGX_HTTP_MP4_HDLR_ATOM     4
0017 #define NGX_HTTP_MP4_MINF_ATOM     5
0018 #define NGX_HTTP_MP4_VMHD_ATOM     6
0019 #define NGX_HTTP_MP4_SMHD_ATOM     7
0020 #define NGX_HTTP_MP4_DINF_ATOM     8
0021 #define NGX_HTTP_MP4_STBL_ATOM     9
0022 #define NGX_HTTP_MP4_STSD_ATOM    10
0023 #define NGX_HTTP_MP4_STTS_ATOM    11
0024 #define NGX_HTTP_MP4_STTS_DATA    12
0025 #define NGX_HTTP_MP4_STSS_ATOM    13
0026 #define NGX_HTTP_MP4_STSS_DATA    14
0027 #define NGX_HTTP_MP4_CTTS_ATOM    15
0028 #define NGX_HTTP_MP4_CTTS_DATA    16
0029 #define NGX_HTTP_MP4_STSC_ATOM    17
0030 #define NGX_HTTP_MP4_STSC_START   18
0031 #define NGX_HTTP_MP4_STSC_DATA    19
0032 #define NGX_HTTP_MP4_STSC_END     20
0033 #define NGX_HTTP_MP4_STSZ_ATOM    21
0034 #define NGX_HTTP_MP4_STSZ_DATA    22
0035 #define NGX_HTTP_MP4_STCO_ATOM    23
0036 #define NGX_HTTP_MP4_STCO_DATA    24
0037 #define NGX_HTTP_MP4_CO64_ATOM    25
0038 #define NGX_HTTP_MP4_CO64_DATA    26
0039 
0040 #define NGX_HTTP_MP4_LAST_ATOM    NGX_HTTP_MP4_CO64_DATA
0041 
0042 
0043 typedef struct {
0044     size_t                buffer_size;
0045     size_t                max_buffer_size;
0046 } ngx_http_mp4_conf_t;
0047 
0048 
0049 typedef struct {
0050     u_char                chunk[4];
0051     u_char                samples[4];
0052     u_char                id[4];
0053 } ngx_mp4_stsc_entry_t;
0054 
0055 
0056 typedef struct {
0057     uint32_t              timescale;
0058     uint32_t              time_to_sample_entries;
0059     uint32_t              sample_to_chunk_entries;
0060     uint32_t              sync_samples_entries;
0061     uint32_t              composition_offset_entries;
0062     uint32_t              sample_sizes_entries;
0063     uint32_t              chunks;
0064 
0065     ngx_uint_t            start_sample;
0066     ngx_uint_t            end_sample;
0067     ngx_uint_t            start_chunk;
0068     ngx_uint_t            end_chunk;
0069     ngx_uint_t            start_chunk_samples;
0070     ngx_uint_t            end_chunk_samples;
0071     uint64_t              start_chunk_samples_size;
0072     uint64_t              end_chunk_samples_size;
0073     off_t                 start_offset;
0074     off_t                 end_offset;
0075 
0076     size_t                tkhd_size;
0077     size_t                mdhd_size;
0078     size_t                hdlr_size;
0079     size_t                vmhd_size;
0080     size_t                smhd_size;
0081     size_t                dinf_size;
0082     size_t                size;
0083 
0084     ngx_chain_t           out[NGX_HTTP_MP4_LAST_ATOM + 1];
0085 
0086     ngx_buf_t             trak_atom_buf;
0087     ngx_buf_t             tkhd_atom_buf;
0088     ngx_buf_t             mdia_atom_buf;
0089     ngx_buf_t             mdhd_atom_buf;
0090     ngx_buf_t             hdlr_atom_buf;
0091     ngx_buf_t             minf_atom_buf;
0092     ngx_buf_t             vmhd_atom_buf;
0093     ngx_buf_t             smhd_atom_buf;
0094     ngx_buf_t             dinf_atom_buf;
0095     ngx_buf_t             stbl_atom_buf;
0096     ngx_buf_t             stsd_atom_buf;
0097     ngx_buf_t             stts_atom_buf;
0098     ngx_buf_t             stts_data_buf;
0099     ngx_buf_t             stss_atom_buf;
0100     ngx_buf_t             stss_data_buf;
0101     ngx_buf_t             ctts_atom_buf;
0102     ngx_buf_t             ctts_data_buf;
0103     ngx_buf_t             stsc_atom_buf;
0104     ngx_buf_t             stsc_start_chunk_buf;
0105     ngx_buf_t             stsc_end_chunk_buf;
0106     ngx_buf_t             stsc_data_buf;
0107     ngx_buf_t             stsz_atom_buf;
0108     ngx_buf_t             stsz_data_buf;
0109     ngx_buf_t             stco_atom_buf;
0110     ngx_buf_t             stco_data_buf;
0111     ngx_buf_t             co64_atom_buf;
0112     ngx_buf_t             co64_data_buf;
0113 
0114     ngx_mp4_stsc_entry_t  stsc_start_chunk_entry;
0115     ngx_mp4_stsc_entry_t  stsc_end_chunk_entry;
0116 } ngx_http_mp4_trak_t;
0117 
0118 
0119 typedef struct {
0120     ngx_file_t            file;
0121 
0122     u_char               *buffer;
0123     u_char               *buffer_start;
0124     u_char               *buffer_pos;
0125     u_char               *buffer_end;
0126     size_t                buffer_size;
0127 
0128     off_t                 offset;
0129     off_t                 end;
0130     off_t                 content_length;
0131     ngx_uint_t            start;
0132     ngx_uint_t            length;
0133     uint32_t              timescale;
0134     ngx_http_request_t   *request;
0135     ngx_array_t           trak;
0136     ngx_http_mp4_trak_t   traks[2];
0137 
0138     size_t                ftyp_size;
0139     size_t                moov_size;
0140 
0141     ngx_chain_t          *out;
0142     ngx_chain_t           ftyp_atom;
0143     ngx_chain_t           moov_atom;
0144     ngx_chain_t           mvhd_atom;
0145     ngx_chain_t           mdat_atom;
0146     ngx_chain_t           mdat_data;
0147 
0148     ngx_buf_t             ftyp_atom_buf;
0149     ngx_buf_t             moov_atom_buf;
0150     ngx_buf_t             mvhd_atom_buf;
0151     ngx_buf_t             mdat_atom_buf;
0152     ngx_buf_t             mdat_data_buf;
0153 
0154     u_char                moov_atom_header[8];
0155     u_char                mdat_atom_header[16];
0156 } ngx_http_mp4_file_t;
0157 
0158 
0159 typedef struct {
0160     char                 *name;
0161     ngx_int_t           (*handler)(ngx_http_mp4_file_t *mp4,
0162                                    uint64_t atom_data_size);
0163 } ngx_http_mp4_atom_handler_t;
0164 
0165 
0166 #define ngx_mp4_atom_header(mp4)   (mp4->buffer_pos - 8)
0167 #define ngx_mp4_atom_data(mp4)     mp4->buffer_pos
0168 #define ngx_mp4_atom_data_size(t)  (uint64_t) (sizeof(t) - 8)
0169 
0170 
0171 #define ngx_mp4_atom_next(mp4, n)                                             \
0172                                                                               \
0173     if (n > (size_t) (mp4->buffer_end - mp4->buffer_pos)) {                   \
0174         mp4->buffer_pos = mp4->buffer_end;                                    \
0175                                                                               \
0176     } else {                                                                  \
0177         mp4->buffer_pos += (size_t) n;                                        \
0178     }                                                                         \
0179                                                                               \
0180     mp4->offset += n
0181 
0182 
0183 #define ngx_mp4_set_atom_name(p, n1, n2, n3, n4)                              \
0184     ((u_char *) (p))[4] = n1;                                                 \
0185     ((u_char *) (p))[5] = n2;                                                 \
0186     ((u_char *) (p))[6] = n3;                                                 \
0187     ((u_char *) (p))[7] = n4
0188 
0189 #define ngx_mp4_get_32value(p)                                                \
0190     ( ((uint32_t) ((u_char *) (p))[0] << 24)                                  \
0191     + (           ((u_char *) (p))[1] << 16)                                  \
0192     + (           ((u_char *) (p))[2] << 8)                                   \
0193     + (           ((u_char *) (p))[3]) )
0194 
0195 #define ngx_mp4_set_32value(p, n)                                             \
0196     ((u_char *) (p))[0] = (u_char) ((n) >> 24);                               \
0197     ((u_char *) (p))[1] = (u_char) ((n) >> 16);                               \
0198     ((u_char *) (p))[2] = (u_char) ((n) >> 8);                                \
0199     ((u_char *) (p))[3] = (u_char)  (n)
0200 
0201 #define ngx_mp4_get_64value(p)                                                \
0202     ( ((uint64_t) ((u_char *) (p))[0] << 56)                                  \
0203     + ((uint64_t) ((u_char *) (p))[1] << 48)                                  \
0204     + ((uint64_t) ((u_char *) (p))[2] << 40)                                  \
0205     + ((uint64_t) ((u_char *) (p))[3] << 32)                                  \
0206     + ((uint64_t) ((u_char *) (p))[4] << 24)                                  \
0207     + (           ((u_char *) (p))[5] << 16)                                  \
0208     + (           ((u_char *) (p))[6] << 8)                                   \
0209     + (           ((u_char *) (p))[7]) )
0210 
0211 #define ngx_mp4_set_64value(p, n)                                             \
0212     ((u_char *) (p))[0] = (u_char) ((uint64_t) (n) >> 56);                    \
0213     ((u_char *) (p))[1] = (u_char) ((uint64_t) (n) >> 48);                    \
0214     ((u_char *) (p))[2] = (u_char) ((uint64_t) (n) >> 40);                    \
0215     ((u_char *) (p))[3] = (u_char) ((uint64_t) (n) >> 32);                    \
0216     ((u_char *) (p))[4] = (u_char) (           (n) >> 24);                    \
0217     ((u_char *) (p))[5] = (u_char) (           (n) >> 16);                    \
0218     ((u_char *) (p))[6] = (u_char) (           (n) >> 8);                     \
0219     ((u_char *) (p))[7] = (u_char)             (n)
0220 
0221 #define ngx_mp4_last_trak(mp4)                                                \
0222     &((ngx_http_mp4_trak_t *) mp4->trak.elts)[mp4->trak.nelts - 1]
0223 
0224 
0225 static ngx_int_t ngx_http_mp4_handler(ngx_http_request_t *r);
0226 static ngx_int_t ngx_http_mp4_atofp(u_char *line, size_t n, size_t point);
0227 
0228 static ngx_int_t ngx_http_mp4_process(ngx_http_mp4_file_t *mp4);
0229 static ngx_int_t ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
0230     ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size);
0231 static ngx_int_t ngx_http_mp4_read(ngx_http_mp4_file_t *mp4, size_t size);
0232 static ngx_int_t ngx_http_mp4_read_ftyp_atom(ngx_http_mp4_file_t *mp4,
0233     uint64_t atom_data_size);
0234 static ngx_int_t ngx_http_mp4_read_moov_atom(ngx_http_mp4_file_t *mp4,
0235     uint64_t atom_data_size);
0236 static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4,
0237     uint64_t atom_data_size);
0238 static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4,
0239     off_t start_offset, off_t end_offset);
0240 static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4,
0241     uint64_t atom_data_size);
0242 static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4,
0243     uint64_t atom_data_size);
0244 static void ngx_http_mp4_update_trak_atom(ngx_http_mp4_file_t *mp4,
0245     ngx_http_mp4_trak_t *trak);
0246 static ngx_int_t ngx_http_mp4_read_cmov_atom(ngx_http_mp4_file_t *mp4,
0247     uint64_t atom_data_size);
0248 static ngx_int_t ngx_http_mp4_read_tkhd_atom(ngx_http_mp4_file_t *mp4,
0249     uint64_t atom_data_size);
0250 static ngx_int_t ngx_http_mp4_read_mdia_atom(ngx_http_mp4_file_t *mp4,
0251     uint64_t atom_data_size);
0252 static void ngx_http_mp4_update_mdia_atom(ngx_http_mp4_file_t *mp4,
0253     ngx_http_mp4_trak_t *trak);
0254 static ngx_int_t ngx_http_mp4_read_mdhd_atom(ngx_http_mp4_file_t *mp4,
0255     uint64_t atom_data_size);
0256 static ngx_int_t ngx_http_mp4_read_hdlr_atom(ngx_http_mp4_file_t *mp4,
0257     uint64_t atom_data_size);
0258 static ngx_int_t ngx_http_mp4_read_minf_atom(ngx_http_mp4_file_t *mp4,
0259     uint64_t atom_data_size);
0260 static void ngx_http_mp4_update_minf_atom(ngx_http_mp4_file_t *mp4,
0261     ngx_http_mp4_trak_t *trak);
0262 static ngx_int_t ngx_http_mp4_read_dinf_atom(ngx_http_mp4_file_t *mp4,
0263     uint64_t atom_data_size);
0264 static ngx_int_t ngx_http_mp4_read_vmhd_atom(ngx_http_mp4_file_t *mp4,
0265     uint64_t atom_data_size);
0266 static ngx_int_t ngx_http_mp4_read_smhd_atom(ngx_http_mp4_file_t *mp4,
0267     uint64_t atom_data_size);
0268 static ngx_int_t ngx_http_mp4_read_stbl_atom(ngx_http_mp4_file_t *mp4,
0269     uint64_t atom_data_size);
0270 static void ngx_http_mp4_update_stbl_atom(ngx_http_mp4_file_t *mp4,
0271     ngx_http_mp4_trak_t *trak);
0272 static ngx_int_t ngx_http_mp4_read_stsd_atom(ngx_http_mp4_file_t *mp4,
0273     uint64_t atom_data_size);
0274 static ngx_int_t ngx_http_mp4_read_stts_atom(ngx_http_mp4_file_t *mp4,
0275     uint64_t atom_data_size);
0276 static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
0277     ngx_http_mp4_trak_t *trak);
0278 static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
0279     ngx_http_mp4_trak_t *trak, ngx_uint_t start);
0280 static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
0281     uint64_t atom_data_size);
0282 static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
0283     ngx_http_mp4_trak_t *trak);
0284 static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
0285     ngx_http_mp4_trak_t *trak, ngx_uint_t start);
0286 static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4,
0287     uint64_t atom_data_size);
0288 static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
0289     ngx_http_mp4_trak_t *trak);
0290 static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
0291     ngx_http_mp4_trak_t *trak, ngx_uint_t start);
0292 static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4,
0293     uint64_t atom_data_size);
0294 static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
0295     ngx_http_mp4_trak_t *trak);
0296 static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
0297     ngx_http_mp4_trak_t *trak, ngx_uint_t start);
0298 static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4,
0299     uint64_t atom_data_size);
0300 static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
0301     ngx_http_mp4_trak_t *trak);
0302 static ngx_int_t ngx_http_mp4_read_stco_atom(ngx_http_mp4_file_t *mp4,
0303     uint64_t atom_data_size);
0304 static ngx_int_t ngx_http_mp4_update_stco_atom(ngx_http_mp4_file_t *mp4,
0305     ngx_http_mp4_trak_t *trak);
0306 static void ngx_http_mp4_adjust_stco_atom(ngx_http_mp4_file_t *mp4,
0307     ngx_http_mp4_trak_t *trak, int32_t adjustment);
0308 static ngx_int_t ngx_http_mp4_read_co64_atom(ngx_http_mp4_file_t *mp4,
0309     uint64_t atom_data_size);
0310 static ngx_int_t ngx_http_mp4_update_co64_atom(ngx_http_mp4_file_t *mp4,
0311     ngx_http_mp4_trak_t *trak);
0312 static void ngx_http_mp4_adjust_co64_atom(ngx_http_mp4_file_t *mp4,
0313     ngx_http_mp4_trak_t *trak, off_t adjustment);
0314 
0315 static char *ngx_http_mp4(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
0316 static void *ngx_http_mp4_create_conf(ngx_conf_t *cf);
0317 static char *ngx_http_mp4_merge_conf(ngx_conf_t *cf, void *parent, void *child);
0318 
0319 
0320 static ngx_command_t  ngx_http_mp4_commands[] = {
0321 
0322     { ngx_string("mp4"),
0323       NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS,
0324       ngx_http_mp4,
0325       0,
0326       0,
0327       NULL },
0328 
0329     { ngx_string("mp4_buffer_size"),
0330       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0331       ngx_conf_set_size_slot,
0332       NGX_HTTP_LOC_CONF_OFFSET,
0333       offsetof(ngx_http_mp4_conf_t, buffer_size),
0334       NULL },
0335 
0336     { ngx_string("mp4_max_buffer_size"),
0337       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
0338       ngx_conf_set_size_slot,
0339       NGX_HTTP_LOC_CONF_OFFSET,
0340       offsetof(ngx_http_mp4_conf_t, max_buffer_size),
0341       NULL },
0342 
0343       ngx_null_command
0344 };
0345 
0346 
0347 static ngx_http_module_t  ngx_http_mp4_module_ctx = {
0348     NULL,                          /* preconfiguration */
0349     NULL,                          /* postconfiguration */
0350 
0351     NULL,                          /* create main configuration */
0352     NULL,                          /* init main configuration */
0353 
0354     NULL,                          /* create server configuration */
0355     NULL,                          /* merge server configuration */
0356 
0357     ngx_http_mp4_create_conf,      /* create location configuration */
0358     ngx_http_mp4_merge_conf        /* merge location configuration */
0359 };
0360 
0361 
0362 ngx_module_t  ngx_http_mp4_module = {
0363     NGX_MODULE_V1,
0364     &ngx_http_mp4_module_ctx,      /* module context */
0365     ngx_http_mp4_commands,         /* module directives */
0366     NGX_HTTP_MODULE,               /* module type */
0367     NULL,                          /* init master */
0368     NULL,                          /* init module */
0369     NULL,                          /* init process */
0370     NULL,                          /* init thread */
0371     NULL,                          /* exit thread */
0372     NULL,                          /* exit process */
0373     NULL,                          /* exit master */
0374     NGX_MODULE_V1_PADDING
0375 };
0376 
0377 
0378 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_atoms[] = {
0379     { "ftyp", ngx_http_mp4_read_ftyp_atom },
0380     { "moov", ngx_http_mp4_read_moov_atom },
0381     { "mdat", ngx_http_mp4_read_mdat_atom },
0382     { NULL, NULL }
0383 };
0384 
0385 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_moov_atoms[] = {
0386     { "mvhd", ngx_http_mp4_read_mvhd_atom },
0387     { "trak", ngx_http_mp4_read_trak_atom },
0388     { "cmov", ngx_http_mp4_read_cmov_atom },
0389     { NULL, NULL }
0390 };
0391 
0392 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_trak_atoms[] = {
0393     { "tkhd", ngx_http_mp4_read_tkhd_atom },
0394     { "mdia", ngx_http_mp4_read_mdia_atom },
0395     { NULL, NULL }
0396 };
0397 
0398 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_mdia_atoms[] = {
0399     { "mdhd", ngx_http_mp4_read_mdhd_atom },
0400     { "hdlr", ngx_http_mp4_read_hdlr_atom },
0401     { "minf", ngx_http_mp4_read_minf_atom },
0402     { NULL, NULL }
0403 };
0404 
0405 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_minf_atoms[] = {
0406     { "vmhd", ngx_http_mp4_read_vmhd_atom },
0407     { "smhd", ngx_http_mp4_read_smhd_atom },
0408     { "dinf", ngx_http_mp4_read_dinf_atom },
0409     { "stbl", ngx_http_mp4_read_stbl_atom },
0410     { NULL, NULL }
0411 };
0412 
0413 static ngx_http_mp4_atom_handler_t  ngx_http_mp4_stbl_atoms[] = {
0414     { "stsd", ngx_http_mp4_read_stsd_atom },
0415     { "stts", ngx_http_mp4_read_stts_atom },
0416     { "stss", ngx_http_mp4_read_stss_atom },
0417     { "ctts", ngx_http_mp4_read_ctts_atom },
0418     { "stsc", ngx_http_mp4_read_stsc_atom },
0419     { "stsz", ngx_http_mp4_read_stsz_atom },
0420     { "stco", ngx_http_mp4_read_stco_atom },
0421     { "co64", ngx_http_mp4_read_co64_atom },
0422     { NULL, NULL }
0423 };
0424 
0425 
0426 static ngx_int_t
0427 ngx_http_mp4_handler(ngx_http_request_t *r)
0428 {
0429     u_char                    *last;
0430     size_t                     root;
0431     ngx_int_t                  rc, start, end;
0432     ngx_uint_t                 level, length;
0433     ngx_str_t                  path, value;
0434     ngx_log_t                 *log;
0435     ngx_buf_t                 *b;
0436     ngx_chain_t                out;
0437     ngx_http_mp4_file_t       *mp4;
0438     ngx_open_file_info_t       of;
0439     ngx_http_core_loc_conf_t  *clcf;
0440 
0441     if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD))) {
0442         return NGX_HTTP_NOT_ALLOWED;
0443     }
0444 
0445     if (r->uri.data[r->uri.len - 1] == '/') {
0446         return NGX_DECLINED;
0447     }
0448 
0449     rc = ngx_http_discard_request_body(r);
0450 
0451     if (rc != NGX_OK) {
0452         return rc;
0453     }
0454 
0455     last = ngx_http_map_uri_to_path(r, &path, &root, 0);
0456     if (last == NULL) {
0457         return NGX_HTTP_INTERNAL_SERVER_ERROR;
0458     }
0459 
0460     log = r->connection->log;
0461 
0462     path.len = last - path.data;
0463 
0464     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
0465                    "http mp4 filename: \"%V\"", &path);
0466 
0467     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
0468 
0469     ngx_memzero(&of, sizeof(ngx_open_file_info_t));
0470 
0471     of.read_ahead = clcf->read_ahead;
0472     of.directio = NGX_MAX_OFF_T_VALUE;
0473     of.valid = clcf->open_file_cache_valid;
0474     of.min_uses = clcf->open_file_cache_min_uses;
0475     of.errors = clcf->open_file_cache_errors;
0476     of.events = clcf->open_file_cache_events;
0477 
0478     if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
0479         return NGX_HTTP_INTERNAL_SERVER_ERROR;
0480     }
0481 
0482     if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
0483         != NGX_OK)
0484     {
0485         switch (of.err) {
0486 
0487         case 0:
0488             return NGX_HTTP_INTERNAL_SERVER_ERROR;
0489 
0490         case NGX_ENOENT:
0491         case NGX_ENOTDIR:
0492         case NGX_ENAMETOOLONG:
0493 
0494             level = NGX_LOG_ERR;
0495             rc = NGX_HTTP_NOT_FOUND;
0496             break;
0497 
0498         case NGX_EACCES:
0499 #if (NGX_HAVE_OPENAT)
0500         case NGX_EMLINK:
0501         case NGX_ELOOP:
0502 #endif
0503 
0504             level = NGX_LOG_ERR;
0505             rc = NGX_HTTP_FORBIDDEN;
0506             break;
0507 
0508         default:
0509 
0510             level = NGX_LOG_CRIT;
0511             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
0512             break;
0513         }
0514 
0515         if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
0516             ngx_log_error(level, log, of.err,
0517                           "%s \"%s\" failed", of.failed, path.data);
0518         }
0519 
0520         return rc;
0521     }
0522 
0523     if (!of.is_file) {
0524 
0525         if (ngx_close_file(of.fd) == NGX_FILE_ERROR) {
0526             ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
0527                           ngx_close_file_n " \"%s\" failed", path.data);
0528         }
0529 
0530         return NGX_DECLINED;
0531     }
0532 
0533     r->root_tested = !r->error_page;
0534     r->allow_ranges = 1;
0535 
0536     start = -1;
0537     length = 0;
0538     r->headers_out.content_length_n = of.size;
0539     mp4 = NULL;
0540     b = NULL;
0541 
0542     if (r->args.len) {
0543 
0544         if (ngx_http_arg(r, (u_char *) "start", 5, &value) == NGX_OK) {
0545 
0546             /*
0547              * A Flash player may send start value with a lot of digits
0548              * after dot so a custom function is used instead of ngx_atofp().
0549              */
0550 
0551             start = ngx_http_mp4_atofp(value.data, value.len, 3);
0552         }
0553 
0554         if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {
0555 
0556             end = ngx_http_mp4_atofp(value.data, value.len, 3);
0557 
0558             if (end > 0) {
0559                 if (start < 0) {
0560                     start = 0;
0561                 }
0562 
0563                 if (end > start) {
0564                     length = end - start;
0565                 }
0566             }
0567         }
0568     }
0569 
0570     if (start >= 0) {
0571         r->single_range = 1;
0572 
0573         mp4 = ngx_pcalloc(r->pool, sizeof(ngx_http_mp4_file_t));
0574         if (mp4 == NULL) {
0575             return NGX_HTTP_INTERNAL_SERVER_ERROR;
0576         }
0577 
0578         mp4->file.fd = of.fd;
0579         mp4->file.name = path;
0580         mp4->file.log = r->connection->log;
0581         mp4->end = of.size;
0582         mp4->start = (ngx_uint_t) start;
0583         mp4->length = length;
0584         mp4->request = r;
0585 
0586         switch (ngx_http_mp4_process(mp4)) {
0587 
0588         case NGX_DECLINED:
0589             if (mp4->buffer) {
0590                 ngx_pfree(r->pool, mp4->buffer);
0591             }
0592 
0593             ngx_pfree(r->pool, mp4);
0594             mp4 = NULL;
0595 
0596             break;
0597 
0598         case NGX_OK:
0599             r->headers_out.content_length_n = mp4->content_length;
0600             break;
0601 
0602         default: /* NGX_ERROR */
0603             if (mp4->buffer) {
0604                 ngx_pfree(r->pool, mp4->buffer);
0605             }
0606 
0607             ngx_pfree(r->pool, mp4);
0608 
0609             return NGX_HTTP_INTERNAL_SERVER_ERROR;
0610         }
0611     }
0612 
0613     log->action = "sending mp4 to client";
0614 
0615     if (clcf->directio <= of.size) {
0616 
0617         /*
0618          * DIRECTIO is set on transfer only
0619          * to allow kernel to cache "moov" atom
0620          */
0621 
0622         if (ngx_directio_on(of.fd) == NGX_FILE_ERROR) {
0623             ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
0624                           ngx_directio_on_n " \"%s\" failed", path.data);
0625         }
0626 
0627         of.is_directio = 1;
0628 
0629         if (mp4) {
0630             mp4->file.directio = 1;
0631         }
0632     }
0633 
0634     r->headers_out.status = NGX_HTTP_OK;
0635     r->headers_out.last_modified_time = of.mtime;
0636 
0637     if (ngx_http_set_etag(r) != NGX_OK) {
0638         return NGX_HTTP_INTERNAL_SERVER_ERROR;
0639     }
0640 
0641     if (ngx_http_set_content_type(r) != NGX_OK) {
0642         return NGX_HTTP_INTERNAL_SERVER_ERROR;
0643     }
0644 
0645     if (mp4 == NULL) {
0646         b = ngx_calloc_buf(r->pool);
0647         if (b == NULL) {
0648             return NGX_HTTP_INTERNAL_SERVER_ERROR;
0649         }
0650 
0651         b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
0652         if (b->file == NULL) {
0653             return NGX_HTTP_INTERNAL_SERVER_ERROR;
0654         }
0655     }
0656 
0657     rc = ngx_http_send_header(r);
0658 
0659     if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
0660         return rc;
0661     }
0662 
0663     if (mp4) {
0664         return ngx_http_output_filter(r, mp4->out);
0665     }
0666 
0667     b->file_pos = 0;
0668     b->file_last = of.size;
0669 
0670     b->in_file = b->file_last ? 1 : 0;
0671     b->last_buf = (r == r->main) ? 1 : 0;
0672     b->last_in_chain = 1;
0673 
0674     b->file->fd = of.fd;
0675     b->file->name = path;
0676     b->file->log = log;
0677     b->file->directio = of.is_directio;
0678 
0679     out.buf = b;
0680     out.next = NULL;
0681 
0682     return ngx_http_output_filter(r, &out);
0683 }
0684 
0685 
0686 static ngx_int_t
0687 ngx_http_mp4_atofp(u_char *line, size_t n, size_t point)
0688 {
0689     ngx_int_t   value, cutoff, cutlim;
0690     ngx_uint_t  dot;
0691 
0692     /* same as ngx_atofp(), but allows additional digits */
0693 
0694     if (n == 0) {
0695         return NGX_ERROR;
0696     }
0697 
0698     cutoff = NGX_MAX_INT_T_VALUE / 10;
0699     cutlim = NGX_MAX_INT_T_VALUE % 10;
0700 
0701     dot = 0;
0702 
0703     for (value = 0; n--; line++) {
0704 
0705         if (*line == '.') {
0706             if (dot) {
0707                 return NGX_ERROR;
0708             }
0709 
0710             dot = 1;
0711             continue;
0712         }
0713 
0714         if (*line < '0' || *line > '9') {
0715             return NGX_ERROR;
0716         }
0717 
0718         if (point == 0) {
0719             continue;
0720         }
0721 
0722         if (value >= cutoff && (value > cutoff || *line - '0' > cutlim)) {
0723             return NGX_ERROR;
0724         }
0725 
0726         value = value * 10 + (*line - '0');
0727         point -= dot;
0728     }
0729 
0730     while (point--) {
0731         if (value > cutoff) {
0732             return NGX_ERROR;
0733         }
0734 
0735         value = value * 10;
0736     }
0737 
0738     return value;
0739 }
0740 
0741 
0742 static ngx_int_t
0743 ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
0744 {
0745     off_t                  start_offset, end_offset, adjustment;
0746     ngx_int_t              rc;
0747     ngx_uint_t             i, j;
0748     ngx_chain_t          **prev;
0749     ngx_http_mp4_trak_t   *trak;
0750     ngx_http_mp4_conf_t   *conf;
0751 
0752     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
0753                    "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
0754 
0755     conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
0756 
0757     mp4->buffer_size = conf->buffer_size;
0758 
0759     rc = ngx_http_mp4_read_atom(mp4, ngx_http_mp4_atoms, mp4->end);
0760     if (rc != NGX_OK) {
0761         return rc;
0762     }
0763 
0764     if (mp4->trak.nelts == 0) {
0765         ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0766                       "no mp4 trak atoms were found in \"%s\"",
0767                       mp4->file.name.data);
0768         return NGX_ERROR;
0769     }
0770 
0771     if (mp4->mdat_atom.buf == NULL) {
0772         ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0773                       "no mp4 mdat atom was found in \"%s\"",
0774                       mp4->file.name.data);
0775         return NGX_ERROR;
0776     }
0777 
0778     prev = &mp4->out;
0779 
0780     if (mp4->ftyp_atom.buf) {
0781         *prev = &mp4->ftyp_atom;
0782         prev = &mp4->ftyp_atom.next;
0783     }
0784 
0785     *prev = &mp4->moov_atom;
0786     prev = &mp4->moov_atom.next;
0787 
0788     if (mp4->mvhd_atom.buf) {
0789         mp4->moov_size += mp4->mvhd_atom_buf.last - mp4->mvhd_atom_buf.pos;
0790         *prev = &mp4->mvhd_atom;
0791         prev = &mp4->mvhd_atom.next;
0792     }
0793 
0794     start_offset = mp4->end;
0795     end_offset = 0;
0796     trak = mp4->trak.elts;
0797 
0798     for (i = 0; i < mp4->trak.nelts; i++) {
0799 
0800         if (ngx_http_mp4_update_stts_atom(mp4, &trak[i]) != NGX_OK) {
0801             return NGX_ERROR;
0802         }
0803 
0804         if (ngx_http_mp4_update_stss_atom(mp4, &trak[i]) != NGX_OK) {
0805             return NGX_ERROR;
0806         }
0807 
0808         ngx_http_mp4_update_ctts_atom(mp4, &trak[i]);
0809 
0810         if (ngx_http_mp4_update_stsc_atom(mp4, &trak[i]) != NGX_OK) {
0811             return NGX_ERROR;
0812         }
0813 
0814         if (ngx_http_mp4_update_stsz_atom(mp4, &trak[i]) != NGX_OK) {
0815             return NGX_ERROR;
0816         }
0817 
0818         if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
0819             if (ngx_http_mp4_update_co64_atom(mp4, &trak[i]) != NGX_OK) {
0820                 return NGX_ERROR;
0821             }
0822 
0823         } else {
0824             if (ngx_http_mp4_update_stco_atom(mp4, &trak[i]) != NGX_OK) {
0825                 return NGX_ERROR;
0826             }
0827         }
0828 
0829         ngx_http_mp4_update_stbl_atom(mp4, &trak[i]);
0830         ngx_http_mp4_update_minf_atom(mp4, &trak[i]);
0831         trak[i].size += trak[i].mdhd_size;
0832         trak[i].size += trak[i].hdlr_size;
0833         ngx_http_mp4_update_mdia_atom(mp4, &trak[i]);
0834         trak[i].size += trak[i].tkhd_size;
0835         ngx_http_mp4_update_trak_atom(mp4, &trak[i]);
0836 
0837         mp4->moov_size += trak[i].size;
0838 
0839         if (start_offset > trak[i].start_offset) {
0840             start_offset = trak[i].start_offset;
0841         }
0842 
0843         if (end_offset < trak[i].end_offset) {
0844             end_offset = trak[i].end_offset;
0845         }
0846 
0847         *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
0848         prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
0849 
0850         for (j = 0; j < NGX_HTTP_MP4_LAST_ATOM + 1; j++) {
0851             if (trak[i].out[j].buf) {
0852                 *prev = &trak[i].out[j];
0853                 prev = &trak[i].out[j].next;
0854             }
0855         }
0856     }
0857 
0858     if (end_offset < start_offset) {
0859         end_offset = start_offset;
0860     }
0861 
0862     mp4->moov_size += 8;
0863 
0864     ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size);
0865     ngx_mp4_set_atom_name(mp4->moov_atom_header, 'm', 'o', 'o', 'v');
0866     mp4->content_length += mp4->moov_size;
0867 
0868     *prev = &mp4->mdat_atom;
0869 
0870     if (start_offset > mp4->mdat_data.buf->file_last) {
0871         ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0872                       "start time is out mp4 mdat atom in \"%s\"",
0873                       mp4->file.name.data);
0874         return NGX_ERROR;
0875     }
0876 
0877     adjustment = mp4->ftyp_size + mp4->moov_size
0878                  + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset)
0879                  - start_offset;
0880 
0881     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
0882                    "mp4 adjustment:%O", adjustment);
0883 
0884     for (i = 0; i < mp4->trak.nelts; i++) {
0885         if (trak[i].out[NGX_HTTP_MP4_CO64_DATA].buf) {
0886             ngx_http_mp4_adjust_co64_atom(mp4, &trak[i], adjustment);
0887         } else {
0888             ngx_http_mp4_adjust_stco_atom(mp4, &trak[i], (int32_t) adjustment);
0889         }
0890     }
0891 
0892     return NGX_OK;
0893 }
0894 
0895 
0896 typedef struct {
0897     u_char    size[4];
0898     u_char    name[4];
0899 } ngx_mp4_atom_header_t;
0900 
0901 typedef struct {
0902     u_char    size[4];
0903     u_char    name[4];
0904     u_char    size64[8];
0905 } ngx_mp4_atom_header64_t;
0906 
0907 
0908 static ngx_int_t
0909 ngx_http_mp4_read_atom(ngx_http_mp4_file_t *mp4,
0910     ngx_http_mp4_atom_handler_t *atom, uint64_t atom_data_size)
0911 {
0912     off_t        end;
0913     size_t       atom_header_size;
0914     u_char      *atom_header, *atom_name;
0915     uint64_t     atom_size;
0916     ngx_int_t    rc;
0917     ngx_uint_t   n;
0918 
0919     end = mp4->offset + atom_data_size;
0920 
0921     while (mp4->offset < end) {
0922 
0923         if (ngx_http_mp4_read(mp4, sizeof(uint32_t)) != NGX_OK) {
0924             return NGX_ERROR;
0925         }
0926 
0927         atom_header = mp4->buffer_pos;
0928         atom_size = ngx_mp4_get_32value(atom_header);
0929         atom_header_size = sizeof(ngx_mp4_atom_header_t);
0930 
0931         if (atom_size == 0) {
0932             ngx_log_debug0(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
0933                            "mp4 atom end");
0934             return NGX_OK;
0935         }
0936 
0937         if (atom_size < sizeof(ngx_mp4_atom_header_t)) {
0938 
0939             if (atom_size == 1) {
0940 
0941                 if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header64_t))
0942                     != NGX_OK)
0943                 {
0944                     return NGX_ERROR;
0945                 }
0946 
0947                 /* 64-bit atom size */
0948                 atom_header = mp4->buffer_pos;
0949                 atom_size = ngx_mp4_get_64value(atom_header + 8);
0950                 atom_header_size = sizeof(ngx_mp4_atom_header64_t);
0951 
0952                 if (atom_size < sizeof(ngx_mp4_atom_header64_t)) {
0953                     ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0954                                   "\"%s\" mp4 atom is too small:%uL",
0955                                   mp4->file.name.data, atom_size);
0956                     return NGX_ERROR;
0957                 }
0958 
0959             } else {
0960                 ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0961                               "\"%s\" mp4 atom is too small:%uL",
0962                               mp4->file.name.data, atom_size);
0963                 return NGX_ERROR;
0964             }
0965         }
0966 
0967         if (ngx_http_mp4_read(mp4, sizeof(ngx_mp4_atom_header_t)) != NGX_OK) {
0968             return NGX_ERROR;
0969         }
0970 
0971         atom_header = mp4->buffer_pos;
0972         atom_name = atom_header + sizeof(uint32_t);
0973 
0974         ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
0975                        "mp4 atom: %*s @%O:%uL",
0976                        (size_t) 4, atom_name, mp4->offset, atom_size);
0977 
0978         if (atom_size > (uint64_t) (NGX_MAX_OFF_T_VALUE - mp4->offset)
0979             || mp4->offset + (off_t) atom_size > end)
0980         {
0981             ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
0982                           "\"%s\" mp4 atom too large:%uL",
0983                           mp4->file.name.data, atom_size);
0984             return NGX_ERROR;
0985         }
0986 
0987         for (n = 0; atom[n].name; n++) {
0988 
0989             if (ngx_strncmp(atom_name, atom[n].name, 4) == 0) {
0990 
0991                 ngx_mp4_atom_next(mp4, atom_header_size);
0992 
0993                 rc = atom[n].handler(mp4, atom_size - atom_header_size);
0994                 if (rc != NGX_OK) {
0995                     return rc;
0996                 }
0997 
0998                 goto next;
0999             }
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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 *
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 *
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 *
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 }