/* * Copyright (C) Roman Arutyunyan */ #include #include #include "ngx_rtmp_play_module.h" #include "ngx_rtmp_codec_module.h" #include "ngx_rtmp_streams.h" static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf); static ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex); static ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f); static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f); static ngx_int_t ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t offset); static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f); static ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts); static ngx_int_t ngx_rtmp_mp4_reset(ngx_rtmp_session_t *s); #define NGX_RTMP_MP4_MAX_FRAMES 8 #pragma pack(push,4) /* disable zero-sized array warning by msvc */ #if (NGX_WIN32) #pragma warning(push) #pragma warning(disable:4200) #endif typedef struct { uint32_t first_chunk; uint32_t samples_per_chunk; uint32_t sample_descrption_index; } ngx_rtmp_mp4_chunk_entry_t; typedef struct { uint32_t version_flags; uint32_t entry_count; ngx_rtmp_mp4_chunk_entry_t entries[0]; } ngx_rtmp_mp4_chunks_t; typedef struct { uint32_t sample_count; uint32_t sample_delta; } ngx_rtmp_mp4_time_entry_t; typedef struct { uint32_t version_flags; uint32_t entry_count; ngx_rtmp_mp4_time_entry_t entries[0]; } ngx_rtmp_mp4_times_t; typedef struct { uint32_t sample_count; uint32_t sample_offset; } ngx_rtmp_mp4_delay_entry_t; typedef struct { uint32_t version_flags; uint32_t entry_count; ngx_rtmp_mp4_delay_entry_t entries[0]; } ngx_rtmp_mp4_delays_t; typedef struct { uint32_t version_flags; uint32_t entry_count; uint32_t entries[0]; } ngx_rtmp_mp4_keys_t; typedef struct { uint32_t version_flags; uint32_t sample_size; uint32_t sample_count; uint32_t entries[0]; } ngx_rtmp_mp4_sizes_t; typedef struct { uint32_t version_flags; uint32_t field_size; uint32_t sample_count; uint32_t entries[0]; } ngx_rtmp_mp4_sizes2_t; typedef struct { uint32_t version_flags; uint32_t entry_count; uint32_t entries[0]; } ngx_rtmp_mp4_offsets_t; typedef struct { uint32_t version_flags; uint32_t entry_count; uint64_t entries[0]; } ngx_rtmp_mp4_offsets64_t; #if (NGX_WIN32) #pragma warning(pop) #endif #pragma pack(pop) typedef struct { uint32_t timestamp; uint32_t last_timestamp; off_t offset; size_t size; ngx_int_t key; uint32_t delay; unsigned not_first:1; unsigned valid:1; ngx_uint_t pos; ngx_uint_t key_pos; ngx_uint_t chunk; ngx_uint_t chunk_pos; ngx_uint_t chunk_count; ngx_uint_t time_pos; ngx_uint_t time_count; ngx_uint_t delay_pos; ngx_uint_t delay_count; ngx_uint_t size_pos; } ngx_rtmp_mp4_cursor_t; typedef struct { ngx_uint_t id; ngx_int_t type; ngx_int_t codec; uint32_t csid; u_char fhdr; ngx_int_t time_scale; uint64_t duration; u_char *header; size_t header_size; unsigned header_sent:1; ngx_rtmp_mp4_times_t *times; ngx_rtmp_mp4_delays_t *delays; ngx_rtmp_mp4_keys_t *keys; ngx_rtmp_mp4_chunks_t *chunks; ngx_rtmp_mp4_sizes_t *sizes; ngx_rtmp_mp4_sizes2_t *sizes2; ngx_rtmp_mp4_offsets_t *offsets; ngx_rtmp_mp4_offsets64_t *offsets64; ngx_rtmp_mp4_cursor_t cursor; } ngx_rtmp_mp4_track_t; typedef struct { void *mmaped; size_t mmaped_size; ngx_fd_t extra; unsigned meta_sent:1; ngx_rtmp_mp4_track_t tracks[2]; ngx_rtmp_mp4_track_t *track; ngx_uint_t ntracks; ngx_uint_t width; ngx_uint_t height; ngx_uint_t nchannels; ngx_uint_t sample_size; ngx_uint_t sample_rate; ngx_int_t atracks, vtracks; ngx_int_t aindex, vindex; uint32_t start_timestamp, epoch; } ngx_rtmp_mp4_ctx_t; #define ngx_rtmp_mp4_make_tag(a, b, c, d) \ ((uint32_t)d << 24 | (uint32_t)c << 16 | (uint32_t)b << 8 | (uint32_t)a) static ngx_inline uint32_t ngx_rtmp_mp4_to_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint64_t ts) { return (uint32_t) (ts * 1000 / t->time_scale); } static ngx_inline uint32_t ngx_rtmp_mp4_from_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts) { return (uint64_t) ts * t->time_scale / 1000; } #define NGX_RTMP_MP4_BUFLEN_ADDON 1000 static u_char ngx_rtmp_mp4_buffer[1024*1024]; #if (NGX_WIN32) static void * ngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra) { void *data; *extra = CreateFileMapping(fd, NULL, PAGE_READONLY, (DWORD) ((uint64_t) size >> 32), (DWORD) (size & 0xffffffff), NULL); if (*extra == NULL) { return NULL; } data = MapViewOfFile(*extra, FILE_MAP_READ, (DWORD) ((uint64_t) offset >> 32), (DWORD) (offset & 0xffffffff), size); if (data == NULL) { CloseHandle(*extra); } /* * non-NULL result means map view handle is open * and should be closed later */ return data; } static ngx_int_t ngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra) { ngx_int_t rc; rc = NGX_OK; if (UnmapViewOfFile(data) == 0) { rc = NGX_ERROR; } if (CloseHandle(*extra) == 0) { rc = NGX_ERROR; } return rc; } #else static void * ngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra) { void *data; data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset); /* valid address is never NULL since there's no MAP_FIXED */ return data == MAP_FAILED ? NULL : data; } static ngx_int_t ngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra) { return munmap(data, size); } #endif static ngx_int_t ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos, u_char *last); typedef ngx_int_t (*ngx_rtmp_mp4_box_pt)(ngx_rtmp_session_t *s, u_char *pos, u_char *last); typedef struct { uint32_t tag; ngx_rtmp_mp4_box_pt handler; } ngx_rtmp_mp4_box_t; static ngx_rtmp_mp4_box_t ngx_rtmp_mp4_boxes[] = { { ngx_rtmp_mp4_make_tag('t','r','a','k'), ngx_rtmp_mp4_parse_trak }, { ngx_rtmp_mp4_make_tag('m','d','i','a'), ngx_rtmp_mp4_parse }, { ngx_rtmp_mp4_make_tag('m','d','h','d'), ngx_rtmp_mp4_parse_mdhd }, { ngx_rtmp_mp4_make_tag('h','d','l','r'), ngx_rtmp_mp4_parse_hdlr }, { ngx_rtmp_mp4_make_tag('m','i','n','f'), ngx_rtmp_mp4_parse }, { ngx_rtmp_mp4_make_tag('s','t','b','l'), ngx_rtmp_mp4_parse }, { ngx_rtmp_mp4_make_tag('s','t','s','d'), ngx_rtmp_mp4_parse_stsd }, { ngx_rtmp_mp4_make_tag('s','t','s','c'), ngx_rtmp_mp4_parse_stsc }, { ngx_rtmp_mp4_make_tag('s','t','t','s'), ngx_rtmp_mp4_parse_stts }, { ngx_rtmp_mp4_make_tag('c','t','t','s'), ngx_rtmp_mp4_parse_ctts }, { ngx_rtmp_mp4_make_tag('s','t','s','s'), ngx_rtmp_mp4_parse_stss }, { ngx_rtmp_mp4_make_tag('s','t','s','z'), ngx_rtmp_mp4_parse_stsz }, { ngx_rtmp_mp4_make_tag('s','t','z','2'), ngx_rtmp_mp4_parse_stz2 }, { ngx_rtmp_mp4_make_tag('s','t','c','o'), ngx_rtmp_mp4_parse_stco }, { ngx_rtmp_mp4_make_tag('c','o','6','4'), ngx_rtmp_mp4_parse_co64 }, { ngx_rtmp_mp4_make_tag('a','v','c','1'), ngx_rtmp_mp4_parse_avc1 }, { ngx_rtmp_mp4_make_tag('a','v','c','C'), ngx_rtmp_mp4_parse_avcC }, { ngx_rtmp_mp4_make_tag('m','p','4','a'), ngx_rtmp_mp4_parse_mp4a }, { ngx_rtmp_mp4_make_tag('m','p','4','v'), ngx_rtmp_mp4_parse_mp4v }, { ngx_rtmp_mp4_make_tag('e','s','d','s'), ngx_rtmp_mp4_parse_esds }, { ngx_rtmp_mp4_make_tag('.','m','p','3'), ngx_rtmp_mp4_parse_mp3 }, { ngx_rtmp_mp4_make_tag('n','m','o','s'), ngx_rtmp_mp4_parse_nmos }, { ngx_rtmp_mp4_make_tag('s','p','e','x'), ngx_rtmp_mp4_parse_spex }, { ngx_rtmp_mp4_make_tag('w','a','v','e'), ngx_rtmp_mp4_parse } }; static ngx_int_t ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, u_char *last); static ngx_int_t ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, u_char *last); typedef ngx_int_t (*ngx_rtmp_mp4_descriptor_pt)(ngx_rtmp_session_t *s, u_char *pos, u_char *last); typedef struct { uint8_t tag; ngx_rtmp_mp4_descriptor_pt handler; } ngx_rtmp_mp4_descriptor_t; static ngx_rtmp_mp4_descriptor_t ngx_rtmp_mp4_descriptors[] = { { 0x03, ngx_rtmp_mp4_parse_es }, /* MPEG ES Descriptor */ { 0x04, ngx_rtmp_mp4_parse_dc }, /* MPEG DecoderConfig Descriptor */ { 0x05, ngx_rtmp_mp4_parse_ds } /* MPEG DecoderSpec Descriptor */ }; static ngx_rtmp_module_t ngx_rtmp_mp4_module_ctx = { NULL, /* preconfiguration */ ngx_rtmp_mp4_postconfiguration, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create app configuration */ NULL /* merge app configuration */ }; ngx_module_t ngx_rtmp_mp4_module = { NGX_MODULE_V1, &ngx_rtmp_mp4_module_ctx, /* module context */ NULL, /* module directives */ NGX_RTMP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track) { return NGX_OK; } ctx->track = (ctx->ntracks == sizeof(ctx->tracks) / sizeof(ctx->tracks[0])) ? NULL : &ctx->tracks[ctx->ntracks]; if (ctx->track) { ngx_memzero(ctx->track, sizeof(*ctx->track)); ctx->track->id = ctx->ntracks; ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: trying track %ui", ctx->ntracks); } if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { return NGX_ERROR; } if (ctx->track && ctx->track->type && (ctx->ntracks == 0 || ctx->tracks[0].type != ctx->tracks[ctx->ntracks].type)) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: adding track %ui", ctx->ntracks); if (ctx->track->type == NGX_RTMP_MSG_AUDIO) { if (ctx->atracks++ != ctx->aindex) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: skipping audio track %ui!=%ui", ctx->atracks - 1, ctx->aindex); ctx->track = NULL; return NGX_OK; } } else { if (ctx->vtracks++ != ctx->vindex) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: skipping video track %i!=%i", ctx->vtracks - 1, ctx->vindex); ctx->track = NULL; return NGX_OK; } } ++ctx->ntracks; } else { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: ignoring track %ui", ctx->ntracks); } ctx->track = NULL; return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; uint8_t version; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL) { return NGX_OK; } t = ctx->track; if (pos + 1 > last) { return NGX_ERROR; } version = *(uint8_t *) pos; switch (version) { case 0: if (pos + 20 > last) { return NGX_ERROR; } pos += 12; t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos); pos += 4; t->duration = ngx_rtmp_r32(*(uint32_t *) pos); break; case 1: if (pos + 28 > last) { return NGX_ERROR; } pos += 20; t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos); pos += 4; t->duration = ngx_rtmp_r64(*(uint64_t *) pos); break; default: return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: duration time_scale=%ui duration=%uL", t->time_scale, t->duration); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; uint32_t type; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL) { return NGX_OK; } if (pos + 12 > last) { return NGX_ERROR; } type = *(uint32_t *)(pos + 8); if (type == ngx_rtmp_mp4_make_tag('v','i','d','e')) { ctx->track->type = NGX_RTMP_MSG_VIDEO; ctx->track->csid = NGX_RTMP_CSID_VIDEO; ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: video track"); } else if (type == ngx_rtmp_mp4_make_tag('s','o','u','n')) { ctx->track->type = NGX_RTMP_MSG_AUDIO; ctx->track->csid = NGX_RTMP_CSID_AUDIO; ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: audio track"); } else { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: unknown track"); } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_video(ngx_rtmp_session_t *s, u_char *pos, u_char *last, ngx_int_t codec) { ngx_rtmp_mp4_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL) { return NGX_OK; } ctx->track->codec = codec; if (pos + 78 > last) { return NGX_ERROR; } pos += 24; ctx->width = ngx_rtmp_r16(*(uint16_t *) pos); pos += 2; ctx->height = ngx_rtmp_r16(*(uint16_t *) pos); pos += 52; ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: video settings codec=%i, width=%ui, height=%ui", codec, ctx->width, ctx->height); if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { return NGX_ERROR; } ctx->track->fhdr = (u_char) ctx->track->codec; return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_audio(ngx_rtmp_session_t *s, u_char *pos, u_char *last, ngx_int_t codec) { ngx_rtmp_mp4_ctx_t *ctx; u_char *p; ngx_uint_t version; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL) { return NGX_OK; } ctx->track->codec = codec; if (pos + 28 > last) { return NGX_ERROR; } pos += 8; version = ngx_rtmp_r16(*(uint16_t *) pos); pos += 8; ctx->nchannels = ngx_rtmp_r16(*(uint16_t *) pos); pos += 2; ctx->sample_size = ngx_rtmp_r16(*(uint16_t *) pos); pos += 6; ctx->sample_rate = ngx_rtmp_r16(*(uint16_t *) pos); pos += 4; p = &ctx->track->fhdr; *p = 0; if (ctx->nchannels == 2) { *p |= 0x01; } if (ctx->sample_size == 16) { *p |= 0x02; } switch (ctx->sample_rate) { case 5512: break; case 11025: *p |= 0x04; break; case 22050: *p |= 0x08; break; default: /*44100 etc */ *p |= 0x0c; break; } ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: audio settings version=%ui, codec=%i, nchannels==%ui, " "sample_size=%ui, sample_rate=%ui", version, codec, ctx->nchannels, ctx->sample_size, ctx->sample_rate); switch (version) { case 1: pos += 16; break; case 2: pos += 36; } if (pos > last) { return NGX_ERROR; } if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) { return NGX_ERROR; } *p |= (ctx->track->codec << 4); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264); } static ngx_int_t ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264); } static ngx_int_t ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; if (pos == last) { return NGX_OK; } ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL || ctx->track->codec != NGX_RTMP_VIDEO_H264) { return NGX_OK; } ctx->track->header = pos; ctx->track->header_size = (size_t) (last - pos); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: video h264 header size=%uz", ctx->track->header_size); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3); } static ngx_int_t ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->header = pos; t->header_size = (size_t) (last - pos); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: decoder header size=%uz", t->header_size); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { uint8_t id; ngx_rtmp_mp4_ctx_t *ctx; ngx_int_t *pc; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx->track == NULL) { return NGX_OK; } if (pos + 13 > last) { return NGX_ERROR; } id = * (uint8_t *) pos; pos += 13; pc = &ctx->track->codec; switch (id) { case 0x21: *pc = NGX_RTMP_VIDEO_H264; break; case 0x40: case 0x66: case 0x67: case 0x68: *pc = NGX_RTMP_AUDIO_AAC; break; case 0x69: case 0x6b: *pc = NGX_RTMP_AUDIO_MP3; break; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: decoder descriptor id=%i codec=%i", (ngx_int_t) id, *pc); return ngx_rtmp_mp4_parse_descr(s, pos, last); } static ngx_int_t ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { uint16_t id; uint8_t flags; if (pos + 3 > last) { return NGX_ERROR; } id = ngx_rtmp_r16(*(uint16_t *) pos); pos += 2; flags = *(uint8_t *) pos; ++pos; if (flags & 0x80) { /* streamDependenceFlag */ pos += 2; } if (flags & 0x40) { /* URL_FLag */ return NGX_OK; } if (flags & 0x20) { /* OCRstreamFlag */ pos += 2; } if (pos > last) { return NGX_ERROR; } (void) id; ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: es descriptor es id=%i flags=%i", (ngx_int_t) id, (ngx_int_t) flags); return ngx_rtmp_mp4_parse_descr(s, pos, last); } static ngx_int_t ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { uint8_t tag, v; uint32_t size; ngx_uint_t n, ndesc; ngx_rtmp_mp4_descriptor_t *ds; ndesc = sizeof(ngx_rtmp_mp4_descriptors) / sizeof(ngx_rtmp_mp4_descriptors[0]); while (pos < last) { tag = *(uint8_t *) pos++; for (size = 0, n = 0; n < 4; ++n) { if (pos == last) { return NGX_ERROR; } v = *(uint8_t *) pos++; size = (size << 7) | (v & 0x7f); if (!(v & 0x80)) { break; } } if (pos + size > last) { return NGX_ERROR; } ds = ngx_rtmp_mp4_descriptors;; for (n = 0; n < ndesc; ++n, ++ds) { if (tag == ds->tag) { break; } } if (n == ndesc) { ds = NULL; } ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: descriptor%s tag=%i size=%uD", ds ? "" : " unhandled", (ngx_int_t) tag, size); if (ds && ds->handler(s, pos, pos + size) != NGX_OK) { return NGX_ERROR; } pos += size; } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { if (pos + 4 > last) { return NGX_ERROR; } pos += 4; /* version */ return ngx_rtmp_mp4_parse_descr(s, pos, last); } static ngx_int_t ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3); } static ngx_int_t ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_NELLY); } static ngx_int_t ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_SPEEX); } static ngx_int_t ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { if (pos + 8 > last) { return NGX_ERROR; } pos += 8; ngx_rtmp_mp4_parse(s, pos, last); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->chunks = (ngx_rtmp_mp4_chunks_t *) pos; if (pos + sizeof(*t->chunks) + ngx_rtmp_r32(t->chunks->entry_count) * sizeof(t->chunks->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: chunks entries=%uD", ngx_rtmp_r32(t->chunks->entry_count)); return NGX_OK; } t->chunks = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->times = (ngx_rtmp_mp4_times_t *) pos; if (pos + sizeof(*t->times) + ngx_rtmp_r32(t->times->entry_count) * sizeof(t->times->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: times entries=%uD", ngx_rtmp_r32(t->times->entry_count)); return NGX_OK; } t->times = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->delays = (ngx_rtmp_mp4_delays_t *) pos; if (pos + sizeof(*t->delays) + ngx_rtmp_r32(t->delays->entry_count) * sizeof(t->delays->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: delays entries=%uD", ngx_rtmp_r32(t->delays->entry_count)); return NGX_OK; } t->delays = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->keys = (ngx_rtmp_mp4_keys_t *) pos; if (pos + sizeof(*t->keys) + ngx_rtmp_r32(t->keys->entry_count) * sizeof(t->keys->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: keys entries=%uD", ngx_rtmp_r32(t->keys->entry_count)); return NGX_OK; } t->keys = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->sizes = (ngx_rtmp_mp4_sizes_t *) pos; if (pos + sizeof(*t->sizes) <= last && t->sizes->sample_size) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: sizes size=%uD", ngx_rtmp_r32(t->sizes->sample_size)); return NGX_OK; } if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes->sample_count) * sizeof(t->sizes->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: sizes entries=%uD", ngx_rtmp_r32(t->sizes->sample_count)); return NGX_OK; } t->sizes = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->sizes2 = (ngx_rtmp_mp4_sizes2_t *) pos; if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes2->sample_count) * ngx_rtmp_r32(t->sizes2->field_size) / 8 <= last) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: sizes2 field_size=%uD entries=%uD", ngx_rtmp_r32(t->sizes2->field_size), ngx_rtmp_r32(t->sizes2->sample_count)); return NGX_OK; } t->sizes2 = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->offsets = (ngx_rtmp_mp4_offsets_t *) pos; if (pos + sizeof(*t->offsets) + ngx_rtmp_r32(t->offsets->entry_count) * sizeof(t->offsets->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: offsets entries=%uD", ngx_rtmp_r32(t->offsets->entry_count)); return NGX_OK; } t->offsets = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); t = ctx->track; if (t == NULL) { return NGX_OK; } t->offsets64 = (ngx_rtmp_mp4_offsets64_t *) pos; if (pos + sizeof(*t->offsets64) + ngx_rtmp_r32(t->offsets64->entry_count) * sizeof(t->offsets64->entries[0]) <= last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: offsets64 entries=%uD", ngx_rtmp_r32(t->offsets64->entry_count)); return NGX_OK; } t->offsets64 = NULL; return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last) { uint32_t *hdr, tag; size_t size, nboxes; ngx_uint_t n; ngx_rtmp_mp4_box_t *b; while (pos != last) { if (pos + 8 > last) { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: too small box: size=%i", last - pos); return NGX_ERROR; } hdr = (uint32_t *) pos; size = ngx_rtmp_r32(hdr[0]); tag = hdr[1]; if (pos + size > last) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: too big box '%*s': size=%uz", 4, &tag, size); return NGX_ERROR; } b = ngx_rtmp_mp4_boxes; nboxes = sizeof(ngx_rtmp_mp4_boxes) / sizeof(ngx_rtmp_mp4_boxes[0]); for (n = 0; n < nboxes && b->tag != tag; ++n, ++b); if (n == nboxes) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: box unhandled '%*s'", 4, &tag); } else { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: box '%*s'", 4, &tag); b->handler(s, pos + 8, pos + size); } pos += size; } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_next_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_time_entry_t *te; if (t->times == NULL) { return NGX_ERROR; } cr = &t->cursor; if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui time[%ui/%uD] overflow", t->id, cr->time_pos, ngx_rtmp_r32(t->times->entry_count)); return NGX_ERROR; } te = &t->times->entries[cr->time_pos]; cr->last_timestamp = cr->timestamp; cr->timestamp += ngx_rtmp_r32(te->sample_delta); cr->not_first = 1; ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui time[%ui] [%ui/%uD][%ui/%uD]=%uD t=%uD", t->id, cr->pos, cr->time_pos, ngx_rtmp_r32(t->times->entry_count), cr->time_count, ngx_rtmp_r32(te->sample_count), ngx_rtmp_r32(te->sample_delta), cr->timestamp); cr->time_count++; cr->pos++; if (cr->time_count >= ngx_rtmp_r32(te->sample_count)) { cr->time_pos++; cr->time_count = 0; } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_seek_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, uint32_t timestamp) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_time_entry_t *te; uint32_t dt; if (t->times == NULL) { return NGX_ERROR; } cr = &t->cursor; te = t->times->entries; while (cr->time_pos < ngx_rtmp_r32(t->times->entry_count)) { dt = ngx_rtmp_r32(te->sample_delta) * ngx_rtmp_r32(te->sample_count); if (cr->timestamp + dt >= timestamp) { if (te->sample_delta == 0) { return NGX_ERROR; } cr->time_count = (timestamp - cr->timestamp) / ngx_rtmp_r32(te->sample_delta); cr->timestamp += ngx_rtmp_r32(te->sample_delta) * cr->time_count; cr->pos += cr->time_count; break; } cr->timestamp += dt; cr->pos += ngx_rtmp_r32(te->sample_count); cr->time_pos++; te++; } if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek time[%ui/%uD] overflow", t->id, cr->time_pos, ngx_rtmp_r32(t->times->entry_count)); return NGX_ERROR; } ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek time[%ui] [%ui/%uD][%ui/%uD]=%uD " "t=%uD", t->id, cr->pos, cr->time_pos, ngx_rtmp_r32(t->times->entry_count), cr->time_count, ngx_rtmp_r32(te->sample_count), ngx_rtmp_r32(te->sample_delta), cr->timestamp); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_update_offset(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_uint_t chunk; cr = &t->cursor; if (cr->chunk < 1) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui offset[%ui] underflow", t->id, cr->chunk); return NGX_ERROR; } chunk = cr->chunk - 1; if (t->offsets) { if (chunk >= ngx_rtmp_r32(t->offsets->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui offset[%ui/%uD] overflow", t->id, cr->chunk, ngx_rtmp_r32(t->offsets->entry_count)); return NGX_ERROR; } cr->offset = (off_t) ngx_rtmp_r32(t->offsets->entries[chunk]); cr->size = 0; ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui offset[%ui/%uD]=%O", t->id, cr->chunk, ngx_rtmp_r32(t->offsets->entry_count), cr->offset); return NGX_OK; } if (t->offsets64) { if (chunk >= ngx_rtmp_r32(t->offsets64->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui offset64[%ui/%uD] overflow", t->id, cr->chunk, ngx_rtmp_r32(t->offsets->entry_count)); return NGX_ERROR; } cr->offset = (off_t) ngx_rtmp_r64(t->offsets64->entries[chunk]); cr->size = 0; ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui offset64[%ui/%uD]=%O", t->id, cr->chunk, ngx_rtmp_r32(t->offsets->entry_count), cr->offset); return NGX_OK; } return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_next_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_chunk_entry_t *ce, *nce; ngx_int_t new_chunk; if (t->chunks == NULL) { return NGX_OK; } cr = &t->cursor; if (cr->chunk_pos >= ngx_rtmp_r32(t->chunks->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui chunk[%ui/%uD] overflow", t->id, cr->chunk_pos, ngx_rtmp_r32(t->chunks->entry_count)); return NGX_ERROR; } ce = &t->chunks->entries[cr->chunk_pos]; cr->chunk_count++; if (cr->chunk_count >= ngx_rtmp_r32(ce->samples_per_chunk)) { cr->chunk_count = 0; cr->chunk++; if (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) { nce = ce + 1; if (cr->chunk >= ngx_rtmp_r32(nce->first_chunk)) { cr->chunk_pos++; ce = nce; } } new_chunk = 1; } else { new_chunk = 0; } ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui chunk[%ui/%uD][%uD..%ui][%ui/%uD]", t->id, cr->chunk_pos, ngx_rtmp_r32(t->chunks->entry_count), ngx_rtmp_r32(ce->first_chunk), cr->chunk, cr->chunk_count, ngx_rtmp_r32(ce->samples_per_chunk)); if (new_chunk) { return ngx_rtmp_mp4_update_offset(s, t); } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_seek_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_chunk_entry_t *ce, *nce; ngx_uint_t pos, dpos, dchunk; cr = &t->cursor; if (t->chunks == NULL || t->chunks->entry_count == 0) { cr->chunk = 1; return NGX_OK; } ce = t->chunks->entries; pos = 0; while (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) { nce = ce + 1; dpos = (ngx_rtmp_r32(nce->first_chunk) - ngx_rtmp_r32(ce->first_chunk)) * ngx_rtmp_r32(ce->samples_per_chunk); if (pos + dpos > cr->pos) { break; } pos += dpos; ce++; cr->chunk_pos++; } if (ce->samples_per_chunk == 0) { return NGX_ERROR; } dchunk = (cr->pos - pos) / ngx_rtmp_r32(ce->samples_per_chunk); cr->chunk = ngx_rtmp_r32(ce->first_chunk) + dchunk; cr->chunk_pos = (ngx_uint_t) (ce - t->chunks->entries); cr->chunk_count = (ngx_uint_t) (cr->pos - pos - dchunk * ngx_rtmp_r32(ce->samples_per_chunk)); ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek chunk[%ui/%uD][%uD..%ui][%ui/%uD]", t->id, cr->chunk_pos, ngx_rtmp_r32(t->chunks->entry_count), ngx_rtmp_r32(ce->first_chunk), cr->chunk, cr->chunk_count, ngx_rtmp_r32(ce->samples_per_chunk)); return ngx_rtmp_mp4_update_offset(s, t); } static ngx_int_t ngx_rtmp_mp4_next_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; cr = &t->cursor; cr->offset += cr->size; if (t->sizes) { if (t->sizes->sample_size) { cr->size = ngx_rtmp_r32(t->sizes->sample_size); ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui size fix=%uz", t->id, cr->size); return NGX_OK; } cr->size_pos++; if (cr->size_pos >= ngx_rtmp_r32(t->sizes->sample_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui size[%ui/%uD] overflow", t->id, cr->size_pos, ngx_rtmp_r32(t->sizes->sample_count)); return NGX_ERROR; } cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]); ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui size[%ui/%uD]=%uz", t->id, cr->size_pos, ngx_rtmp_r32(t->sizes->sample_count), cr->size); return NGX_OK; } if (t->sizes2) { if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui size[%ui/%uD] overflow", t->id, cr->size_pos, ngx_rtmp_r32(t->sizes2->sample_count)); return NGX_ERROR; } /*TODO*/ return NGX_OK; } return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_seek_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_uint_t pos; cr = &t->cursor; if (cr->chunk_count > cr->pos) { return NGX_ERROR; } if (t->sizes) { if (t->sizes->sample_size) { cr->size = ngx_rtmp_r32(t->sizes->sample_size); cr->offset += cr->size * cr->chunk_count; ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek size fix=%uz", t->id, cr->size); return NGX_OK; } if (cr->pos >= ngx_rtmp_r32(t->sizes->sample_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek size[%ui/%uD] overflow", t->id, cr->pos, ngx_rtmp_r32(t->sizes->sample_count)); return NGX_ERROR; } for (pos = 1; pos <= cr->chunk_count; ++pos) { cr->offset += ngx_rtmp_r32(t->sizes->entries[cr->pos - pos]); } cr->size_pos = cr->pos; cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]); ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek size[%ui/%uD]=%uz", t->id, cr->size_pos, ngx_rtmp_r32(t->sizes->sample_count), cr->size); return NGX_OK; } if (t->sizes2) { if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek size2[%ui/%uD] overflow", t->id, cr->size_pos, ngx_rtmp_r32(t->sizes->sample_count)); return NGX_ERROR; } cr->size_pos = cr->pos; /* TODO */ return NGX_OK; } return NGX_ERROR; } static ngx_int_t ngx_rtmp_mp4_next_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; uint32_t *ke; cr = &t->cursor; if (t->keys == NULL) { return NGX_OK; } if (cr->key) { cr->key_pos++; } if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui key[%ui/%uD] overflow", t->id, cr->key_pos, ngx_rtmp_r32(t->keys->entry_count)); cr->key = 0; return NGX_OK; } ke = &t->keys->entries[cr->key_pos]; cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke)); ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui key[%ui/%uD][%ui/%uD]=%s", t->id, cr->key_pos, ngx_rtmp_r32(t->keys->entry_count), cr->pos, ngx_rtmp_r32(*ke), cr->key ? "match" : "miss"); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_seek_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; uint32_t *ke; ngx_int_t dpos; cr = &t->cursor; if (t->keys == NULL) { return NGX_OK; } while (cr->key_pos < ngx_rtmp_r32(t->keys->entry_count)) { if (ngx_rtmp_r32(t->keys->entries[cr->key_pos]) > cr->pos) { break; } cr->key_pos++; } if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek key[%ui/%uD] overflow", t->id, cr->key_pos, ngx_rtmp_r32(t->keys->entry_count)); return NGX_OK; } ke = &t->keys->entries[cr->key_pos]; /*cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/ /* distance to the next keyframe */ dpos = ngx_rtmp_r32(*ke) - cr->pos - 1; cr->key = 1; /* TODO: range version needed */ for (; dpos > 0; --dpos) { ngx_rtmp_mp4_next_time(s, t); } /* cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/ ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek key[%ui/%uD][%ui/%uD]=%s", t->id, cr->key_pos, ngx_rtmp_r32(t->keys->entry_count), cr->pos, ngx_rtmp_r32(*ke), cr->key ? "match" : "miss"); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_next_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_delay_entry_t *de; cr = &t->cursor; if (t->delays == NULL) { return NGX_OK; } if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui delay[%ui/%uD] overflow", t->id, cr->delay_pos, ngx_rtmp_r32(t->delays->entry_count)); return NGX_OK; } cr->delay_count++; de = &t->delays->entries[cr->delay_pos]; if (cr->delay_count >= ngx_rtmp_r32(de->sample_count)) { cr->delay_pos++; de++; cr->delay_count = 0; } if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui delay[%ui/%uD] overflow", t->id, cr->delay_pos, ngx_rtmp_r32(t->delays->entry_count)); return NGX_OK; } cr->delay = ngx_rtmp_r32(de->sample_offset); ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui delay[%ui/%uD][%ui/%uD]=%ui", t->id, cr->delay_pos, ngx_rtmp_r32(t->delays->entry_count), cr->delay_count, ngx_rtmp_r32(de->sample_count), cr->delay); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_seek_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_delay_entry_t *de; uint32_t pos, dpos; cr = &t->cursor; if (t->delays == NULL) { return NGX_OK; } pos = 0; de = t->delays->entries; while (cr->delay_pos < ngx_rtmp_r32(t->delays->entry_count)) { dpos = ngx_rtmp_r32(de->sample_count); if (pos + dpos > cr->pos) { cr->delay_count = cr->pos - pos; cr->delay = ngx_rtmp_r32(de->sample_offset); break; } cr->delay_pos++; pos += dpos; de++; } if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek delay[%ui/%uD] overflow", t->id, cr->delay_pos, ngx_rtmp_r32(t->delays->entry_count)); return NGX_OK; } ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek delay[%ui/%uD][%ui/%uD]=%ui", t->id, cr->delay_pos, ngx_rtmp_r32(t->delays->entry_count), cr->delay_count, ngx_rtmp_r32(de->sample_count), cr->delay); return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_next(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t) { if (ngx_rtmp_mp4_next_time(s, t) != NGX_OK || ngx_rtmp_mp4_next_key(s, t) != NGX_OK || ngx_rtmp_mp4_next_chunk(s, t) != NGX_OK || ngx_rtmp_mp4_next_size(s, t) != NGX_OK || ngx_rtmp_mp4_next_delay(s, t) != NGX_OK) { t->cursor.valid = 0; return NGX_ERROR; } t->cursor.valid = 1; return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_send_meta(ngx_rtmp_session_t *s) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_core_srv_conf_t *cscf; ngx_int_t rc; ngx_uint_t n; ngx_rtmp_header_t h; ngx_chain_t *out; ngx_rtmp_mp4_track_t *t; double d; static struct { double width; double height; double duration; double video_codec_id; double audio_codec_id; double audio_sample_rate; } v; static ngx_rtmp_amf_elt_t out_inf[] = { { NGX_RTMP_AMF_NUMBER, ngx_string("width"), &v.width, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("height"), &v.height, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("displayWidth"), &v.width, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("displayHeight"), &v.height, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("duration"), &v.duration, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("videocodecid"), &v.video_codec_id, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("audiocodecid"), &v.audio_codec_id, 0 }, { NGX_RTMP_AMF_NUMBER, ngx_string("audiosamplerate"), &v.audio_sample_rate, 0 }, }; static ngx_rtmp_amf_elt_t out_elts[] = { { NGX_RTMP_AMF_STRING, ngx_null_string, "onMetaData", 0 }, { NGX_RTMP_AMF_OBJECT, ngx_null_string, out_inf, sizeof(out_inf) }, }; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_OK; } cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ngx_memzero(&v, sizeof(v)); v.width = ctx->width; v.height = ctx->height; v.audio_sample_rate = ctx->sample_rate; t = &ctx->tracks[0]; for (n = 0; n < ctx->ntracks; ++n, ++t) { d = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->duration) / 1000.; if (v.duration < d) { v.duration = d; } switch (t->type) { case NGX_RTMP_MSG_AUDIO: v.audio_codec_id = t->codec; break; case NGX_RTMP_MSG_VIDEO: v.video_codec_id = t->codec; break; } } out = NULL; rc = ngx_rtmp_append_amf(s, &out, NULL, out_elts, sizeof(out_elts) / sizeof(out_elts[0])); if (rc != NGX_OK || out == NULL) { return NGX_ERROR; } ngx_memzero(&h, sizeof(h)); h.csid = NGX_RTMP_CSID_AMF; h.msid = NGX_RTMP_MSID; h.type = NGX_RTMP_MSG_AMF_META; ngx_rtmp_prepare_message(s, &h, NULL, out); rc = ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); return rc; } static ngx_int_t ngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t, ngx_int_t timestamp) { ngx_rtmp_mp4_cursor_t *cr; cr = &t->cursor; ngx_memzero(cr, sizeof(*cr)); if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp( t, timestamp)) != NGX_OK || ngx_rtmp_mp4_seek_key(s, t) != NGX_OK || ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK || ngx_rtmp_mp4_seek_size(s, t) != NGX_OK || ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK) { return NGX_ERROR; } cr->valid = 1; return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts) { ngx_rtmp_mp4_ctx_t *ctx; ngx_buf_t in_buf; ngx_rtmp_header_t h, lh; ngx_rtmp_core_srv_conf_t *cscf; ngx_chain_t *out, in; ngx_rtmp_mp4_track_t *t, *cur_t; ngx_rtmp_mp4_cursor_t *cr, *cur_cr; uint32_t buflen, end_timestamp, timestamp, last_timestamp, rdelay, cur_timestamp; ssize_t ret; u_char fhdr[5]; size_t fhdr_size; ngx_int_t rc; ngx_uint_t n, counter; cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module); ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_ERROR; } if (!ctx->meta_sent) { rc = ngx_rtmp_mp4_send_meta(s); if (rc == NGX_OK) { ctx->meta_sent = 1; } return rc; } buflen = s->buflen + NGX_RTMP_MP4_BUFLEN_ADDON; counter = 0; last_timestamp = 0; end_timestamp = ctx->start_timestamp + (ngx_current_msec - ctx->epoch) + buflen; for ( ;; ) { counter++; if (counter > NGX_RTMP_MP4_MAX_FRAMES) { return NGX_OK; } timestamp = 0; t = NULL; for (n = 0; n < ctx->ntracks; n++) { cur_t = &ctx->tracks[n]; cur_cr = &cur_t->cursor; if (!cur_cr->valid) { continue; } cur_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(cur_t, cur_cr->timestamp); if (t == NULL || cur_timestamp < timestamp) { timestamp = cur_timestamp; t = cur_t; } } if (t == NULL) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "mp4: no track"); return NGX_DONE; } if (timestamp > end_timestamp) { ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui ahead %uD > %uD", t->id, timestamp, end_timestamp); if (ts) { *ts = last_timestamp; } return (uint32_t) (timestamp - end_timestamp); } cr = &t->cursor; last_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->last_timestamp); ngx_memzero(&h, sizeof(h)); h.msid = NGX_RTMP_MSID; h.type = (uint8_t) t->type; h.csid = t->csid; lh = h; h.timestamp = timestamp; lh.timestamp = last_timestamp; ngx_memzero(&in, sizeof(in)); ngx_memzero(&in_buf, sizeof(in_buf)); if (t->header && !t->header_sent) { ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui sending header of size=%uz", t->id, t->header_size); fhdr[0] = t->fhdr; fhdr[1] = 0; if (t->type == NGX_RTMP_MSG_VIDEO) { fhdr[0] |= 0x10; fhdr[2] = fhdr[3] = fhdr[4] = 0; fhdr_size = 5; } else { fhdr_size = 2; } in.buf = &in_buf; in_buf.pos = fhdr; in_buf.last = fhdr + fhdr_size; out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); in.buf = &in_buf; in_buf.pos = t->header; in_buf.last = t->header + t->header_size; ngx_rtmp_append_shared_bufs(cscf, out, &in); ngx_rtmp_prepare_message(s, &h, NULL, out); rc = ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); if (rc == NGX_AGAIN) { return NGX_AGAIN; } t->header_sent = 1; } ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui read frame offset=%O, size=%uz, " "timestamp=%uD, last_timestamp=%uD", t->id, cr->offset, cr->size, timestamp, last_timestamp); ngx_rtmp_mp4_buffer[0] = t->fhdr; fhdr_size = 1; if (t->type == NGX_RTMP_MSG_VIDEO) { if (cr->key) { ngx_rtmp_mp4_buffer[0] |= 0x10; } else if (cr->delay) { ngx_rtmp_mp4_buffer[0] |= 0x20; } else { ngx_rtmp_mp4_buffer[0] |= 0x30; } if (t->header) { fhdr_size = 5; rdelay = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->delay); ngx_rtmp_mp4_buffer[1] = 1; ngx_rtmp_mp4_buffer[2] = (rdelay >> 16) & 0xff; ngx_rtmp_mp4_buffer[3] = (rdelay >> 8) & 0xff; ngx_rtmp_mp4_buffer[4] = rdelay & 0xff; } } else { /* NGX_RTMP_MSG_AUDIO */ if (t->header) { fhdr_size = 2; ngx_rtmp_mp4_buffer[1] = 1; } } if (cr->size + fhdr_size > sizeof(ngx_rtmp_mp4_buffer)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "mp4: track#%ui too big frame: %D>%uz", t->id, cr->size, sizeof(ngx_rtmp_mp4_buffer)); goto next; } ret = ngx_read_file(f, ngx_rtmp_mp4_buffer + fhdr_size, cr->size, cr->offset); if (ret != (ssize_t) cr->size) { ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, "mp4: track#%ui could not read frame", t->id); goto next; } in.buf = &in_buf; in_buf.pos = ngx_rtmp_mp4_buffer; in_buf.last = ngx_rtmp_mp4_buffer + cr->size + fhdr_size; out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in); ngx_rtmp_prepare_message(s, &h, cr->not_first ? &lh : NULL, out); rc = ngx_rtmp_send_message(s, out, 0); ngx_rtmp_free_shared_chain(cscf, out); if (rc == NGX_AGAIN) { return NGX_AGAIN; } s->current_time = timestamp; next: if (ngx_rtmp_mp4_next(s, t) != NGX_OK) { return NGX_DONE; } } } static ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex) { ngx_rtmp_mp4_ctx_t *ctx; uint32_t hdr[2]; ssize_t n; size_t offset, page_offset, size, shift; uint64_t extended_size; ngx_file_info_t fi; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_mp4_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_mp4_module); } ngx_memzero(ctx, sizeof(*ctx)); ctx->aindex = aindex; ctx->vindex = vindex; offset = 0; size = 0; for ( ;; ) { n = ngx_read_file(f, (u_char *) &hdr, sizeof(hdr), offset); if (n != sizeof(hdr)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: error reading file at offset=%uz " "while searching for moov box", offset); return NGX_ERROR; } size = (size_t) ngx_rtmp_r32(hdr[0]); shift = sizeof(hdr); if (size == 1) { n = ngx_read_file(f, (u_char *) &extended_size, sizeof(extended_size), offset + sizeof(hdr)); if (n != sizeof(extended_size)) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: error reading file at offset=%uz " "while searching for moov box", offset + 8); return NGX_ERROR; } size = (size_t) ngx_rtmp_r64(extended_size); shift += sizeof(extended_size); } else if (size == 0) { if (ngx_fd_info(f->fd, &fi) == NGX_FILE_ERROR) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: " ngx_fd_info_n " failed"); return NGX_ERROR; } size = ngx_file_size(&fi) - offset; } if (hdr[1] == ngx_rtmp_mp4_make_tag('m','o','o','v')) { ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: found moov box"); break; } ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: skipping box '%*s'", 4, hdr + 1); offset += size; } if (size < shift) { return NGX_ERROR; } size -= shift; offset += shift; page_offset = offset & (ngx_pagesize - 1); ctx->mmaped_size = page_offset + size; ctx->mmaped = ngx_rtmp_mp4_mmap(f->fd, ctx->mmaped_size, offset - page_offset, &ctx->extra); if (ctx->mmaped == NULL) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: mmap failed at offset=%ui, size=%uz", offset, size); return NGX_ERROR; } return ngx_rtmp_mp4_parse(s, (u_char *) ctx->mmaped + page_offset, (u_char *) ctx->mmaped + page_offset + size); } static ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f) { ngx_rtmp_mp4_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL || ctx->mmaped == NULL) { return NGX_OK; } if (ngx_rtmp_mp4_munmap(ctx->mmaped, ctx->mmaped_size, &ctx->extra) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, "mp4: munmap failed"); return NGX_ERROR; } ctx->mmaped = NULL; ctx->mmaped_size = 0; return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_track_t *t; ngx_uint_t n; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: seek timestamp=%ui", timestamp); for (n = 0; n < ctx->ntracks; ++n) { t = &ctx->tracks[n]; if (t->type != NGX_RTMP_MSG_VIDEO) { continue; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek video", n); ngx_rtmp_mp4_seek_track(s, t, timestamp); timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->cursor.timestamp); break; } for (n = 0; n < ctx->ntracks; ++n) { t = &ctx->tracks[n]; if (t->type == NGX_RTMP_MSG_VIDEO) { continue; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: track#%ui seek", n); ngx_rtmp_mp4_seek_track(s, &ctx->tracks[n], timestamp); } ctx->start_timestamp = timestamp; ctx->epoch = ngx_current_msec; return ngx_rtmp_mp4_reset(s); } static ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f) { ngx_rtmp_mp4_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_OK; } ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: start timestamp=%uD", ctx->start_timestamp); ctx->epoch = ngx_current_msec; return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/ } static ngx_int_t ngx_rtmp_mp4_reset(ngx_rtmp_session_t *s) { ngx_rtmp_mp4_ctx_t *ctx; ngx_rtmp_mp4_cursor_t *cr; ngx_rtmp_mp4_track_t *t; ngx_uint_t n; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_OK; } t = &ctx->tracks[0]; for (n = 0; n < ctx->ntracks; ++n, ++t) { cr = &t->cursor; cr->not_first = 0; } return NGX_OK; } static ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f) { ngx_rtmp_mp4_ctx_t *ctx; ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module); if (ctx == NULL) { return NGX_OK; } ctx->start_timestamp += (ngx_current_msec - ctx->epoch); ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, "mp4: stop timestamp=%uD", ctx->start_timestamp); return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/ } static ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf) { ngx_rtmp_play_main_conf_t *pmcf; ngx_rtmp_play_fmt_t **pfmt, *fmt; pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module); pfmt = ngx_array_push(&pmcf->fmts); if (pfmt == NULL) { return NGX_ERROR; } fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t)); if (fmt == NULL) { return NGX_ERROR; } *pfmt = fmt; ngx_str_set(&fmt->name, "mp4-format"); ngx_str_set(&fmt->pfx, "mp4:"); ngx_str_set(&fmt->sfx, ".mp4"); fmt->init = ngx_rtmp_mp4_init; fmt->done = ngx_rtmp_mp4_done; fmt->seek = ngx_rtmp_mp4_seek; fmt->start = ngx_rtmp_mp4_start; fmt->stop = ngx_rtmp_mp4_stop; fmt->send = ngx_rtmp_mp4_send; return NGX_OK; }