加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
ngx_rtmp_mp4_module.c 69.42 KB
一键复制 编辑 原始数据 按行查看 历史
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591
/*
* 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]);
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;
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化