1 Star 0 Fork 128

xrw001 / nginx-http-flv-module

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
ngx_rtmp_mp4_module.c 69.48 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595
/*
* Copyright (C) Roman Arutyunyan
*/
#include <ngx_config.h>
#include <ngx_core.h>
#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]);
if (size == 0) {
return NGX_ERROR;
}
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;
}
C
1
https://gitee.com/phpll_app/nginx-http-flv-module.git
git@gitee.com:phpll_app/nginx-http-flv-module.git
phpll_app
nginx-http-flv-module
nginx-http-flv-module
master

搜索帮助