transmuxer.test.js 144 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142414341444145414641474148414941504151415241534154415541564157415841594160416141624163416441654166416741684169417041714172417341744175417641774178417941804181418241834184418541864187418841894190419141924193419441954196419741984199420042014202420342044205420642074208420942104211421242134214421542164217421842194220422142224223422442254226422742284229423042314232423342344235423642374238423942404241424242434244424542464247424842494250425142524253425442554256425742584259426042614262426342644265426642674268426942704271427242734274427542764277427842794280428142824283428442854286428742884289429042914292429342944295429642974298429943004301430243034304430543064307430843094310431143124313431443154316431743184319432043214322432343244325432643274328432943304331433243334334433543364337433843394340434143424343434443454346434743484349435043514352435343544355435643574358435943604361436243634364436543664367436843694370437143724373437443754376437743784379438043814382438343844385438643874388438943904391439243934394439543964397439843994400440144024403440444054406440744084409441044114412441344144415441644174418441944204421442244234424442544264427442844294430443144324433443444354436443744384439444044414442444344444445444644474448444944504451445244534454445544564457445844594460446144624463446444654466446744684469447044714472447344744475447644774478447944804481448244834484448544864487448844894490449144924493449444954496449744984499450045014502450345044505450645074508450945104511451245134514451545164517451845194520452145224523452445254526452745284529453045314532453345344535453645374538453945404541454245434544454545464547454845494550455145524553455445554556455745584559456045614562456345644565456645674568456945704571457245734574457545764577457845794580458145824583458445854586458745884589459045914592459345944595459645974598459946004601460246034604460546064607460846094610461146124613461446154616461746184619462046214622462346244625462646274628462946304631463246334634463546364637463846394640464146424643464446454646464746484649465046514652465346544655465646574658465946604661466246634664
  1. 'use strict';
  2. var segments = require('data-files!segments');
  3. var mp2t = require('../lib/m2ts'),
  4. codecs = require('../lib/codecs'),
  5. flv = require('../lib/flv'),
  6. id3Generator = require('./utils/id3-generator'),
  7. mp4 = require('../lib/mp4'),
  8. QUnit = require('qunit'),
  9. testSegment = segments['test-segment.ts'](),
  10. testMiddlePatPMT = segments['test-middle-pat-pmt.ts'](),
  11. mp4Transmuxer = require('../lib/mp4/transmuxer'),
  12. mp4AudioProperties = mp4Transmuxer.AUDIO_PROPERTIES,
  13. mp4VideoProperties = mp4Transmuxer.VIDEO_PROPERTIES,
  14. generateSegmentTimingInfo = mp4Transmuxer.generateSegmentTimingInfo,
  15. clock = require('../lib/utils/clock'),
  16. utils = require('./utils'),
  17. TransportPacketStream = mp2t.TransportPacketStream,
  18. transportPacketStream,
  19. TransportParseStream = mp2t.TransportParseStream,
  20. transportParseStream,
  21. ElementaryStream = mp2t.ElementaryStream,
  22. elementaryStream,
  23. TimestampRolloverStream = mp2t.TimestampRolloverStream,
  24. timestampRolloverStream,
  25. H264Stream = codecs.h264.H264Stream,
  26. h264Stream,
  27. VideoSegmentStream = mp4.VideoSegmentStream,
  28. videoSegmentStream,
  29. AudioSegmentStream = mp4.AudioSegmentStream,
  30. audioSegmentStream,
  31. AdtsStream = codecs.Adts,
  32. adtsStream,
  33. Transmuxer = mp4.Transmuxer,
  34. FlvTransmuxer = flv.Transmuxer,
  35. transmuxer,
  36. NalByteStream = codecs.h264.NalByteStream,
  37. nalByteStream,
  38. H264_STREAM_TYPE = mp2t.H264_STREAM_TYPE,
  39. ADTS_STREAM_TYPE = mp2t.ADTS_STREAM_TYPE,
  40. METADATA_STREAM_TYPE = mp2t.METADATA_STREAM_TYPE,
  41. validateTrack,
  42. validateTrackFragment,
  43. PMT = utils.PMT,
  44. PAT = utils.PAT,
  45. generatePMT = utils.generatePMT,
  46. pesHeader = utils.pesHeader,
  47. packetize = utils.packetize,
  48. videoPes = utils.videoPes,
  49. adtsFrame = utils.adtsFrame,
  50. audioPes = utils.audioPes,
  51. timedMetadataPes = utils.timedMetadataPes;
  52. mp4.tools = require('../lib/tools/mp4-inspector');
  53. QUnit.module('MP2T Packet Stream', {
  54. beforeEach: function() {
  55. transportPacketStream = new TransportPacketStream();
  56. }
  57. });
  58. QUnit.test('tester', function(assert) {
  59. assert.ok(true, 'did not throw');
  60. });
  61. QUnit.test('empty input does not error', function(assert) {
  62. transportPacketStream.push(new Uint8Array([]));
  63. assert.ok(true, 'did not throw');
  64. });
  65. QUnit.test('parses a generic packet', function(assert) {
  66. var
  67. datas = [],
  68. packet = new Uint8Array(188);
  69. packet[0] = 0x47; // Sync-byte
  70. transportPacketStream.on('data', function(event) {
  71. datas.push(event);
  72. });
  73. transportPacketStream.push(packet);
  74. transportPacketStream.flush();
  75. assert.equal(1, datas.length, 'fired one event');
  76. assert.equal(datas[0].byteLength, 188, 'delivered the packet');
  77. });
  78. QUnit.test('buffers partial packets', function(assert) {
  79. var
  80. datas = [],
  81. partialPacket1 = new Uint8Array(187),
  82. partialPacket2 = new Uint8Array(189);
  83. partialPacket1[0] = 0x47; // Sync-byte
  84. partialPacket2[1] = 0x47; // Sync-byte
  85. transportPacketStream.on('data', function(event) {
  86. datas.push(event);
  87. });
  88. transportPacketStream.push(partialPacket1);
  89. assert.equal(0, datas.length, 'did not fire an event');
  90. transportPacketStream.push(partialPacket2);
  91. transportPacketStream.flush();
  92. assert.equal(2, datas.length, 'fired events');
  93. assert.equal(188, datas[0].byteLength, 'parsed the first packet');
  94. assert.equal(188, datas[1].byteLength, 'parsed the second packet');
  95. });
  96. QUnit.test('parses multiple packets delivered at once', function(assert) {
  97. var datas = [], packetStream = new Uint8Array(188 * 3);
  98. packetStream[0] = 0x47; // Sync-byte
  99. packetStream[188] = 0x47; // Sync-byte
  100. packetStream[376] = 0x47; // Sync-byte
  101. transportPacketStream.on('data', function(event) {
  102. datas.push(event);
  103. });
  104. transportPacketStream.push(packetStream);
  105. transportPacketStream.flush();
  106. assert.equal(3, datas.length, 'fired three events');
  107. assert.equal(188, datas[0].byteLength, 'parsed the first packet');
  108. assert.equal(188, datas[1].byteLength, 'parsed the second packet');
  109. assert.equal(188, datas[2].byteLength, 'parsed the third packet');
  110. });
  111. QUnit.test('resyncs packets', function(assert) {
  112. var datas = [], packetStream = new Uint8Array(188 * 3 - 2);
  113. packetStream[0] = 0x47; // Sync-byte
  114. packetStream[186] = 0x47; // Sync-byte
  115. packetStream[374] = 0x47; // Sync-byte
  116. transportPacketStream.on('data', function(event) {
  117. datas.push(event);
  118. });
  119. transportPacketStream.push(packetStream);
  120. transportPacketStream.flush();
  121. assert.equal(datas.length, 2, 'fired three events');
  122. assert.equal(datas[0].byteLength, 188, 'parsed the first packet');
  123. assert.equal(datas[1].byteLength, 188, 'parsed the second packet');
  124. });
  125. QUnit.test('buffers extra after multiple packets', function(assert) {
  126. var datas = [], packetStream = new Uint8Array(188 * 2 + 10);
  127. packetStream[0] = 0x47; // Sync-byte
  128. packetStream[188] = 0x47; // Sync-byte
  129. packetStream[376] = 0x47; // Sync-byte
  130. transportPacketStream.on('data', function(event) {
  131. datas.push(event);
  132. });
  133. transportPacketStream.push(packetStream);
  134. assert.equal(2, datas.length, 'fired three events');
  135. assert.equal(188, datas[0].byteLength, 'parsed the first packet');
  136. assert.equal(188, datas[1].byteLength, 'parsed the second packet');
  137. transportPacketStream.push(new Uint8Array(178));
  138. transportPacketStream.flush();
  139. assert.equal(3, datas.length, 'fired a final event');
  140. assert.equal(188, datas[2].length, 'parsed the finel packet');
  141. });
  142. QUnit.module('MP2T TransportParseStream', {
  143. beforeEach: function() {
  144. transportPacketStream = new TransportPacketStream();
  145. transportParseStream = new TransportParseStream();
  146. transportPacketStream.pipe(transportParseStream);
  147. }
  148. });
  149. QUnit.test('parses generic packet properties', function(assert) {
  150. var packet;
  151. transportParseStream.on('data', function(data) {
  152. packet = data;
  153. });
  154. transportParseStream.push(packetize(PAT));
  155. transportParseStream.push(packetize(generatePMT({})));
  156. transportParseStream.push(new Uint8Array([
  157. 0x47, // sync byte
  158. // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
  159. 0x40, 0x01, 0x6c
  160. ]));
  161. assert.ok(packet.payloadUnitStartIndicator, 'parsed payload_unit_start_indicator');
  162. assert.ok(packet.pid, 'parsed PID');
  163. });
  164. QUnit.test('parses piped data events', function(assert) {
  165. var packet;
  166. transportParseStream.on('data', function(data) {
  167. packet = data;
  168. });
  169. transportParseStream.push(packetize(PAT));
  170. transportParseStream.push(packetize(generatePMT({})));
  171. transportParseStream.push(new Uint8Array([
  172. 0x47, // sync byte
  173. // tei:0 pusi:1 tp:0 pid:0 0000 0000 0001 tsc:01 afc:10 cc:11 padding: 00
  174. 0x40, 0x01, 0x6c
  175. ]));
  176. assert.ok(packet, 'parsed a packet');
  177. });
  178. QUnit.test('parses a data packet with adaptation fields', function(assert) {
  179. var packet;
  180. transportParseStream.on('data', function(data) {
  181. packet = data;
  182. });
  183. transportParseStream.push(new Uint8Array([
  184. 0x47, // sync byte
  185. // tei:0 pusi:1 tp:0 pid:0 0000 0000 0000 tsc:01 afc:10 cc:11 afl:00 0000 00 stuffing:00 0000 00 pscp:00 0001 padding:0000
  186. 0x40, 0x00, 0x6c, 0x00, 0x00, 0x10
  187. ]));
  188. assert.strictEqual(packet.type, 'pat', 'parsed the packet type');
  189. });
  190. QUnit.test('parses a PES packet', function(assert) {
  191. var packet;
  192. transportParseStream.on('data', function(data) {
  193. packet = data;
  194. });
  195. // setup a program map table
  196. transportParseStream.programMapTable = {
  197. video: 0x0010,
  198. 'timed-metadata': {}
  199. };
  200. transportParseStream.push(new Uint8Array([
  201. 0x47, // sync byte
  202. // tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:01 cc:11 padding:00
  203. 0x40, 0x02, 0x5c
  204. ]));
  205. assert.strictEqual(packet.type, 'pes', 'parsed a PES packet');
  206. });
  207. QUnit.test('parses packets with variable length adaptation fields and a payload', function(assert) {
  208. var packet;
  209. transportParseStream.on('data', function(data) {
  210. packet = data;
  211. });
  212. // setup a program map table
  213. transportParseStream.programMapTable = {
  214. video: 0x0010,
  215. 'timed-metadata': {}
  216. };
  217. transportParseStream.push(new Uint8Array([
  218. 0x47, // sync byte
  219. // tei:0 pusi:1 tp:0 pid:0 0000 0000 0010 tsc:01 afc:11 cc:11 afl:00 0000 11 stuffing:00 0000 0000 00 pscp:00 0001
  220. 0x40, 0x02, 0x7c, 0x0c, 0x00, 0x01
  221. ]));
  222. assert.strictEqual(packet.type, 'pes', 'parsed a PES packet');
  223. });
  224. QUnit.test('parses the program map table pid from the program association table (PAT)', function(assert) {
  225. var packet;
  226. transportParseStream.on('data', function(data) {
  227. packet = data;
  228. });
  229. transportParseStream.push(new Uint8Array(PAT));
  230. assert.ok(packet, 'parsed a packet');
  231. assert.strictEqual(0x0010, transportParseStream.pmtPid, 'parsed PMT pid');
  232. });
  233. QUnit.test('does not parse PES packets until after the PES has been parsed', function(assert) {
  234. var pesCount = 0;
  235. transportParseStream.on('data', function(data) {
  236. if (data.type === 'pmt') {
  237. assert.equal(pesCount, 0, 'have not yet parsed any PES packets');
  238. } else if (data.type === 'pes') {
  239. pesCount++;
  240. }
  241. });
  242. transportPacketStream.push(testMiddlePatPMT);
  243. });
  244. QUnit.test('parse the elementary streams from a program map table', function(assert) {
  245. var packet;
  246. transportParseStream.on('data', function(data) {
  247. packet = data;
  248. });
  249. transportParseStream.pmtPid = 0x0010;
  250. transportParseStream.push(new Uint8Array(PMT.concat(0, 0, 0, 0, 0)));
  251. assert.ok(packet, 'parsed a packet');
  252. assert.ok(transportParseStream.programMapTable, 'parsed a program map');
  253. assert.strictEqual(transportParseStream.programMapTable.video, 0x11, 'associated h264 with pid 0x11');
  254. assert.strictEqual(transportParseStream.programMapTable.audio, 0x12, 'associated adts with pid 0x12');
  255. assert.deepEqual(transportParseStream.programMapTable, packet.programMapTable, 'recorded the PMT');
  256. });
  257. QUnit.module('MP2T ElementaryStream', {
  258. beforeEach: function() {
  259. elementaryStream = new ElementaryStream();
  260. }
  261. });
  262. QUnit.test('parses metadata events from PSI packets', function(assert) {
  263. var
  264. metadatas = [],
  265. datas = 0,
  266. sortById = function(left, right) {
  267. return left.id - right.id;
  268. };
  269. elementaryStream.on('data', function(data) {
  270. if (data.type === 'metadata') {
  271. metadatas.push(data);
  272. }
  273. datas++;
  274. });
  275. elementaryStream.push({
  276. type: 'pat'
  277. });
  278. elementaryStream.push({
  279. type: 'pmt',
  280. programMapTable: {
  281. video: 1,
  282. audio: 2,
  283. 'timed-metadata': {}
  284. }
  285. });
  286. assert.equal(1, datas, 'data fired');
  287. assert.equal(1, metadatas.length, 'metadata generated');
  288. metadatas[0].tracks.sort(sortById);
  289. assert.deepEqual(metadatas[0].tracks, [{
  290. id: 1,
  291. codec: 'avc',
  292. type: 'video',
  293. timelineStartInfo: {
  294. baseMediaDecodeTime: 0
  295. }
  296. }, {
  297. id: 2,
  298. codec: 'adts',
  299. type: 'audio',
  300. timelineStartInfo: {
  301. baseMediaDecodeTime: 0
  302. }
  303. }], 'identified two tracks');
  304. });
  305. QUnit.test('parses standalone program stream packets', function(assert) {
  306. var
  307. packets = [],
  308. packetData = [0x01, 0x02],
  309. pesHead = pesHeader(false, 7, 2);
  310. elementaryStream.on('data', function(packet) {
  311. packets.push(packet);
  312. });
  313. elementaryStream.push({
  314. type: 'pes',
  315. streamType: ADTS_STREAM_TYPE,
  316. payloadUnitStartIndicator: true,
  317. data: new Uint8Array(pesHead.concat(packetData))
  318. });
  319. elementaryStream.flush();
  320. assert.equal(packets.length, 1, 'built one packet');
  321. assert.equal(packets[0].type, 'audio', 'identified audio data');
  322. assert.equal(packets[0].data.byteLength, packetData.length, 'parsed the correct payload size');
  323. assert.equal(packets[0].pts, 7, 'correctly parsed the pts value');
  324. });
  325. QUnit.test('aggregates program stream packets from the transport stream', function(assert) {
  326. var
  327. events = [],
  328. packetData = [0x01, 0x02],
  329. pesHead = pesHeader(false, 7);
  330. elementaryStream.on('data', function(event) {
  331. events.push(event);
  332. });
  333. elementaryStream.push({
  334. type: 'pes',
  335. streamType: H264_STREAM_TYPE,
  336. payloadUnitStartIndicator: true,
  337. data: new Uint8Array(pesHead.slice(0, 4)) // Spread PES Header across packets
  338. });
  339. assert.equal(events.length, 0, 'buffers partial packets');
  340. elementaryStream.push({
  341. type: 'pes',
  342. streamType: H264_STREAM_TYPE,
  343. data: new Uint8Array(pesHead.slice(4).concat(packetData))
  344. });
  345. elementaryStream.flush();
  346. assert.equal(events.length, 1, 'built one packet');
  347. assert.equal(events[0].type, 'video', 'identified video data');
  348. assert.equal(events[0].pts, 7, 'correctly parsed the pts');
  349. assert.equal(events[0].data.byteLength, packetData.length, 'concatenated transport packets');
  350. });
  351. QUnit.test('aggregates program stream packets from the transport stream with no header data', function(assert) {
  352. var events = []
  353. elementaryStream.on('data', function(event) {
  354. events.push(event);
  355. });
  356. elementaryStream.push({
  357. type: 'pes',
  358. streamType: H264_STREAM_TYPE,
  359. data: new Uint8Array([0x1, 0x2, 0x3])
  360. });
  361. assert.equal(events.length, 0, 'buffers partial packets');
  362. elementaryStream.push({
  363. type: 'pes',
  364. streamType: H264_STREAM_TYPE,
  365. payloadUnitStartIndicator: true,
  366. data: new Uint8Array([0x4, 0x5, 0x6])
  367. });
  368. elementaryStream.push({
  369. type: 'pes',
  370. streamType: H264_STREAM_TYPE,
  371. data: new Uint8Array([0x7, 0x8, 0x9])
  372. });
  373. elementaryStream.flush();
  374. assert.equal(events.length, 1, 'built one packet');
  375. assert.equal(events[0].type, 'video', 'identified video data');
  376. assert.equal(events[0].data.byteLength, 0, 'empty packet');
  377. });
  378. QUnit.test('parses an elementary stream packet with just a pts', function(assert) {
  379. var packet;
  380. elementaryStream.on('data', function(data) {
  381. packet = data;
  382. });
  383. elementaryStream.push({
  384. type: 'pes',
  385. streamType: H264_STREAM_TYPE,
  386. payloadUnitStartIndicator: true,
  387. data: new Uint8Array([
  388. // pscp:0000 0000 0000 0000 0000 0001
  389. 0x00, 0x00, 0x01,
  390. // sid:0000 0000 ppl:0000 0000 0000 1001
  391. 0x00, 0x00, 0x09,
  392. // 10 psc:00 pp:0 dai:1 c:0 ooc:0
  393. 0x84,
  394. // pdf:10 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
  395. 0xc0,
  396. // phdl:0000 0101 '0010' pts:111 mb:1 pts:1111 1111
  397. 0x05, 0xFF, 0xFF,
  398. // pts:1111 111 mb:1 pts:1111 1111 pts:1111 111 mb:1
  399. 0xFF, 0xFF, 0xFF,
  400. // "data":0101
  401. 0x11
  402. ])
  403. });
  404. elementaryStream.flush();
  405. assert.ok(packet, 'parsed a packet');
  406. assert.equal(packet.data.byteLength, 1, 'parsed a single data byte');
  407. assert.equal(packet.data[0], 0x11, 'parsed the data');
  408. // 2^33-1 is the maximum value of a 33-bit unsigned value
  409. assert.equal(packet.pts, Math.pow(2, 33) - 1, 'parsed the pts');
  410. });
  411. QUnit.test('parses an elementary stream packet with a pts and dts', function(assert) {
  412. var packet;
  413. elementaryStream.on('data', function(data) {
  414. packet = data;
  415. });
  416. elementaryStream.push({
  417. type: 'pes',
  418. streamType: H264_STREAM_TYPE,
  419. payloadUnitStartIndicator: true,
  420. data: new Uint8Array([
  421. // pscp:0000 0000 0000 0000 0000 0001
  422. 0x00, 0x00, 0x01,
  423. // sid:0000 0000 ppl:0000 0000 0000 1110
  424. 0x00, 0x00, 0x0e,
  425. // 10 psc:00 pp:0 dai:1 c:0 ooc:0
  426. 0x84,
  427. // pdf:11 ef:1 erf:0 dtmf:0 acif:0 pcf:0 pef:0
  428. 0xe0,
  429. // phdl:0000 1010 '0011' pts:000 mb:1 pts:0000 0000
  430. 0x0a, 0x21, 0x00,
  431. // pts:0000 000 mb:1 pts:0000 0000 pts:0000 100 mb:1
  432. 0x01, 0x00, 0x09,
  433. // '0001' dts:000 mb:1 dts:0000 0000 dts:0000 000 mb:1
  434. 0x11, 0x00, 0x01,
  435. // dts:0000 0000 dts:0000 010 mb:1
  436. 0x00, 0x05,
  437. // "data":0101
  438. 0x11
  439. ])
  440. });
  441. elementaryStream.flush();
  442. assert.ok(packet, 'parsed a packet');
  443. assert.equal(packet.data.byteLength, 1, 'parsed a single data byte');
  444. assert.equal(packet.data[0], 0x11, 'parsed the data');
  445. assert.equal(packet.pts, 4, 'parsed the pts');
  446. assert.equal(packet.dts, 2, 'parsed the dts');
  447. });
  448. QUnit.test('parses an elementary stream packet without a pts or dts', function(assert) {
  449. var packet;
  450. elementaryStream.on('data', function(data) {
  451. packet = data;
  452. });
  453. elementaryStream.push({
  454. type: 'pes',
  455. streamType: H264_STREAM_TYPE,
  456. payloadUnitStartIndicator: true,
  457. data: new Uint8Array(pesHeader().concat([0xaf, 0x01]))
  458. });
  459. elementaryStream.flush();
  460. assert.ok(packet, 'parsed a packet');
  461. assert.equal(packet.data.byteLength, 2, 'parsed two data bytes');
  462. assert.equal(packet.data[0], 0xaf, 'parsed the first data byte');
  463. assert.equal(packet.data[1], 0x01, 'parsed the second data byte');
  464. assert.ok(!packet.pts, 'did not parse a pts');
  465. assert.ok(!packet.dts, 'did not parse a dts');
  466. });
  467. QUnit.test('won\'t emit non-video packets if the PES_packet_length is larger than the contents', function(assert) {
  468. var events = [];
  469. var pesHead = pesHeader(false, 1, 5);
  470. elementaryStream.on('data', function(event) {
  471. events.push(event);
  472. });
  473. elementaryStream.push({
  474. type: 'pes',
  475. payloadUnitStartIndicator: true,
  476. streamType: H264_STREAM_TYPE,
  477. data: new Uint8Array(pesHead.concat([1]))
  478. });
  479. elementaryStream.push({
  480. type: 'pes',
  481. payloadUnitStartIndicator: true,
  482. streamType: ADTS_STREAM_TYPE,
  483. data: new Uint8Array(pesHead.concat([1]))
  484. });
  485. elementaryStream.push({
  486. type: 'pes',
  487. payloadUnitStartIndicator: true,
  488. streamType: METADATA_STREAM_TYPE,
  489. // data larger than 5 byte dataLength, should still emit event
  490. data: new Uint8Array(pesHead.concat([1, 1, 1, 1, 1, 1, 1, 1, 1]))
  491. });
  492. assert.equal(0, events.length, 'buffers partial packets');
  493. elementaryStream.flush();
  494. assert.equal(events.length, 2, 'emitted 2 packets');
  495. assert.equal(events[0].type, 'video', 'identified video data');
  496. assert.equal(events[1].type, 'timed-metadata', 'identified timed-metadata');
  497. });
  498. QUnit.test('buffers audio and video program streams individually', function(assert) {
  499. var events = [];
  500. var pesHead = pesHeader(false, 1, 2);
  501. elementaryStream.on('data', function(event) {
  502. events.push(event);
  503. });
  504. elementaryStream.push({
  505. type: 'pes',
  506. payloadUnitStartIndicator: true,
  507. streamType: H264_STREAM_TYPE,
  508. data: new Uint8Array(pesHead.concat([1]))
  509. });
  510. elementaryStream.push({
  511. type: 'pes',
  512. payloadUnitStartIndicator: true,
  513. streamType: ADTS_STREAM_TYPE,
  514. data: new Uint8Array(pesHead.concat([1]))
  515. });
  516. assert.equal(0, events.length, 'buffers partial packets');
  517. elementaryStream.push({
  518. type: 'pes',
  519. streamType: H264_STREAM_TYPE,
  520. data: new Uint8Array(1)
  521. });
  522. elementaryStream.push({
  523. type: 'pes',
  524. streamType: ADTS_STREAM_TYPE,
  525. data: new Uint8Array(1)
  526. });
  527. elementaryStream.flush();
  528. assert.equal(2, events.length, 'parsed a complete packet');
  529. assert.equal('video', events[0].type, 'identified video data');
  530. assert.equal('audio', events[1].type, 'identified audio data');
  531. });
  532. QUnit.test('flushes the buffered packets when a new one of that type is started', function(assert) {
  533. var packets = [];
  534. var pesHead = pesHeader(false, 1, 2);
  535. elementaryStream.on('data', function(packet) {
  536. packets.push(packet);
  537. });
  538. elementaryStream.push({
  539. type: 'pes',
  540. payloadUnitStartIndicator: true,
  541. streamType: H264_STREAM_TYPE,
  542. data: new Uint8Array(pesHead.concat([1]))
  543. });
  544. elementaryStream.push({
  545. type: 'pes',
  546. payloadUnitStartIndicator: true,
  547. streamType: ADTS_STREAM_TYPE,
  548. data: new Uint8Array(pesHead.concat([1, 2]))
  549. });
  550. elementaryStream.push({
  551. type: 'pes',
  552. streamType: H264_STREAM_TYPE,
  553. data: new Uint8Array(1)
  554. });
  555. assert.equal(packets.length, 0, 'buffers packets by type');
  556. elementaryStream.push({
  557. type: 'pes',
  558. payloadUnitStartIndicator: true,
  559. streamType: H264_STREAM_TYPE,
  560. data: new Uint8Array(pesHead.concat([1]))
  561. });
  562. assert.equal(packets.length, 1, 'built one packet');
  563. assert.equal(packets[0].type, 'video', 'identified video data');
  564. assert.equal(packets[0].data.byteLength, 2, 'concatenated packets');
  565. elementaryStream.flush();
  566. assert.equal(packets.length, 3, 'built two more packets');
  567. assert.equal(packets[1].type, 'video', 'identified video data');
  568. assert.equal(packets[1].data.byteLength, 1, 'parsed the video payload');
  569. assert.equal(packets[2].type, 'audio', 'identified audio data');
  570. assert.equal(packets[2].data.byteLength, 2, 'parsed the audio payload');
  571. });
  572. QUnit.test('buffers and emits timed-metadata', function(assert) {
  573. var packets = [];
  574. var pesHead = pesHeader(false, 1, 4);
  575. elementaryStream.on('data', function(packet) {
  576. packets.push(packet);
  577. });
  578. elementaryStream.push({
  579. type: 'pes',
  580. payloadUnitStartIndicator: true,
  581. streamType: METADATA_STREAM_TYPE,
  582. data: new Uint8Array(pesHead.concat([0, 1]))
  583. });
  584. elementaryStream.push({
  585. type: 'pes',
  586. streamType: METADATA_STREAM_TYPE,
  587. data: new Uint8Array([2, 3])
  588. });
  589. assert.equal(packets.length, 0, 'buffers metadata until the next start indicator');
  590. elementaryStream.push({
  591. type: 'pes',
  592. payloadUnitStartIndicator: true,
  593. streamType: METADATA_STREAM_TYPE,
  594. data: new Uint8Array(pesHead.concat([4, 5]))
  595. });
  596. elementaryStream.push({
  597. type: 'pes',
  598. streamType: METADATA_STREAM_TYPE,
  599. data: new Uint8Array([6, 7])
  600. });
  601. assert.equal(packets.length, 1, 'built a packet');
  602. assert.equal(packets[0].type, 'timed-metadata', 'identified timed-metadata');
  603. assert.deepEqual(packets[0].data, new Uint8Array([0, 1, 2, 3]), 'concatenated the data');
  604. elementaryStream.flush();
  605. assert.equal(packets.length, 2, 'flushed a packet');
  606. assert.equal(packets[1].type, 'timed-metadata', 'identified timed-metadata');
  607. assert.deepEqual(packets[1].data, new Uint8Array([4, 5, 6, 7]), 'included the data');
  608. });
  609. QUnit.test('drops packets with unknown stream types', function(assert) {
  610. var packets = [];
  611. elementaryStream.on('data', function(packet) {
  612. packets.push(packet);
  613. });
  614. elementaryStream.push({
  615. type: 'pes',
  616. payloadUnitStartIndicator: true,
  617. data: new Uint8Array(1)
  618. });
  619. elementaryStream.push({
  620. type: 'pes',
  621. payloadUnitStartIndicator: true,
  622. data: new Uint8Array(1)
  623. });
  624. assert.equal(packets.length, 0, 'ignored unknown packets');
  625. });
  626. QUnit.module('MP2T TimestampRolloverStream', {
  627. beforeEach: function() {
  628. timestampRolloverStream = new TimestampRolloverStream('audio');
  629. elementaryStream = new ElementaryStream();
  630. elementaryStream.pipe(timestampRolloverStream);
  631. }
  632. });
  633. QUnit.test('Correctly parses rollover PTS', function(assert) {
  634. var
  635. maxTS = 8589934592,
  636. packets = [],
  637. packetData = [0x01, 0x02],
  638. pesHeadOne = pesHeader(false, maxTS - 400, 2),
  639. pesHeadTwo = pesHeader(false, maxTS - 100, 2),
  640. pesHeadThree = pesHeader(false, maxTS, 2),
  641. pesHeadFour = pesHeader(false, 50, 2);
  642. timestampRolloverStream.on('data', function(packet) {
  643. packets.push(packet);
  644. });
  645. elementaryStream.push({
  646. type: 'pes',
  647. streamType: ADTS_STREAM_TYPE,
  648. payloadUnitStartIndicator: true,
  649. data: new Uint8Array(pesHeadOne.concat(packetData))
  650. });
  651. elementaryStream.push({
  652. type: 'pes',
  653. streamType: ADTS_STREAM_TYPE,
  654. payloadUnitStartIndicator: true,
  655. data: new Uint8Array(pesHeadTwo.concat(packetData))
  656. });
  657. elementaryStream.push({
  658. type: 'pes',
  659. streamType: ADTS_STREAM_TYPE,
  660. payloadUnitStartIndicator: true,
  661. data: new Uint8Array(pesHeadThree.concat(packetData))
  662. });
  663. elementaryStream.push({
  664. type: 'pes',
  665. streamType: ADTS_STREAM_TYPE,
  666. payloadUnitStartIndicator: true,
  667. data: new Uint8Array(pesHeadFour.concat(packetData))
  668. });
  669. elementaryStream.flush();
  670. assert.equal(packets.length, 4, 'built four packets');
  671. assert.equal(packets[0].type, 'audio', 'identified audio data');
  672. assert.equal(packets[0].data.byteLength, packetData.length, 'parsed the correct payload size');
  673. assert.equal(packets[0].pts, maxTS - 400, 'correctly parsed the pts value');
  674. assert.equal(packets[1].pts, maxTS - 100, 'Does not rollover on minor change');
  675. assert.equal(packets[2].pts, maxTS, 'correctly parses the max pts value');
  676. assert.equal(packets[3].pts, maxTS + 50, 'correctly parsed the rollover pts value');
  677. });
  678. QUnit.test('Correctly parses multiple PTS rollovers', function(assert) {
  679. var
  680. maxTS = 8589934592,
  681. packets = [],
  682. packetData = [0x01, 0x02],
  683. pesArray = [pesHeader(false, 1, 2),
  684. pesHeader(false, Math.floor(maxTS * (1 / 3)), 2),
  685. pesHeader(false, Math.floor(maxTS * (2 / 3)), 2),
  686. pesHeader(false, 1, 2),
  687. pesHeader(false, Math.floor(maxTS * (1 / 3)), 2),
  688. pesHeader(false, Math.floor(maxTS * (2 / 3)), 2),
  689. pesHeader(false, 1, 2),
  690. pesHeader(false, Math.floor(maxTS * (1 / 3)), 2),
  691. pesHeader(false, Math.floor(maxTS * (2 / 3)), 2),
  692. pesHeader(false, 1, 2)];
  693. timestampRolloverStream.on('data', function(packet) {
  694. packets.push(packet);
  695. });
  696. while (pesArray.length > 0) {
  697. elementaryStream.push({
  698. type: 'pes',
  699. streamType: ADTS_STREAM_TYPE,
  700. payloadUnitStartIndicator: true,
  701. data: new Uint8Array(pesArray.shift().concat(packetData))
  702. });
  703. elementaryStream.flush();
  704. }
  705. assert.equal(packets.length, 10, 'built ten packets');
  706. assert.equal(packets[0].pts, 1, 'correctly parsed the pts value');
  707. assert.equal(packets[1].pts, Math.floor(maxTS * (1 / 3)), 'correctly parsed the pts value');
  708. assert.equal(packets[2].pts, Math.floor(maxTS * (2 / 3)), 'correctly parsed the pts value');
  709. assert.equal(packets[3].pts, maxTS + 1, 'correctly parsed the pts value');
  710. assert.equal(packets[4].pts, maxTS + Math.floor(maxTS * (1 / 3)), 'correctly parsed the pts value');
  711. assert.equal(packets[5].pts, maxTS + Math.floor(maxTS * (2 / 3)), 'correctly parsed the pts value');
  712. assert.equal(packets[6].pts, (2 * maxTS) + 1, 'correctly parsed the pts value');
  713. assert.equal(packets[7].pts, (2 * maxTS) + Math.floor(maxTS * (1 / 3)), 'correctly parsed the pts value');
  714. assert.equal(packets[8].pts, (2 * maxTS) + Math.floor(maxTS * (2 / 3)), 'correctly parsed the pts value');
  715. assert.equal(packets[9].pts, (3 * maxTS) + 1, 'correctly parsed the pts value');
  716. });
  717. QUnit.module('H264 Stream', {
  718. beforeEach: function() {
  719. h264Stream = new H264Stream();
  720. }
  721. });
  722. QUnit.test('properly parses seq_parameter_set_rbsp nal units', function(assert) {
  723. var
  724. data,
  725. expectedRBSP = new Uint8Array([
  726. 0x42, 0xc0, 0x1e, 0xd9,
  727. 0x00, 0xb4, 0x35, 0xf9,
  728. 0xe1, 0x00, 0x00, 0x00,
  729. 0x01, 0x00, 0x00, 0x00,
  730. 0x3c, 0x0f, 0x16, 0x2e,
  731. 0x48
  732. ]),
  733. expectedConfig = {
  734. profileIdc: 66,
  735. levelIdc: 30,
  736. profileCompatibility: 192,
  737. width: 720,
  738. height: 404,
  739. sarRatio: [1, 1]
  740. };
  741. h264Stream.on('data', function(event) {
  742. data = event;
  743. });
  744. // QUnit.test SPS:
  745. h264Stream.push({
  746. type: 'video',
  747. data: new Uint8Array([
  748. 0x00, 0x00, 0x00, 0x01,
  749. 0x67, 0x42, 0xc0, 0x1e,
  750. 0xd9, 0x00, 0xb4, 0x35,
  751. 0xf9, 0xe1, 0x00, 0x00,
  752. 0x03, 0x00, 0x01, 0x00,
  753. 0x00, 0x03, 0x00, 0x3c,
  754. 0x0f, 0x16, 0x2e, 0x48,
  755. 0x00, 0x00, 0x01
  756. ])
  757. });
  758. assert.ok(data, 'generated a data event');
  759. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  760. assert.deepEqual(data.escapedRBSP, expectedRBSP, 'properly removed Emulation Prevention Bytes from the RBSP');
  761. assert.deepEqual(data.config, expectedConfig, 'parsed the sps');
  762. });
  763. QUnit.test('Properly parses seq_parameter_set VUI nal unit', function(assert) {
  764. var
  765. data,
  766. expectedConfig = {
  767. profileIdc: 66,
  768. levelIdc: 30,
  769. profileCompatibility: 192,
  770. width: 16,
  771. height: 16,
  772. sarRatio: [65528, 16384]
  773. };
  774. h264Stream.on('data', function(event) {
  775. data = event;
  776. });
  777. h264Stream.push({
  778. type: 'video',
  779. data: new Uint8Array([
  780. 0x00, 0x00, 0x00, 0x01,
  781. 0x67, 0x42, 0xc0, 0x1e,
  782. 0xd9, 0xff, 0xff, 0xff,
  783. 0xff, 0xe1, 0x00, 0x00,
  784. 0x03, 0x00, 0x01, 0x00,
  785. 0x00, 0x03, 0x00, 0x3c,
  786. 0x0f, 0x16, 0x2e, 0x48,
  787. 0xff, 0x00, 0x00, 0x01
  788. ])
  789. });
  790. assert.ok(data, 'generated a data event');
  791. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  792. assert.deepEqual(data.config, expectedConfig, 'parsed the sps');
  793. });
  794. QUnit.test('Properly parses seq_parameter_set nal unit with defined sarRatio', function(assert) {
  795. var
  796. data,
  797. expectedConfig = {
  798. profileIdc: 77,
  799. levelIdc: 21,
  800. profileCompatibility: 64,
  801. width: 352,
  802. height: 480,
  803. sarRatio: [20, 11]
  804. };
  805. h264Stream.on('data', function(event) {
  806. data = event;
  807. });
  808. h264Stream.push({
  809. type: 'video',
  810. data: new Uint8Array([
  811. 0x00, 0x00, 0x00, 0x01,
  812. 0x67, 0x4d, 0x40, 0x15,
  813. 0xec, 0xa0, 0xb0, 0x7b,
  814. 0x60, 0xe2, 0x00, 0x00,
  815. 0x07, 0xd2, 0x00, 0x01,
  816. 0xd4, 0xc0, 0x1e, 0x2c,
  817. 0x5b, 0x2c, 0x00, 0x00,
  818. 0x01
  819. ])
  820. });
  821. assert.ok(data, 'generated a data event');
  822. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  823. assert.deepEqual(data.config, expectedConfig, 'parsed the sps');
  824. });
  825. QUnit.test('Properly parses seq_parameter_set nal unit with extended sarRatio', function(assert) {
  826. var
  827. data,
  828. expectedConfig = {
  829. profileIdc: 77,
  830. levelIdc: 21,
  831. profileCompatibility: 64,
  832. width: 352,
  833. height: 480,
  834. sarRatio: [8, 7]
  835. };
  836. h264Stream.on('data', function(event) {
  837. data = event;
  838. });
  839. h264Stream.push({
  840. type: 'video',
  841. data: new Uint8Array([
  842. 0x00, 0x00, 0x00, 0x01,
  843. 0x67, 0x4d, 0x40, 0x15,
  844. 0xec, 0xa0, 0xb0, 0x7b,
  845. 0x7F, 0xe0, 0x01, 0x00,
  846. 0x00, 0xf0, 0x00, 0x00,
  847. 0x00, 0x01
  848. ])
  849. });
  850. assert.ok(data, 'generated a data event');
  851. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  852. assert.deepEqual(data.config, expectedConfig, 'parsed the sps');
  853. });
  854. QUnit.test('Properly parses seq_parameter_set nal unit without VUI', function(assert) {
  855. var
  856. data,
  857. expectedConfig = {
  858. profileIdc: 77,
  859. levelIdc: 21,
  860. profileCompatibility: 64,
  861. width: 352,
  862. height: 480,
  863. sarRatio: [1, 1]
  864. };
  865. h264Stream.on('data', function(event) {
  866. data = event;
  867. });
  868. h264Stream.push({
  869. type: 'video',
  870. data: new Uint8Array([
  871. 0x00, 0x00, 0x00, 0x01,
  872. 0x67, 0x4d, 0x40, 0x15,
  873. 0xec, 0xa0, 0xb0, 0x7b,
  874. 0x02, 0x00, 0x00, 0x01
  875. ])
  876. });
  877. assert.ok(data, 'generated a data event');
  878. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  879. assert.deepEqual(data.config, expectedConfig, 'parsed the sps');
  880. });
  881. QUnit.test('unpacks nal units from simple byte stream framing', function(assert) {
  882. var data;
  883. h264Stream.on('data', function(event) {
  884. data = event;
  885. });
  886. // the simplest byte stream framing:
  887. h264Stream.push({
  888. type: 'video',
  889. data: new Uint8Array([
  890. 0x00, 0x00, 0x00, 0x01,
  891. 0x09, 0x07,
  892. 0x00, 0x00, 0x01
  893. ])
  894. });
  895. assert.ok(data, 'generated a data event');
  896. assert.equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
  897. assert.equal(data.data.length, 2, 'calculated nal unit length');
  898. assert.equal(data.data[1], 7, 'read a payload byte');
  899. });
  900. QUnit.test('unpacks nal units from byte streams split across pushes', function(assert) {
  901. var data;
  902. h264Stream.on('data', function(event) {
  903. data = event;
  904. });
  905. // handles byte streams split across pushes
  906. h264Stream.push({
  907. type: 'video',
  908. data: new Uint8Array([
  909. 0x00, 0x00, 0x00, 0x01,
  910. 0x09, 0x07, 0x06, 0x05,
  911. 0x04
  912. ])
  913. });
  914. assert.ok(!data, 'buffers NAL units across events');
  915. h264Stream.push({
  916. type: 'video',
  917. data: new Uint8Array([
  918. 0x03, 0x02, 0x01,
  919. 0x00, 0x00, 0x01
  920. ])
  921. });
  922. assert.ok(data, 'generated a data event');
  923. assert.equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
  924. assert.equal(data.data.length, 8, 'calculated nal unit length');
  925. assert.equal(data.data[1], 7, 'read a payload byte');
  926. });
  927. QUnit.test('buffers nal unit trailing zeros across pushes', function(assert) {
  928. var data = [];
  929. h264Stream.on('data', function(event) {
  930. data.push(event);
  931. });
  932. // lots of zeros after the nal, stretching into the next push
  933. h264Stream.push({
  934. type: 'video',
  935. data: new Uint8Array([
  936. 0x00, 0x00, 0x00, 0x01,
  937. 0x09, 0x07, 0x00, 0x00,
  938. 0x00, 0x00, 0x00, 0x00,
  939. 0x00, 0x00, 0x00, 0x00,
  940. 0x00
  941. ])
  942. });
  943. assert.equal(data.length, 1, 'delivered the first nal');
  944. h264Stream.push({
  945. type: 'video',
  946. data: new Uint8Array([
  947. 0x00, 0x00,
  948. 0x00, 0x00, 0x01,
  949. 0x09, 0x06,
  950. 0x00, 0x00, 0x01
  951. ])
  952. });
  953. assert.equal(data.length, 2, 'generated data events');
  954. assert.equal(data[0].data.length, 2, 'ignored trailing zeros');
  955. assert.equal(data[0].data[0], 0x09, 'found the first nal start');
  956. assert.equal(data[1].data.length, 2, 'found the following nal start');
  957. assert.equal(data[1].data[0], 0x09, 'found the second nal start');
  958. });
  959. QUnit.test('unpacks nal units from byte streams with split sync points', function(assert) {
  960. var data;
  961. h264Stream.on('data', function(event) {
  962. data = event;
  963. });
  964. // handles sync points split across pushes
  965. h264Stream.push({
  966. type: 'video',
  967. data: new Uint8Array([
  968. 0x00, 0x00, 0x00, 0x01,
  969. 0x09, 0x07,
  970. 0x00])
  971. });
  972. assert.ok(!data, 'buffers NAL units across events');
  973. h264Stream.push({
  974. type: 'video',
  975. data: new Uint8Array([
  976. 0x00, 0x01
  977. ])
  978. });
  979. assert.ok(data, 'generated a data event');
  980. assert.equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
  981. assert.equal(data.data.length, 2, 'calculated nal unit length');
  982. assert.equal(data.data[1], 7, 'read a payload byte');
  983. });
  984. QUnit.test('parses nal unit types', function(assert) {
  985. var data;
  986. h264Stream.on('data', function(event) {
  987. data = event;
  988. });
  989. h264Stream.push({
  990. type: 'video',
  991. data: new Uint8Array([
  992. 0x00, 0x00, 0x00, 0x01,
  993. 0x09
  994. ])
  995. });
  996. h264Stream.flush();
  997. assert.ok(data, 'generated a data event');
  998. assert.equal(data.nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
  999. data = null;
  1000. h264Stream.push({
  1001. type: 'video',
  1002. data: new Uint8Array([
  1003. 0x00, 0x00, 0x00, 0x01,
  1004. 0x07,
  1005. 0x27, 0x42, 0xe0, 0x0b,
  1006. 0xa9, 0x18, 0x60, 0x9d,
  1007. 0x80, 0x35, 0x06, 0x01,
  1008. 0x06, 0xb6, 0xc2, 0xb5,
  1009. 0xef, 0x7c, 0x04
  1010. ])
  1011. });
  1012. h264Stream.flush();
  1013. assert.ok(data, 'generated a data event');
  1014. assert.equal(data.nalUnitType, 'seq_parameter_set_rbsp', 'identified a sequence parameter set');
  1015. data = null;
  1016. h264Stream.push({
  1017. type: 'video',
  1018. data: new Uint8Array([
  1019. 0x00, 0x00, 0x00, 0x01,
  1020. 0x08, 0x01
  1021. ])
  1022. });
  1023. h264Stream.flush();
  1024. assert.ok(data, 'generated a data event');
  1025. assert.equal(data.nalUnitType, 'pic_parameter_set_rbsp', 'identified a picture parameter set');
  1026. data = null;
  1027. h264Stream.push({
  1028. type: 'video',
  1029. data: new Uint8Array([
  1030. 0x00, 0x00, 0x00, 0x01,
  1031. 0x05, 0x01
  1032. ])
  1033. });
  1034. h264Stream.flush();
  1035. assert.ok(data, 'generated a data event');
  1036. assert.equal(data.nalUnitType, 'slice_layer_without_partitioning_rbsp_idr', 'identified a key frame');
  1037. data = null;
  1038. h264Stream.push({
  1039. type: 'video',
  1040. data: new Uint8Array([
  1041. 0x00, 0x00, 0x00, 0x01,
  1042. 0x06, 0x01
  1043. ])
  1044. });
  1045. h264Stream.flush();
  1046. assert.ok(data, 'generated a data event');
  1047. assert.equal(data.nalUnitType, 'sei_rbsp', 'identified a supplemental enhancement information unit');
  1048. });
  1049. // MP4 expects H264 (aka AVC) data to be in storage format. Storage
  1050. // format is optimized for reliable, random-access media in contrast
  1051. // to the byte stream format that retransmits metadata regularly to
  1052. // allow decoders to quickly begin operation from wherever in the
  1053. // broadcast they begin receiving.
  1054. // Details on the byte stream format can be found in Annex B of
  1055. // Recommendation ITU-T H.264.
  1056. // The storage format is described in ISO/IEC 14496-15
  1057. QUnit.test('strips byte stream framing during parsing', function(assert) {
  1058. var data = [];
  1059. h264Stream.on('data', function(event) {
  1060. data.push(event);
  1061. });
  1062. h264Stream.push({
  1063. type: 'video',
  1064. data: new Uint8Array([
  1065. // -- NAL unit start
  1066. // zero_byte
  1067. 0x00,
  1068. // start_code_prefix_one_3bytes
  1069. 0x00, 0x00, 0x01,
  1070. // nal_unit_type (picture parameter set)
  1071. 0x08,
  1072. // fake data
  1073. 0x01, 0x02, 0x03, 0x04,
  1074. 0x05, 0x06, 0x07,
  1075. // trailing_zero_8bits * 5
  1076. 0x00, 0x00, 0x00, 0x00,
  1077. 0x00,
  1078. // -- NAL unit start
  1079. // zero_byte
  1080. 0x00,
  1081. // start_code_prefix_one_3bytes
  1082. 0x00, 0x00, 0x01,
  1083. // nal_unit_type (access_unit_delimiter_rbsp)
  1084. 0x09,
  1085. // fake data
  1086. 0x06, 0x05, 0x04, 0x03,
  1087. 0x02, 0x01, 0x00
  1088. ])
  1089. });
  1090. h264Stream.flush();
  1091. assert.equal(data.length, 2, 'parsed two NAL units');
  1092. assert.deepEqual(new Uint8Array([
  1093. 0x08,
  1094. 0x01, 0x02, 0x03, 0x04,
  1095. 0x05, 0x06, 0x07
  1096. ]), new Uint8Array(data[0].data), 'parsed the first NAL unit');
  1097. assert.deepEqual(new Uint8Array([
  1098. 0x09,
  1099. 0x06, 0x05, 0x04, 0x03,
  1100. 0x02, 0x01, 0x00
  1101. ]), new Uint8Array(data[1].data), 'parsed the second NAL unit');
  1102. });
  1103. QUnit.test('can be reset', function(assert) {
  1104. var input = {
  1105. type: 'video',
  1106. data: new Uint8Array([
  1107. 0x00, 0x00, 0x00, 0x01,
  1108. 0x09, 0x07,
  1109. 0x00, 0x00, 0x01
  1110. ])
  1111. }, data = [];
  1112. // only the laQUnit.test event is relevant for this QUnit.test
  1113. h264Stream.on('data', function(event) {
  1114. data.push(event);
  1115. });
  1116. h264Stream.push(input);
  1117. h264Stream.flush();
  1118. h264Stream.push(input);
  1119. h264Stream.flush();
  1120. assert.equal(data.length, 2, 'generated two data events');
  1121. assert.equal(data[1].nalUnitType, 'access_unit_delimiter_rbsp', 'identified an access unit delimiter');
  1122. assert.equal(data[1].data.length, 2, 'calculated nal unit length');
  1123. assert.equal(data[1].data[1], 7, 'read a payload byte');
  1124. });
  1125. QUnit.module('VideoSegmentStream', {
  1126. beforeEach: function() {
  1127. var track = {};
  1128. var options = {};
  1129. videoSegmentStream = new VideoSegmentStream(track, options);
  1130. videoSegmentStream.track = track;
  1131. videoSegmentStream.options = options;
  1132. videoSegmentStream.track.timelineStartInfo = {
  1133. dts: 10,
  1134. pts: 10,
  1135. baseMediaDecodeTime: 0
  1136. };
  1137. }
  1138. });
  1139. // see ISO/IEC 14496-15, Section 5 "AVC elementary streams and sample definitions"
  1140. QUnit.test('concatenates NAL units into AVC elementary streams', function(assert) {
  1141. var segment, boxes;
  1142. videoSegmentStream.on('data', function(data) {
  1143. segment = data.boxes;
  1144. });
  1145. videoSegmentStream.push({
  1146. nalUnitType: 'access_unit_delimiter_rbsp',
  1147. data: new Uint8Array([0x09, 0x01])
  1148. });
  1149. videoSegmentStream.push({
  1150. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1151. data: new Uint8Array([0x05, 0x01])
  1152. });
  1153. videoSegmentStream.push({
  1154. data: new Uint8Array([
  1155. 0x08,
  1156. 0x01, 0x02, 0x03
  1157. ])
  1158. });
  1159. videoSegmentStream.push({
  1160. data: new Uint8Array([
  1161. 0x08,
  1162. 0x04, 0x03, 0x02, 0x01, 0x00
  1163. ])
  1164. });
  1165. videoSegmentStream.flush();
  1166. assert.ok(segment, 'generated a data event');
  1167. boxes = mp4.tools.inspect(segment);
  1168. assert.equal(boxes[1].byteLength,
  1169. (2 + 4) + (2 + 4) + (4 + 4) + (4 + 6),
  1170. 'wrote the correct number of bytes');
  1171. assert.deepEqual(new Uint8Array(segment.subarray(boxes[0].size + 8)), new Uint8Array([
  1172. 0, 0, 0, 2,
  1173. 0x09, 0x01,
  1174. 0, 0, 0, 2,
  1175. 0x05, 0x01,
  1176. 0, 0, 0, 4,
  1177. 0x08, 0x01, 0x02, 0x03,
  1178. 0, 0, 0, 6,
  1179. 0x08, 0x04, 0x03, 0x02, 0x01, 0x00
  1180. ]), 'wrote an AVC stream into the mdat');
  1181. });
  1182. QUnit.test('infers sample durations from DTS values', function(assert) {
  1183. var segment, boxes, samples;
  1184. videoSegmentStream.on('data', function(data) {
  1185. segment = data.boxes;
  1186. });
  1187. videoSegmentStream.push({
  1188. data: new Uint8Array([0x09, 0x01]),
  1189. nalUnitType: 'access_unit_delimiter_rbsp',
  1190. dts: 1
  1191. });
  1192. videoSegmentStream.push({
  1193. data: new Uint8Array([0x09, 0x01]),
  1194. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1195. dts: 1
  1196. });
  1197. videoSegmentStream.push({
  1198. data: new Uint8Array([0x09, 0x01]),
  1199. nalUnitType: 'access_unit_delimiter_rbsp',
  1200. dts: 2
  1201. });
  1202. videoSegmentStream.push({
  1203. data: new Uint8Array([0x09, 0x01]),
  1204. nalUnitType: 'access_unit_delimiter_rbsp',
  1205. dts: 4
  1206. });
  1207. videoSegmentStream.flush();
  1208. boxes = mp4.tools.inspect(segment);
  1209. samples = boxes[0].boxes[1].boxes[2].samples;
  1210. assert.equal(samples.length, 3, 'generated three samples');
  1211. assert.equal(samples[0].duration, 1, 'set the first sample duration');
  1212. assert.equal(samples[1].duration, 2, 'set the second sample duration');
  1213. assert.equal(samples[2].duration, 2, 'inferred the final sample duration');
  1214. });
  1215. QUnit.test('filters pre-IDR samples and calculate duration correctly', function(assert) {
  1216. var segment, boxes, samples;
  1217. videoSegmentStream.on('data', function(data) {
  1218. segment = data.boxes;
  1219. });
  1220. videoSegmentStream.push({
  1221. data: new Uint8Array([0x09, 0x01]),
  1222. nalUnitType: 'access_unit_delimiter_rbsp',
  1223. dts: 1
  1224. });
  1225. videoSegmentStream.push({
  1226. data: new Uint8Array([0x09, 0x01]),
  1227. nalUnitType: 'slice_layer_without_partitioning_rbsp',
  1228. dts: 1
  1229. });
  1230. videoSegmentStream.push({
  1231. data: new Uint8Array([0x09, 0x01]),
  1232. nalUnitType: 'access_unit_delimiter_rbsp',
  1233. dts: 1
  1234. });
  1235. videoSegmentStream.push({
  1236. data: new Uint8Array([0x09, 0x01]),
  1237. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1238. dts: 2
  1239. });
  1240. videoSegmentStream.push({
  1241. data: new Uint8Array([0x09, 0x01]),
  1242. nalUnitType: 'access_unit_delimiter_rbsp',
  1243. dts: 4
  1244. });
  1245. videoSegmentStream.flush();
  1246. boxes = mp4.tools.inspect(segment);
  1247. samples = boxes[0].boxes[1].boxes[2].samples;
  1248. assert.equal(samples.length, 2, 'generated two samples, filters out pre-IDR');
  1249. assert.equal(samples[0].duration, 3, 'set the first sample duration');
  1250. assert.equal(samples[1].duration, 3, 'set the second sample duration');
  1251. });
  1252. QUnit.test('holds onto the last GOP and prepends the subsequent push operation with that GOP', function(assert) {
  1253. var segment, boxes, samples;
  1254. videoSegmentStream.track.timelineStartInfo.dts = 0;
  1255. videoSegmentStream.push({
  1256. data: new Uint8Array([0x01, 0x01]),
  1257. nalUnitType: 'access_unit_delimiter_rbsp',
  1258. dts: 1,
  1259. pts: 1
  1260. });
  1261. videoSegmentStream.push({
  1262. data: new Uint8Array([0x00, 0x00]),
  1263. nalUnitType: 'seq_parameter_set_rbsp',
  1264. config: {},
  1265. dts: 1,
  1266. pts: 1
  1267. });
  1268. videoSegmentStream.push({
  1269. data: new Uint8Array([0x00, 0x00]),
  1270. nalUnitType: 'pic_parameter_set_rbsp',
  1271. dts: 1,
  1272. pts: 1
  1273. });
  1274. videoSegmentStream.push({
  1275. data: new Uint8Array([0x66, 0x66]),
  1276. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1277. dts: 1,
  1278. pts: 1
  1279. });
  1280. videoSegmentStream.push({
  1281. data: new Uint8Array([0x01, 0x02]),
  1282. nalUnitType: 'access_unit_delimiter_rbsp',
  1283. dts: 2,
  1284. pts: 2
  1285. });
  1286. videoSegmentStream.push({
  1287. data: new Uint8Array([0x01, 0x03]),
  1288. nalUnitType: 'access_unit_delimiter_rbsp',
  1289. dts: 3,
  1290. pts: 3
  1291. });
  1292. videoSegmentStream.push({
  1293. data: new Uint8Array([0x99, 0x99]),
  1294. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1295. dts: 3,
  1296. pts: 3
  1297. });
  1298. videoSegmentStream.push({
  1299. data: new Uint8Array([0x01, 0x04]),
  1300. nalUnitType: 'access_unit_delimiter_rbsp',
  1301. dts: 4,
  1302. pts: 4
  1303. });
  1304. videoSegmentStream.flush();
  1305. videoSegmentStream.on('data', function(data) {
  1306. segment = data.boxes;
  1307. });
  1308. videoSegmentStream.push({
  1309. data: new Uint8Array([0x02, 0x01]),
  1310. nalUnitType: 'access_unit_delimiter_rbsp',
  1311. dts: 5,
  1312. pts: 5
  1313. });
  1314. videoSegmentStream.push({
  1315. data: new Uint8Array([0x02, 0x02]),
  1316. nalUnitType: 'access_unit_delimiter_rbsp',
  1317. dts: 6,
  1318. pts: 6
  1319. });
  1320. videoSegmentStream.push({
  1321. data: new Uint8Array([0x00, 0x00]),
  1322. nalUnitType: 'seq_parameter_set_rbsp',
  1323. config: {},
  1324. dts: 1,
  1325. pts: 1
  1326. });
  1327. videoSegmentStream.push({
  1328. data: new Uint8Array([0x00, 0x00]),
  1329. nalUnitType: 'pic_parameter_set_rbsp',
  1330. dts: 1,
  1331. pts: 1
  1332. });
  1333. videoSegmentStream.push({
  1334. data: new Uint8Array([0x11, 0x11]),
  1335. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1336. dts: 6,
  1337. pts: 6
  1338. });
  1339. videoSegmentStream.flush();
  1340. boxes = mp4.tools.inspect(segment);
  1341. samples = boxes[0].boxes[1].boxes[2].samples;
  1342. assert.equal(samples.length, 4, 'generated four samples, two from previous segment');
  1343. assert.equal(samples[0].size, 12, 'first sample is an AUD + IDR pair');
  1344. assert.equal(samples[1].size, 6, 'second sample is an AUD');
  1345. assert.equal(samples[2].size, 6, 'third sample is an AUD');
  1346. assert.equal(samples[3].size, 24, 'fourth sample is an AUD + PPS + SPS + IDR');
  1347. });
  1348. QUnit.test('doesn\'t prepend the last GOP if the next segment has earlier PTS', function(assert) {
  1349. var segment, boxes, samples;
  1350. videoSegmentStream.push({
  1351. data: new Uint8Array([0x01, 0x01]),
  1352. nalUnitType: 'access_unit_delimiter_rbsp',
  1353. dts: 10,
  1354. pts: 10
  1355. });
  1356. videoSegmentStream.push({
  1357. data: new Uint8Array([0x66, 0x66]),
  1358. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1359. dts: 10,
  1360. pts: 10
  1361. });
  1362. videoSegmentStream.push({
  1363. data: new Uint8Array([0x01, 0x02]),
  1364. nalUnitType: 'access_unit_delimiter_rbsp',
  1365. dts: 11,
  1366. pts: 11
  1367. });
  1368. videoSegmentStream.push({
  1369. data: new Uint8Array([0x01, 0x03]),
  1370. nalUnitType: 'access_unit_delimiter_rbsp',
  1371. dts: 12,
  1372. pts: 12
  1373. });
  1374. videoSegmentStream.push({
  1375. data: new Uint8Array([0x99, 0x99]),
  1376. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1377. dts: 12,
  1378. pts: 12
  1379. });
  1380. videoSegmentStream.push({
  1381. data: new Uint8Array([0x01, 0x04]),
  1382. nalUnitType: 'access_unit_delimiter_rbsp',
  1383. dts: 13,
  1384. pts: 13
  1385. });
  1386. videoSegmentStream.flush();
  1387. videoSegmentStream.on('data', function(data) {
  1388. segment = data.boxes;
  1389. });
  1390. videoSegmentStream.push({
  1391. data: new Uint8Array([0x02, 0x01]),
  1392. nalUnitType: 'access_unit_delimiter_rbsp',
  1393. dts: 5,
  1394. pts: 5
  1395. });
  1396. videoSegmentStream.push({
  1397. data: new Uint8Array([0x02, 0x02]),
  1398. nalUnitType: 'access_unit_delimiter_rbsp',
  1399. dts: 6,
  1400. pts: 6
  1401. });
  1402. videoSegmentStream.push({
  1403. data: new Uint8Array([0x11, 0x11]),
  1404. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1405. dts: 6,
  1406. pts: 6
  1407. });
  1408. videoSegmentStream.flush();
  1409. boxes = mp4.tools.inspect(segment);
  1410. samples = boxes[0].boxes[1].boxes[2].samples;
  1411. assert.equal(samples.length, 1, 'generated one sample');
  1412. assert.equal(samples[0].size, 12, 'first sample is an AUD + IDR pair');
  1413. });
  1414. QUnit.test('doesn\'t prepend the last GOP if the next segment has different PPS or SPS', function(assert) {
  1415. var segment, boxes, samples;
  1416. videoSegmentStream.push({
  1417. data: new Uint8Array([0x01, 0x01]),
  1418. nalUnitType: 'access_unit_delimiter_rbsp',
  1419. dts: 1,
  1420. pts: 1
  1421. });
  1422. videoSegmentStream.push({
  1423. data: new Uint8Array([0x00, 0x00]),
  1424. nalUnitType: 'seq_parameter_set_rbsp',
  1425. config: {},
  1426. dts: 1,
  1427. pts: 1
  1428. });
  1429. videoSegmentStream.push({
  1430. data: new Uint8Array([0x00, 0x00]),
  1431. nalUnitType: 'pic_parameter_set_rbsp',
  1432. dts: 1,
  1433. pts: 1
  1434. });
  1435. videoSegmentStream.push({
  1436. data: new Uint8Array([0x66, 0x66]),
  1437. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1438. dts: 1,
  1439. pts: 1
  1440. });
  1441. videoSegmentStream.push({
  1442. data: new Uint8Array([0x01, 0x02]),
  1443. nalUnitType: 'access_unit_delimiter_rbsp',
  1444. dts: 2,
  1445. pts: 2
  1446. });
  1447. videoSegmentStream.push({
  1448. data: new Uint8Array([0x01, 0x03]),
  1449. nalUnitType: 'access_unit_delimiter_rbsp',
  1450. dts: 3,
  1451. pts: 3
  1452. });
  1453. videoSegmentStream.push({
  1454. data: new Uint8Array([0x99, 0x99]),
  1455. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1456. dts: 3,
  1457. pts: 3
  1458. });
  1459. videoSegmentStream.push({
  1460. data: new Uint8Array([0x01, 0x04]),
  1461. nalUnitType: 'access_unit_delimiter_rbsp',
  1462. dts: 4,
  1463. pts: 4
  1464. });
  1465. videoSegmentStream.flush();
  1466. videoSegmentStream.on('data', function(data) {
  1467. segment = data.boxes;
  1468. });
  1469. videoSegmentStream.push({
  1470. data: new Uint8Array([0x02, 0x01]),
  1471. nalUnitType: 'access_unit_delimiter_rbsp',
  1472. dts: 5,
  1473. pts: 5
  1474. });
  1475. videoSegmentStream.push({
  1476. data: new Uint8Array([0x02, 0x02]),
  1477. nalUnitType: 'access_unit_delimiter_rbsp',
  1478. dts: 6,
  1479. pts: 6
  1480. });
  1481. videoSegmentStream.push({
  1482. data: new Uint8Array([0x00, 0x01]),
  1483. nalUnitType: 'seq_parameter_set_rbsp',
  1484. config: {},
  1485. dts: 1,
  1486. pts: 1
  1487. });
  1488. videoSegmentStream.push({
  1489. data: new Uint8Array([0x00, 0x01]),
  1490. nalUnitType: 'pic_parameter_set_rbsp',
  1491. dts: 1,
  1492. pts: 1
  1493. });
  1494. videoSegmentStream.push({
  1495. data: new Uint8Array([0x11, 0x11]),
  1496. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1497. dts: 6,
  1498. pts: 6
  1499. });
  1500. videoSegmentStream.flush();
  1501. boxes = mp4.tools.inspect(segment);
  1502. samples = boxes[0].boxes[1].boxes[2].samples;
  1503. assert.equal(samples.length, 1, 'generated one sample');
  1504. assert.equal(samples[0].size, 24, 'first sample is an AUD + PPS + SPS + IDR');
  1505. });
  1506. QUnit.test('doesn\'t prepend the last GOP if the next segment is more than 1 seconds in the future', function(assert) {
  1507. var segment, boxes, samples;
  1508. videoSegmentStream.push({
  1509. data: new Uint8Array([0x01, 0x01]),
  1510. nalUnitType: 'access_unit_delimiter_rbsp',
  1511. dts: 1,
  1512. pts: 1
  1513. });
  1514. videoSegmentStream.push({
  1515. data: new Uint8Array([0x66, 0x66]),
  1516. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1517. dts: 1,
  1518. pts: 1
  1519. });
  1520. videoSegmentStream.push({
  1521. data: new Uint8Array([0x01, 0x02]),
  1522. nalUnitType: 'access_unit_delimiter_rbsp',
  1523. dts: 2,
  1524. pts: 2
  1525. });
  1526. videoSegmentStream.push({
  1527. data: new Uint8Array([0x01, 0x03]),
  1528. nalUnitType: 'access_unit_delimiter_rbsp',
  1529. dts: 3,
  1530. pts: 3
  1531. });
  1532. videoSegmentStream.push({
  1533. data: new Uint8Array([0x99, 0x99]),
  1534. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1535. dts: 3,
  1536. pts: 3
  1537. });
  1538. videoSegmentStream.push({
  1539. data: new Uint8Array([0x01, 0x04]),
  1540. nalUnitType: 'access_unit_delimiter_rbsp',
  1541. dts: 4,
  1542. pts: 4
  1543. });
  1544. videoSegmentStream.flush();
  1545. videoSegmentStream.on('data', function(data) {
  1546. segment = data.boxes;
  1547. });
  1548. videoSegmentStream.push({
  1549. data: new Uint8Array([0x02, 0x01]),
  1550. nalUnitType: 'access_unit_delimiter_rbsp',
  1551. dts: 1000000,
  1552. pts: 1000000
  1553. });
  1554. videoSegmentStream.push({
  1555. data: new Uint8Array([0x02, 0x02]),
  1556. nalUnitType: 'access_unit_delimiter_rbsp',
  1557. dts: 1000001,
  1558. pts: 1000001
  1559. });
  1560. videoSegmentStream.push({
  1561. data: new Uint8Array([0x11, 0x11]),
  1562. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1563. dts: 1000001,
  1564. pts: 1000001
  1565. });
  1566. videoSegmentStream.flush();
  1567. boxes = mp4.tools.inspect(segment);
  1568. samples = boxes[0].boxes[1].boxes[2].samples;
  1569. assert.equal(samples.length, 1, 'generated one sample');
  1570. assert.equal(samples[0].size, 12, 'first sample is an AUD + IDR pair');
  1571. });
  1572. QUnit.test('track values from seq_parameter_set_rbsp should be cleared by a flush', function(assert) {
  1573. var track;
  1574. videoSegmentStream.on('data', function(data) {
  1575. track = data.track;
  1576. });
  1577. videoSegmentStream.push({
  1578. data: new Uint8Array([0xFF]),
  1579. nalUnitType: 'access_unit_delimiter_rbsp'
  1580. });
  1581. videoSegmentStream.push({
  1582. data: new Uint8Array([0xFF]),
  1583. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr'
  1584. });
  1585. videoSegmentStream.push({
  1586. data: new Uint8Array([0xFF]),
  1587. nalUnitType: 'seq_parameter_set_rbsp',
  1588. config: {
  1589. width: 123,
  1590. height: 321,
  1591. profileIdc: 1,
  1592. levelIdc: 2,
  1593. profileCompatibility: 3
  1594. },
  1595. dts: 1
  1596. });
  1597. videoSegmentStream.push({
  1598. data: new Uint8Array([0x88]),
  1599. nalUnitType: 'seq_parameter_set_rbsp',
  1600. config: {
  1601. width: 1234,
  1602. height: 4321,
  1603. profileIdc: 4,
  1604. levelIdc: 5,
  1605. profileCompatibility: 6
  1606. },
  1607. dts: 1
  1608. });
  1609. videoSegmentStream.flush();
  1610. assert.equal(track.width, 123, 'width is set by first SPS');
  1611. assert.equal(track.height, 321, 'height is set by first SPS');
  1612. assert.equal(track.sps[0][0], 0xFF, 'first sps is 0xFF');
  1613. assert.equal(track.profileIdc, 1, 'profileIdc is set by first SPS');
  1614. assert.equal(track.levelIdc, 2, 'levelIdc is set by first SPS');
  1615. assert.equal(track.profileCompatibility, 3, 'profileCompatibility is set by first SPS');
  1616. videoSegmentStream.push({
  1617. data: new Uint8Array([0x99]),
  1618. nalUnitType: 'seq_parameter_set_rbsp',
  1619. config: {
  1620. width: 300,
  1621. height: 200,
  1622. profileIdc: 11,
  1623. levelIdc: 12,
  1624. profileCompatibility: 13
  1625. },
  1626. dts: 1
  1627. });
  1628. videoSegmentStream.flush();
  1629. assert.equal(track.width, 300, 'width is set by first SPS after flush');
  1630. assert.equal(track.height, 200, 'height is set by first SPS after flush');
  1631. assert.equal(track.sps.length, 1, 'there is one sps');
  1632. assert.equal(track.sps[0][0], 0x99, 'first sps is 0x99');
  1633. assert.equal(track.profileIdc, 11, 'profileIdc is set by first SPS after flush');
  1634. assert.equal(track.levelIdc, 12, 'levelIdc is set by first SPS after flush');
  1635. assert.equal(track.profileCompatibility, 13, 'profileCompatibility is set by first SPS after flush');
  1636. });
  1637. QUnit.test('track pps from pic_parameter_set_rbsp should be cleared by a flush', function(assert) {
  1638. var track;
  1639. videoSegmentStream.on('data', function(data) {
  1640. track = data.track;
  1641. });
  1642. videoSegmentStream.push({
  1643. data: new Uint8Array([0xFF]),
  1644. nalUnitType: 'access_unit_delimiter_rbsp'
  1645. });
  1646. videoSegmentStream.push({
  1647. data: new Uint8Array([0xFF]),
  1648. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr'
  1649. });
  1650. videoSegmentStream.push({
  1651. data: new Uint8Array([0x01]),
  1652. nalUnitType: 'pic_parameter_set_rbsp',
  1653. dts: 1
  1654. });
  1655. videoSegmentStream.push({
  1656. data: new Uint8Array([0x02]),
  1657. nalUnitType: 'pic_parameter_set_rbsp',
  1658. dts: 1
  1659. });
  1660. videoSegmentStream.flush();
  1661. assert.equal(track.pps[0][0], 0x01, 'first pps is 0x01');
  1662. videoSegmentStream.push({
  1663. data: new Uint8Array([0x03]),
  1664. nalUnitType: 'pic_parameter_set_rbsp',
  1665. dts: 1
  1666. });
  1667. videoSegmentStream.flush();
  1668. assert.equal(track.pps[0][0], 0x03, 'first pps is 0x03 after a flush');
  1669. });
  1670. QUnit.test('calculates compositionTimeOffset values from the PTS/DTS', function(assert) {
  1671. var segment, boxes, samples;
  1672. videoSegmentStream.on('data', function(data) {
  1673. segment = data.boxes;
  1674. });
  1675. videoSegmentStream.push({
  1676. data: new Uint8Array([0x09, 0x01]),
  1677. nalUnitType: 'access_unit_delimiter_rbsp',
  1678. dts: 1,
  1679. pts: 1
  1680. });
  1681. videoSegmentStream.push({
  1682. data: new Uint8Array([0x09, 0x01]),
  1683. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1684. dts: 1
  1685. });
  1686. videoSegmentStream.push({
  1687. data: new Uint8Array([0x09, 0x01]),
  1688. nalUnitType: 'access_unit_delimiter_rbsp',
  1689. dts: 1,
  1690. pts: 2
  1691. });
  1692. videoSegmentStream.push({
  1693. data: new Uint8Array([0x09, 0x01]),
  1694. nalUnitType: 'access_unit_delimiter_rbsp',
  1695. dts: 1,
  1696. pts: 4
  1697. });
  1698. videoSegmentStream.flush();
  1699. boxes = mp4.tools.inspect(segment);
  1700. samples = boxes[0].boxes[1].boxes[2].samples;
  1701. assert.equal(samples.length, 3, 'generated three samples');
  1702. assert.equal(samples[0].compositionTimeOffset, 0, 'calculated compositionTimeOffset');
  1703. assert.equal(samples[1].compositionTimeOffset, 1, 'calculated compositionTimeOffset');
  1704. assert.equal(samples[2].compositionTimeOffset, 3, 'calculated compositionTimeOffset');
  1705. });
  1706. QUnit.test('calculates baseMediaDecodeTime values from the first DTS ever seen and subsequent segments\' lowest DTS', function(assert) {
  1707. var segment, boxes, tfdt;
  1708. videoSegmentStream.on('data', function(data) {
  1709. segment = data.boxes;
  1710. });
  1711. videoSegmentStream.push({
  1712. data: new Uint8Array([0x09, 0x01]),
  1713. nalUnitType: 'access_unit_delimiter_rbsp',
  1714. dts: 100,
  1715. pts: 100
  1716. });
  1717. videoSegmentStream.push({
  1718. data: new Uint8Array([0x09, 0x01]),
  1719. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1720. dts: 100,
  1721. pts: 100
  1722. });
  1723. videoSegmentStream.push({
  1724. data: new Uint8Array([0x09, 0x01]),
  1725. nalUnitType: 'access_unit_delimiter_rbsp',
  1726. dts: 200,
  1727. pts: 200
  1728. });
  1729. videoSegmentStream.push({
  1730. data: new Uint8Array([0x09, 0x01]),
  1731. nalUnitType: 'access_unit_delimiter_rbsp',
  1732. dts: 300,
  1733. pts: 300
  1734. });
  1735. videoSegmentStream.flush();
  1736. boxes = mp4.tools.inspect(segment);
  1737. tfdt = boxes[0].boxes[1].boxes[1];
  1738. assert.equal(tfdt.baseMediaDecodeTime, 90, 'calculated baseMediaDecodeTime');
  1739. });
  1740. QUnit.test('doesn\'t adjust baseMediaDecodeTime when configured to keep original timestamps', function(assert) {
  1741. videoSegmentStream.options.keepOriginalTimestamps = true;
  1742. var segment, boxes, tfdt;
  1743. videoSegmentStream.on('data', function(data) {
  1744. segment = data.boxes;
  1745. });
  1746. videoSegmentStream.push({
  1747. data: new Uint8Array([0x09, 0x01]),
  1748. nalUnitType: 'access_unit_delimiter_rbsp',
  1749. dts: 100,
  1750. pts: 100
  1751. });
  1752. videoSegmentStream.push({
  1753. data: new Uint8Array([0x09, 0x01]),
  1754. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1755. dts: 100,
  1756. pts: 100
  1757. });
  1758. videoSegmentStream.push({
  1759. data: new Uint8Array([0x09, 0x01]),
  1760. nalUnitType: 'access_unit_delimiter_rbsp',
  1761. dts: 200,
  1762. pts: 200
  1763. });
  1764. videoSegmentStream.push({
  1765. data: new Uint8Array([0x09, 0x01]),
  1766. nalUnitType: 'access_unit_delimiter_rbsp',
  1767. dts: 300,
  1768. pts: 300
  1769. });
  1770. videoSegmentStream.flush();
  1771. boxes = mp4.tools.inspect(segment);
  1772. tfdt = boxes[0].boxes[1].boxes[1];
  1773. assert.equal(tfdt.baseMediaDecodeTime, 100, 'calculated baseMediaDecodeTime');
  1774. });
  1775. QUnit.test('calculates baseMediaDecodeTime values relative to a customizable baseMediaDecodeTime', function(assert) {
  1776. var segment, boxes, tfdt, baseMediaDecodeTimeValue;
  1777. // Set the baseMediaDecodeTime to something over 2^32 to ensure
  1778. // that the version 1 TFDT box is being created correctly
  1779. baseMediaDecodeTimeValue = Math.pow(2, 32) + 100;
  1780. videoSegmentStream.track.timelineStartInfo = {
  1781. dts: 10,
  1782. pts: 10,
  1783. baseMediaDecodeTime: baseMediaDecodeTimeValue
  1784. };
  1785. videoSegmentStream.on('data', function(data) {
  1786. segment = data.boxes;
  1787. });
  1788. videoSegmentStream.push({
  1789. data: new Uint8Array([0x09, 0x01]),
  1790. nalUnitType: 'access_unit_delimiter_rbsp',
  1791. dts: 100,
  1792. pts: 100
  1793. });
  1794. videoSegmentStream.push({
  1795. data: new Uint8Array([0x09, 0x01]),
  1796. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1797. dts: 100,
  1798. pts: 100
  1799. });
  1800. videoSegmentStream.push({
  1801. data: new Uint8Array([0x09, 0x01]),
  1802. nalUnitType: 'access_unit_delimiter_rbsp',
  1803. dts: 200,
  1804. pts: 200
  1805. });
  1806. videoSegmentStream.push({
  1807. data: new Uint8Array([0x09, 0x01]),
  1808. nalUnitType: 'access_unit_delimiter_rbsp',
  1809. dts: 300,
  1810. pts: 300
  1811. });
  1812. videoSegmentStream.flush();
  1813. boxes = mp4.tools.inspect(segment);
  1814. tfdt = boxes[0].boxes[1].boxes[1];
  1815. // The timeline begins at 10 and the first sample has a dts of
  1816. // 100, so the baseMediaDecodeTime should be equal to (100 - 10)
  1817. assert.equal(tfdt.baseMediaDecodeTime, baseMediaDecodeTimeValue + 90, 'calculated baseMediaDecodeTime');
  1818. });
  1819. QUnit.test('do not subtract the first frame\'s compositionTimeOffset from baseMediaDecodeTime', function(assert) {
  1820. var segment, boxes, tfdt;
  1821. videoSegmentStream.track.timelineStartInfo = {
  1822. dts: 10,
  1823. pts: 10,
  1824. baseMediaDecodeTime: 100
  1825. };
  1826. videoSegmentStream.on('data', function(data) {
  1827. segment = data.boxes;
  1828. });
  1829. videoSegmentStream.push({
  1830. data: new Uint8Array([0x09, 0x01]),
  1831. nalUnitType: 'access_unit_delimiter_rbsp',
  1832. dts: 50,
  1833. pts: 60
  1834. });
  1835. videoSegmentStream.push({
  1836. data: new Uint8Array([0x09, 0x01]),
  1837. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1838. dts: 50,
  1839. pts: 60
  1840. });
  1841. videoSegmentStream.push({
  1842. data: new Uint8Array([0x09, 0x01]),
  1843. nalUnitType: 'access_unit_delimiter_rbsp',
  1844. dts: 100,
  1845. pts: 110
  1846. });
  1847. videoSegmentStream.push({
  1848. data: new Uint8Array([0x09, 0x01]),
  1849. nalUnitType: 'access_unit_delimiter_rbsp',
  1850. dts: 150,
  1851. pts: 160
  1852. });
  1853. videoSegmentStream.flush();
  1854. boxes = mp4.tools.inspect(segment);
  1855. tfdt = boxes[0].boxes[1].boxes[1];
  1856. // The timelineStartInfo's bMDT is 100 and that corresponds to a dts/pts of 10
  1857. // The first frame has a dts 50 so the bMDT is calculated as: (50 - 10) + 100 = 140
  1858. assert.equal(tfdt.baseMediaDecodeTime, 140, 'calculated baseMediaDecodeTime');
  1859. });
  1860. QUnit.test('video segment stream triggers segmentTimingInfo with timing info',
  1861. function(assert) {
  1862. var
  1863. segmentTimingInfoArr = [],
  1864. baseMediaDecodeTime = 40,
  1865. startingDts = 50,
  1866. startingPts = 60,
  1867. lastFrameStartDts = 150,
  1868. lastFrameStartPts = 160;
  1869. videoSegmentStream.on('segmentTimingInfo', function(segmentTimingInfo) {
  1870. segmentTimingInfoArr.push(segmentTimingInfo);
  1871. });
  1872. videoSegmentStream.push({
  1873. data: new Uint8Array([0x09, 0x01]),
  1874. nalUnitType: 'access_unit_delimiter_rbsp',
  1875. dts: startingDts,
  1876. pts: startingPts
  1877. });
  1878. videoSegmentStream.push({
  1879. data: new Uint8Array([0x09, 0x01]),
  1880. nalUnitType: 'slice_layer_without_partitioning_rbsp_idr',
  1881. dts: startingDts,
  1882. pts: startingPts
  1883. });
  1884. videoSegmentStream.push({
  1885. data: new Uint8Array([0x09, 0x01]),
  1886. nalUnitType: 'access_unit_delimiter_rbsp',
  1887. dts: 100,
  1888. pts: 110
  1889. });
  1890. videoSegmentStream.push({
  1891. data: new Uint8Array([0x09, 0x01]),
  1892. nalUnitType: 'access_unit_delimiter_rbsp',
  1893. dts: lastFrameStartDts,
  1894. pts: lastFrameStartPts
  1895. });
  1896. videoSegmentStream.flush();
  1897. assert.equal(segmentTimingInfoArr.length, 1, 'triggered segmentTimingInfo once');
  1898. assert.equal(
  1899. baseMediaDecodeTime,
  1900. segmentTimingInfoArr[0].baseMediaDecodeTime,
  1901. 'set baseMediaDecodeTime'
  1902. );
  1903. assert.deepEqual(segmentTimingInfoArr[0], {
  1904. start: {
  1905. // baseMediaDecodeTime
  1906. dts: 40,
  1907. // baseMediaDecodeTime + startingPts - startingDts
  1908. pts: 40 + 60 - 50
  1909. },
  1910. end: {
  1911. // because no duration is provided in this test, the duration will instead be based
  1912. // on the previous frame, which will be the start of this frame minus the end of the
  1913. // last frame, or 150 - 100 = 50, which gets added to lastFrameStartDts - startDts =
  1914. // 150 - 50 = 100
  1915. // + baseMediaDecodeTime
  1916. dts: 40 + 100 + 50,
  1917. pts: 40 + 100 + 50
  1918. },
  1919. prependedContentDuration: 0,
  1920. baseMediaDecodeTime: baseMediaDecodeTime
  1921. }, 'triggered correct segment timing info');
  1922. });
  1923. QUnit.test('aignGopsAtStart_ filters gops appropriately', function(assert) {
  1924. var gopsToAlignWith, gops, actual, expected;
  1925. // atog === arrayToGops
  1926. var atog = function(list) {
  1927. var mapped = list.map(function(item) {
  1928. return {
  1929. pts: item,
  1930. dts: item,
  1931. nalCount: 1,
  1932. duration: 1,
  1933. byteLength: 1
  1934. };
  1935. });
  1936. mapped.byteLength = mapped.length;
  1937. mapped.nalCount = mapped.length;
  1938. mapped.duration = mapped.length;
  1939. mapped.dts = mapped[0].dts;
  1940. mapped.pts = mapped[0].pts;
  1941. return mapped;
  1942. };
  1943. // no gops to trim, all gops start after any alignment candidates
  1944. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  1945. gops = atog([10, 12, 13, 14, 16]);
  1946. expected = atog([10, 12, 13, 14, 16]);
  1947. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1948. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1949. assert.deepEqual(actual, expected,
  1950. 'no gops to trim, all gops start after any alignment candidates');
  1951. // no gops to trim, first gop has a match with first alignment candidate
  1952. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  1953. gops = atog([0, 2, 4, 6, 8]);
  1954. expected = atog([0, 2, 4, 6, 8]);
  1955. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1956. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1957. assert.deepEqual(actual, expected,
  1958. 'no gops to trim, first gop has a match with first alignment candidate');
  1959. // no gops to trim, first gop has a match with last alignment candidate
  1960. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  1961. gops = atog([8, 10, 12, 13, 14, 16]);
  1962. expected = atog([8, 10, 12, 13, 14, 16]);
  1963. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1964. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1965. assert.deepEqual(actual, expected,
  1966. 'no gops to trim, first gop has a match with last alignment candidate');
  1967. // no gops to trim, first gop has a match with an alignment candidate
  1968. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  1969. gops = atog([6, 9, 10, 12, 13, 14, 16]);
  1970. expected = atog([6, 9, 10, 12, 13, 14, 16]);
  1971. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1972. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1973. assert.deepEqual(actual, expected,
  1974. 'no gops to trim, first gop has a match with an alignment candidate');
  1975. // all gops trimmed, all gops come before first alignment candidate
  1976. gopsToAlignWith = atog([10, 12, 13, 14, 16]);
  1977. gops = atog([0, 2, 4, 6, 8]);
  1978. expected = null;
  1979. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1980. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1981. assert.deepEqual(actual, expected,
  1982. 'all gops trimmed, all gops come before first alignment candidate');
  1983. // all gops trimmed, all gops come before last alignment candidate, no match found
  1984. gopsToAlignWith = atog([10, 12, 13, 14, 16]);
  1985. gops = atog([0, 2, 4, 6, 8, 11, 15]);
  1986. expected = null;
  1987. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1988. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1989. assert.deepEqual(actual, expected,
  1990. 'all gops trimmed, all gops come before last alignment candidate, no match found');
  1991. // all gops trimmed, all gops contained between alignment candidates, no match found
  1992. gopsToAlignWith = atog([6, 10, 12, 13, 14, 16]);
  1993. gops = atog([7, 8, 9, 11, 15]);
  1994. expected = null;
  1995. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  1996. actual = videoSegmentStream.alignGopsAtStart_(gops);
  1997. assert.deepEqual(actual, expected,
  1998. 'all gops trimmed, all gops contained between alignment candidates, no match found');
  1999. // some gops trimmed, some gops before first alignment candidate
  2000. // match on first alignment candidate
  2001. gopsToAlignWith = atog([9, 10, 13, 16]);
  2002. gops = atog([7, 8, 9, 10, 12]);
  2003. expected = atog([9, 10, 12]);
  2004. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2005. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2006. assert.deepEqual(actual, expected,
  2007. 'some gops trimmed, some gops before first alignment candidate,' +
  2008. 'match on first alignment candidate');
  2009. // some gops trimmed, some gops before first alignment candidate
  2010. // match on an alignment candidate
  2011. gopsToAlignWith = atog([9, 10, 13, 16]);
  2012. gops = atog([7, 8, 11, 13, 14]);
  2013. expected = atog([13, 14]);
  2014. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2015. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2016. assert.deepEqual(actual, expected,
  2017. 'some gops trimmed, some gops before first alignment candidate,' +
  2018. 'match on an alignment candidate');
  2019. // some gops trimmed, some gops before first alignment candidate
  2020. // match on last alignment candidate
  2021. gopsToAlignWith = atog([9, 10, 13, 16]);
  2022. gops = atog([7, 8, 11, 12, 15, 16]);
  2023. expected = atog([16]);
  2024. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2025. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2026. assert.deepEqual(actual, expected,
  2027. 'some gops trimmed, some gops before first alignment candidate,' +
  2028. 'match on last alignment candidate');
  2029. // some gops trimmed, some gops after last alignment candidate
  2030. // match on an alignment candidate
  2031. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2032. gops = atog([4, 5, 9, 11, 13]);
  2033. expected = atog([9, 11, 13]);
  2034. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2035. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2036. assert.deepEqual(actual, expected,
  2037. 'some gops trimmed, some gops after last alignment candidate,' +
  2038. 'match on an alignment candidate');
  2039. // some gops trimmed, some gops after last alignment candidate
  2040. // match on last alignment candidate
  2041. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2042. gops = atog([4, 5, 7, 10, 13]);
  2043. expected = atog([10, 13]);
  2044. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2045. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2046. assert.deepEqual(actual, expected,
  2047. 'some gops trimmed, some gops after last alignment candidate,' +
  2048. 'match on last alignment candidate');
  2049. // some gops trimmed, some gops after last alignment candidate
  2050. // no match found
  2051. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2052. gops = atog([4, 5, 7, 13, 15]);
  2053. expected = atog([13, 15]);
  2054. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2055. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2056. assert.deepEqual(actual, expected,
  2057. 'some gops trimmed, some gops after last alignment candidate,' +
  2058. 'no match found');
  2059. // some gops trimmed, gops contained between alignment candidates
  2060. // match with an alignment candidate
  2061. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2062. gops = atog([2, 4, 6, 8]);
  2063. expected = atog([6, 8]);
  2064. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2065. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2066. assert.deepEqual(actual, expected,
  2067. 'some gops trimmed, gops contained between alignment candidates,' +
  2068. 'match with an alignment candidate');
  2069. // some gops trimmed, alignment candidates contained between gops
  2070. // no match
  2071. gopsToAlignWith = atog([3, 6, 9, 10]);
  2072. gops = atog([0, 2, 4, 8, 11, 13]);
  2073. expected = atog([11, 13]);
  2074. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2075. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2076. assert.deepEqual(actual, expected,
  2077. 'some gops trimmed, alignment candidates contained between gops,' +
  2078. 'no match');
  2079. // some gops trimmed, alignment candidates contained between gops
  2080. // match with first alignment candidate
  2081. gopsToAlignWith = atog([3, 6, 9, 10]);
  2082. gops = atog([0, 2, 3, 4, 5, 9, 10, 11]);
  2083. expected = atog([3, 4, 5, 9, 10, 11]);
  2084. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2085. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2086. assert.deepEqual(actual, expected,
  2087. 'some gops trimmed, alignment candidates contained between gops,' +
  2088. 'match with first alignment candidate');
  2089. // some gops trimmed, alignment candidates contained between gops
  2090. // match with last alignment candidate
  2091. gopsToAlignWith = atog([3, 6, 9, 10]);
  2092. gops = atog([0, 2, 4, 8, 10, 13, 15]);
  2093. expected = atog([10, 13, 15]);
  2094. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2095. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2096. assert.deepEqual(actual, expected,
  2097. 'some gops trimmed, alignment candidates contained between gops,' +
  2098. 'match with last alignment candidate');
  2099. // some gops trimmed, alignment candidates contained between gops
  2100. // match with an alignment candidate
  2101. gopsToAlignWith = atog([3, 6, 9, 10]);
  2102. gops = atog([0, 2, 4, 6, 9, 11, 13]);
  2103. expected = atog([6, 9, 11, 13]);
  2104. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2105. actual = videoSegmentStream.alignGopsAtStart_(gops);
  2106. assert.deepEqual(actual, expected,
  2107. 'some gops trimmed, alignment candidates contained between gops,' +
  2108. 'match with an alignment candidate');
  2109. });
  2110. QUnit.test('alignGopsAtEnd_ filters gops appropriately', function(assert) {
  2111. var gopsToAlignWith, gops, actual, expected;
  2112. // atog === arrayToGops
  2113. var atog = function(list) {
  2114. var mapped = list.map(function(item) {
  2115. return {
  2116. pts: item,
  2117. dts: item,
  2118. nalCount: 1,
  2119. duration: 1,
  2120. byteLength: 1
  2121. };
  2122. });
  2123. mapped.byteLength = mapped.length;
  2124. mapped.nalCount = mapped.length;
  2125. mapped.duration = mapped.length;
  2126. mapped.dts = mapped[0].dts;
  2127. mapped.pts = mapped[0].pts;
  2128. return mapped;
  2129. };
  2130. // no gops to trim, all gops start after any alignment candidates
  2131. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  2132. gops = atog([10, 12, 13, 14, 16]);
  2133. expected = atog([10, 12, 13, 14, 16]);
  2134. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2135. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2136. assert.deepEqual(actual, expected,
  2137. 'no gops to trim, all gops start after any alignment candidates');
  2138. // no gops to trim, first gop has a match with first alignment candidate
  2139. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  2140. gops = atog([0, 1, 3, 5, 7]);
  2141. expected = atog([0, 1, 3, 5, 7]);
  2142. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2143. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2144. assert.deepEqual(actual, expected,
  2145. 'no gops to trim, first gop has a match with first alignment candidate');
  2146. // no gops to trim, first gop has a match with last alignment candidate
  2147. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  2148. gops = atog([8, 10, 12, 13, 14, 16]);
  2149. expected = atog([8, 10, 12, 13, 14, 16]);
  2150. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2151. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2152. assert.deepEqual(actual, expected,
  2153. 'no gops to trim, first gop has a match with last alignment candidate');
  2154. // no gops to trim, first gop has a match with an alignment candidate
  2155. gopsToAlignWith = atog([0, 2, 4, 6, 8]);
  2156. gops = atog([6, 9, 10, 12, 13, 14, 16]);
  2157. expected = atog([6, 9, 10, 12, 13, 14, 16]);
  2158. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2159. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2160. assert.deepEqual(actual, expected,
  2161. 'no gops to trim, first gop has a match with an alignment candidate');
  2162. // all gops trimmed, all gops come before first alignment candidate
  2163. gopsToAlignWith = atog([10, 12, 13, 14, 16]);
  2164. gops = atog([0, 2, 4, 6, 8]);
  2165. expected = null;
  2166. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2167. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2168. assert.deepEqual(actual, expected,
  2169. 'all gops trimmed, all gops come before first alignment candidate');
  2170. // all gops trimmed, all gops come before last alignment candidate, no match found
  2171. gopsToAlignWith = atog([10, 12, 13, 14, 16]);
  2172. gops = atog([0, 2, 4, 6, 8, 11, 15]);
  2173. expected = null;
  2174. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2175. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2176. assert.deepEqual(actual, expected,
  2177. 'all gops trimmed, all gops come before last alignment candidate, no match found');
  2178. gopsToAlignWith = atog([10, 12, 13, 14, 16]);
  2179. gops = atog([8, 11, 15]);
  2180. expected = null;
  2181. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2182. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2183. assert.deepEqual(actual, expected,
  2184. 'all gops trimmed, all gops come before last alignment candidate, no match found');
  2185. // all gops trimmed, all gops contained between alignment candidates, no match found
  2186. gopsToAlignWith = atog([6, 10, 12, 13, 14, 16]);
  2187. gops = atog([7, 8, 9, 11, 15]);
  2188. expected = null;
  2189. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2190. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2191. assert.deepEqual(actual, expected,
  2192. 'all gops trimmed, all gops contained between alignment candidates, no match found');
  2193. // some gops trimmed, some gops before first alignment candidate
  2194. // match on first alignment candidate
  2195. gopsToAlignWith = atog([9, 11, 13, 16]);
  2196. gops = atog([7, 8, 9, 10, 12]);
  2197. expected = atog([9, 10, 12]);
  2198. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2199. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2200. assert.deepEqual(actual, expected,
  2201. 'some gops trimmed, some gops before first alignment candidate,' +
  2202. 'match on first alignment candidate');
  2203. // some gops trimmed, some gops before first alignment candidate
  2204. // match on an alignment candidate
  2205. gopsToAlignWith = atog([9, 10, 11, 13, 16]);
  2206. gops = atog([7, 8, 11, 13, 14, 15]);
  2207. expected = atog([13, 14, 15]);
  2208. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2209. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2210. assert.deepEqual(actual, expected,
  2211. 'some gops trimmed, some gops before first alignment candidate,' +
  2212. 'match on an alignment candidate');
  2213. // some gops trimmed, some gops before first alignment candidate
  2214. // match on last alignment candidate
  2215. gopsToAlignWith = atog([9, 10, 13, 16]);
  2216. gops = atog([7, 8, 11, 12, 15, 16]);
  2217. expected = atog([16]);
  2218. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2219. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2220. assert.deepEqual(actual, expected,
  2221. 'some gops trimmed, some gops before first alignment candidate,' +
  2222. 'match on last alignment candidate');
  2223. // some gops trimmed, some gops after last alignment candidate
  2224. // match on an alignment candidate
  2225. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2226. gops = atog([4, 5, 6, 9, 11, 13]);
  2227. expected = atog([9, 11, 13]);
  2228. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2229. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2230. assert.deepEqual(actual, expected,
  2231. 'some gops trimmed, some gops after last alignment candidate,' +
  2232. 'match on an alignment candidate');
  2233. // some gops trimmed, some gops after last alignment candidate
  2234. // match on last alignment candidate
  2235. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2236. gops = atog([4, 5, 7, 9, 10, 13]);
  2237. expected = atog([10, 13]);
  2238. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2239. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2240. assert.deepEqual(actual, expected,
  2241. 'some gops trimmed, some gops after last alignment candidate,' +
  2242. 'match on last alignment candidate');
  2243. // some gops trimmed, some gops after last alignment candidate
  2244. // no match found
  2245. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2246. gops = atog([4, 5, 7, 13, 15]);
  2247. expected = atog([13, 15]);
  2248. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2249. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2250. assert.deepEqual(actual, expected,
  2251. 'some gops trimmed, some gops after last alignment candidate,' +
  2252. 'no match found');
  2253. // some gops trimmed, gops contained between alignment candidates
  2254. // match with an alignment candidate
  2255. gopsToAlignWith = atog([0, 3, 6, 9, 10]);
  2256. gops = atog([2, 4, 6, 8]);
  2257. expected = atog([6, 8]);
  2258. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2259. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2260. assert.deepEqual(actual, expected,
  2261. 'some gops trimmed, gops contained between alignment candidates,' +
  2262. 'match with an alignment candidate');
  2263. // some gops trimmed, alignment candidates contained between gops
  2264. // no match
  2265. gopsToAlignWith = atog([3, 6, 9, 10]);
  2266. gops = atog([0, 2, 4, 8, 11, 13]);
  2267. expected = atog([11, 13]);
  2268. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2269. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2270. assert.deepEqual(actual, expected,
  2271. 'some gops trimmed, alignment candidates contained between gops,' +
  2272. 'no match');
  2273. // some gops trimmed, alignment candidates contained between gops
  2274. // match with first alignment candidate
  2275. gopsToAlignWith = atog([3, 6, 9, 10]);
  2276. gops = atog([0, 2, 3, 4, 5, 11]);
  2277. expected = atog([3, 4, 5, 11]);
  2278. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2279. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2280. assert.deepEqual(actual, expected,
  2281. 'some gops trimmed, alignment candidates contained between gops,' +
  2282. 'match with first alignment candidate');
  2283. // some gops trimmed, alignment candidates contained between gops
  2284. // match with last alignment candidate
  2285. gopsToAlignWith = atog([3, 6, 9, 10]);
  2286. gops = atog([0, 2, 4, 8, 10, 13, 15]);
  2287. expected = atog([10, 13, 15]);
  2288. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2289. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2290. assert.deepEqual(actual, expected,
  2291. 'some gops trimmed, alignment candidates contained between gops,' +
  2292. 'match with last alignment candidate');
  2293. // some gops trimmed, alignment candidates contained between gops
  2294. // match with an alignment candidate
  2295. gopsToAlignWith = atog([3, 6, 9, 10]);
  2296. gops = atog([0, 2, 4, 6, 9, 11, 13]);
  2297. expected = atog([9, 11, 13]);
  2298. videoSegmentStream.alignGopsWith(gopsToAlignWith);
  2299. actual = videoSegmentStream.alignGopsAtEnd_(gops);
  2300. assert.deepEqual(actual, expected,
  2301. 'some gops trimmed, alignment candidates contained between gops,' +
  2302. 'match with an alignment candidate');
  2303. });
  2304. QUnit.test('generateSegmentTimingInfo generates correct timing info object', function(assert) {
  2305. var
  2306. firstFrame = {
  2307. dts: 12,
  2308. pts: 14,
  2309. duration: 3
  2310. },
  2311. lastFrame = {
  2312. dts: 120,
  2313. pts: 140,
  2314. duration: 4
  2315. },
  2316. baseMediaDecodeTime = 20,
  2317. prependedContentDuration = 0;
  2318. assert.deepEqual(
  2319. generateSegmentTimingInfo(
  2320. baseMediaDecodeTime,
  2321. firstFrame.dts,
  2322. firstFrame.pts,
  2323. lastFrame.dts + lastFrame.duration,
  2324. lastFrame.pts + lastFrame.duration,
  2325. prependedContentDuration
  2326. ), {
  2327. start: {
  2328. // baseMediaDecodeTime,
  2329. dts: 20,
  2330. // baseMediaDecodeTime + firstFrame.pts - firstFrame.dts
  2331. pts: 20 + 14 - 12
  2332. },
  2333. end: {
  2334. // baseMediaDecodeTime + lastFrame.dts + lastFrame.duration - firstFrame.dts,
  2335. dts: 20 + 120 + 4 - 12,
  2336. // baseMediaDecodeTime + lastFrame.pts + lastFrame.duration - firstFrame.pts
  2337. pts: 20 + 140 + 4 - 14
  2338. },
  2339. prependedContentDuration: 0,
  2340. baseMediaDecodeTime: baseMediaDecodeTime
  2341. }, 'generated correct timing info object');
  2342. });
  2343. QUnit.test('generateSegmentTimingInfo accounts for prepended GOPs', function(assert) {
  2344. var
  2345. firstFrame = {
  2346. dts: 12,
  2347. pts: 14,
  2348. duration: 3
  2349. },
  2350. lastFrame = {
  2351. dts: 120,
  2352. pts: 140,
  2353. duration: 4
  2354. },
  2355. baseMediaDecodeTime = 20,
  2356. prependedContentDuration = 7;
  2357. assert.deepEqual(
  2358. generateSegmentTimingInfo(
  2359. baseMediaDecodeTime,
  2360. firstFrame.dts,
  2361. firstFrame.pts,
  2362. lastFrame.dts + lastFrame.duration,
  2363. lastFrame.pts + lastFrame.duration,
  2364. prependedContentDuration
  2365. ), {
  2366. start: {
  2367. // baseMediaDecodeTime,
  2368. dts: 20,
  2369. // baseMediaDecodeTime + firstFrame.pts - firstFrame.dts
  2370. pts: 20 + 14 - 12
  2371. },
  2372. end: {
  2373. // baseMediaDecodeTime + lastFrame.dts + lastFrame.duration - firstFrame.dts,
  2374. dts: 20 + 120 + 4 - 12,
  2375. // baseMediaDecodeTime + lastFrame.pts + lastFrame.duration - firstFrame.pts
  2376. pts: 20 + 140 + 4 - 14
  2377. },
  2378. prependedContentDuration: 7,
  2379. baseMediaDecodeTime: 20
  2380. },
  2381. 'included prepended content duration in timing info');
  2382. });
  2383. QUnit.test('generateSegmentTimingInfo handles GOPS where pts is < dts', function(assert) {
  2384. var
  2385. firstFrame = {
  2386. dts: 14,
  2387. pts: 12,
  2388. duration: 3
  2389. },
  2390. lastFrame = {
  2391. dts: 140,
  2392. pts: 120,
  2393. duration: 4
  2394. },
  2395. baseMediaDecodeTime = 20,
  2396. prependedContentDuration = 7;
  2397. assert.deepEqual(
  2398. generateSegmentTimingInfo(
  2399. baseMediaDecodeTime,
  2400. firstFrame.dts,
  2401. firstFrame.pts,
  2402. lastFrame.dts + lastFrame.duration,
  2403. lastFrame.pts + lastFrame.duration,
  2404. prependedContentDuration
  2405. ), {
  2406. start: {
  2407. // baseMediaDecodeTime,
  2408. dts: 20,
  2409. // baseMediaDecodeTime + firstFrame.pts - firstFrame.dts
  2410. pts: 20 + 12 - 14
  2411. },
  2412. end: {
  2413. // baseMediaDecodeTime + lastFrame.dts + lastFrame.duration - firstFrame.dts,
  2414. dts: 20 + 140 + 4 - 14,
  2415. // baseMediaDecodeTime + lastFrame.pts + lastFrame.duration - firstFrame.pts
  2416. pts: 20 + 120 + 4 - 12
  2417. },
  2418. prependedContentDuration: 7,
  2419. baseMediaDecodeTime: 20
  2420. },
  2421. 'included prepended content duration in timing info');
  2422. });
  2423. QUnit.module('ADTS Stream', {
  2424. beforeEach: function() {
  2425. adtsStream = new AdtsStream();
  2426. }
  2427. });
  2428. QUnit.test('generates AAC frame events from ADTS bytes', function(assert) {
  2429. var frames = [];
  2430. adtsStream.on('data', function(frame) {
  2431. frames.push(frame);
  2432. });
  2433. adtsStream.push({
  2434. type: 'audio',
  2435. data: new Uint8Array([
  2436. 0xff, 0xf1, // no CRC
  2437. 0x10, // AAC Main, 44.1KHz
  2438. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2439. 0x00, // one AAC per ADTS frame
  2440. 0x12, 0x34, // AAC payload
  2441. 0x56, 0x78 // extra junk that should be ignored
  2442. ])
  2443. });
  2444. assert.equal(frames.length, 1, 'generated one frame');
  2445. assert.deepEqual(new Uint8Array(frames[0].data),
  2446. new Uint8Array([0x12, 0x34]),
  2447. 'extracted AAC frame');
  2448. assert.equal(frames[0].channelcount, 2, 'parsed channelcount');
  2449. assert.equal(frames[0].samplerate, 44100, 'parsed samplerate');
  2450. // Chrome only supports 8, 16, and 32 bit sample sizes. Assuming the
  2451. // default value of 16 in ISO/IEC 14496-12 AudioSampleEntry is
  2452. // acceptable.
  2453. assert.equal(frames[0].samplesize, 16, 'parsed samplesize');
  2454. });
  2455. QUnit.test('skips garbage data between sync words', function(assert) {
  2456. var frames = [];
  2457. var logs = [];
  2458. adtsStream.on('data', function(frame) {
  2459. frames.push(frame);
  2460. });
  2461. adtsStream.on('log', function(log) {
  2462. logs.push(log);
  2463. });
  2464. var frameHeader = [
  2465. 0xff, 0xf1, // no CRC
  2466. 0x10, // AAC Main, 44.1KHz
  2467. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 including header
  2468. 0x00, // one AAC per ADTS frame
  2469. ];
  2470. adtsStream.push({
  2471. type: 'audio',
  2472. data: new Uint8Array(
  2473. []
  2474. // garbage
  2475. .concat([0x00, 0x00, 0x00])
  2476. // frame
  2477. .concat(frameHeader)
  2478. .concat([0x00, 0x01])
  2479. // garbage
  2480. .concat([0x00, 0x00, 0x00, 0x00, 0x00])
  2481. .concat(frameHeader)
  2482. .concat([0x00, 0x02])
  2483. // garbage
  2484. .concat([0x00, 0x00, 0x00, 0x00])
  2485. .concat(frameHeader)
  2486. .concat([0x00, 0x03])
  2487. .concat([0x00, 0x00, 0x00, 0x00])
  2488. )
  2489. });
  2490. assert.equal(frames.length, 3, 'generated three frames');
  2491. frames.forEach(function(frame, i) {
  2492. assert.deepEqual(
  2493. new Uint8Array(frame.data),
  2494. new Uint8Array([0x00, i + 1]),
  2495. 'extracted AAC frame'
  2496. );
  2497. assert.equal(frame.channelcount, 2, 'parsed channelcount');
  2498. assert.equal(frame.samplerate, 44100, 'parsed samplerate');
  2499. // Chrome only supports 8, 16, and 32 bit sample sizes. Assuming the
  2500. // default value of 16 in ISO/IEC 14496-12 AudioSampleEntry is
  2501. // acceptable.
  2502. assert.equal(frame.samplesize, 16, 'parsed samplesize');
  2503. });
  2504. assert.deepEqual(logs, [
  2505. {level: 'warn', message: 'adts skiping bytes 0 to 3 in frame 0 outside syncword'},
  2506. {level: 'warn', message: 'adts skiping bytes 12 to 17 in frame 1 outside syncword'},
  2507. {level: 'warn', message: 'adts skiping bytes 26 to 30 in frame 2 outside syncword'}
  2508. ], 'logged skipped data');
  2509. });
  2510. QUnit.test('parses across packets', function(assert) {
  2511. var frames = [];
  2512. adtsStream.on('data', function(frame) {
  2513. frames.push(frame);
  2514. });
  2515. adtsStream.push({
  2516. type: 'audio',
  2517. data: new Uint8Array([
  2518. 0xff, 0xf1, // no CRC
  2519. 0x10, // AAC Main, 44.1KHz
  2520. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2521. 0x00, // one AAC per ADTS frame
  2522. 0x12, 0x34 // AAC payload 1
  2523. ])
  2524. });
  2525. adtsStream.push({
  2526. type: 'audio',
  2527. data: new Uint8Array([
  2528. 0xff, 0xf1, // no CRC
  2529. 0x10, // AAC Main, 44.1KHz
  2530. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2531. 0x00, // one AAC per ADTS frame
  2532. 0x9a, 0xbc, // AAC payload 2
  2533. 0xde, 0xf0 // extra junk that should be ignored
  2534. ])
  2535. });
  2536. assert.equal(frames.length, 2, 'parsed two frames');
  2537. assert.deepEqual(new Uint8Array(frames[1].data),
  2538. new Uint8Array([0x9a, 0xbc]),
  2539. 'extracted the second AAC frame');
  2540. });
  2541. QUnit.test('parses frames segmented across packet', function(assert) {
  2542. var frames = [];
  2543. adtsStream.on('data', function(frame) {
  2544. frames.push(frame);
  2545. });
  2546. adtsStream.push({
  2547. type: 'audio',
  2548. data: new Uint8Array([
  2549. 0xff, 0xf1, // no CRC
  2550. 0x10, // AAC Main, 44.1KHz
  2551. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2552. 0x00, // one AAC per ADTS frame
  2553. 0x12 // incomplete AAC payload 1
  2554. ])
  2555. });
  2556. adtsStream.push({
  2557. type: 'audio',
  2558. data: new Uint8Array([
  2559. 0x34, // remainder of the previous frame's payload
  2560. 0xff, 0xf1, // no CRC
  2561. 0x10, // AAC Main, 44.1KHz
  2562. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2563. 0x00, // one AAC per ADTS frame
  2564. 0x9a, 0xbc, // AAC payload 2
  2565. 0xde, 0xf0 // extra junk that should be ignored
  2566. ])
  2567. });
  2568. assert.equal(frames.length, 2, 'parsed two frames');
  2569. assert.deepEqual(new Uint8Array(frames[0].data),
  2570. new Uint8Array([0x12, 0x34]),
  2571. 'extracted the first AAC frame');
  2572. assert.deepEqual(new Uint8Array(frames[1].data),
  2573. new Uint8Array([0x9a, 0xbc]),
  2574. 'extracted the second AAC frame');
  2575. });
  2576. QUnit.test('resyncs data in aac frames that contain garbage', function(assert) {
  2577. var frames = [];
  2578. adtsStream.on('data', function(frame) {
  2579. frames.push(frame);
  2580. });
  2581. adtsStream.push({
  2582. type: 'audio',
  2583. data: new Uint8Array([
  2584. 0x67, // garbage
  2585. 0xff, 0xf1, // no CRC
  2586. 0x10, // AAC Main, 44.1KHz
  2587. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2588. 0x00, // one AAC per ADTS frame
  2589. 0x9a, 0xbc, // AAC payload 1
  2590. 0xde, 0xf0 // extra junk that should be ignored
  2591. ])
  2592. });
  2593. adtsStream.push({
  2594. type: 'audio',
  2595. data: new Uint8Array([
  2596. 0x67, // garbage
  2597. 0xff, 0xf1, // no CRC
  2598. 0x10, // AAC Main, 44.1KHz
  2599. 0xbc, 0x01, 0x20, // 2 channels, frame length 9 bytes
  2600. 0x00, // one AAC per ADTS frame
  2601. 0x12, 0x34 // AAC payload 2
  2602. ])
  2603. });
  2604. assert.equal(frames.length, 2, 'parsed two frames');
  2605. assert.deepEqual(new Uint8Array(frames[0].data),
  2606. new Uint8Array([0x9a, 0xbc]),
  2607. 'extracted the first AAC frame');
  2608. assert.deepEqual(new Uint8Array(frames[1].data),
  2609. new Uint8Array([0x12, 0x34]),
  2610. 'extracted the second AAC frame');
  2611. });
  2612. QUnit.test('ignores audio "MPEG version" bit in adts header', function(assert) {
  2613. var frames = [];
  2614. adtsStream.on('data', function(frame) {
  2615. frames.push(frame);
  2616. });
  2617. adtsStream.push({
  2618. type: 'audio',
  2619. data: new Uint8Array([
  2620. 0xff, 0xf8, // MPEG-2 audio, CRC
  2621. 0x10, // AAC Main, 44.1KHz
  2622. 0xbc, 0x01, 0x60, // 2 channels, frame length 11 bytes
  2623. 0x00, // one AAC per ADTS frame
  2624. 0xfe, 0xdc, // "CRC"
  2625. 0x12, 0x34 // AAC payload 2
  2626. ])
  2627. });
  2628. assert.equal(frames.length, 1, 'parsed a frame');
  2629. assert.deepEqual(new Uint8Array(frames[0].data),
  2630. new Uint8Array([0x12, 0x34]),
  2631. 'skipped the CRC');
  2632. });
  2633. QUnit.test('skips CRC bytes', function(assert) {
  2634. var frames = [];
  2635. adtsStream.on('data', function(frame) {
  2636. frames.push(frame);
  2637. });
  2638. adtsStream.push({
  2639. type: 'audio',
  2640. data: new Uint8Array([
  2641. 0xff, 0xf0, // with CRC
  2642. 0x10, // AAC Main, 44.1KHz
  2643. 0xbc, 0x01, 0x60, // 2 channels, frame length 11 bytes
  2644. 0x00, // one AAC per ADTS frame
  2645. 0xfe, 0xdc, // "CRC"
  2646. 0x12, 0x34 // AAC payload 2
  2647. ])
  2648. });
  2649. assert.equal(frames.length, 1, 'parsed a frame');
  2650. assert.deepEqual(new Uint8Array(frames[0].data),
  2651. new Uint8Array([0x12, 0x34]),
  2652. 'skipped the CRC');
  2653. });
  2654. QUnit.module('AudioSegmentStream', {
  2655. beforeEach: function() {
  2656. var track = {
  2657. type: 'audio',
  2658. samplerate: 90e3 // no scaling
  2659. };
  2660. audioSegmentStream = new AudioSegmentStream(track);
  2661. audioSegmentStream.track = track;
  2662. audioSegmentStream.track.timelineStartInfo = {
  2663. dts: 111,
  2664. pts: 111,
  2665. baseMediaDecodeTime: 0
  2666. };
  2667. }
  2668. });
  2669. QUnit.test('fills audio gaps taking into account audio sample rate', function(assert) {
  2670. var
  2671. events = [],
  2672. boxes,
  2673. numSilentFrames,
  2674. videoGap = 0.29,
  2675. audioGap = 0.49,
  2676. expectedFillSeconds = audioGap - videoGap,
  2677. sampleRate = 44100,
  2678. frameDuration = Math.ceil(90e3 / (sampleRate / 1024)),
  2679. frameSeconds = clock.videoTsToSeconds(frameDuration),
  2680. audioBMDT,
  2681. offsetSeconds = clock.videoTsToSeconds(111),
  2682. startingAudioBMDT = clock.secondsToAudioTs(10 + audioGap - offsetSeconds, sampleRate);
  2683. audioSegmentStream.on('data', function(event) {
  2684. events.push(event);
  2685. });
  2686. audioSegmentStream.setAudioAppendStart(clock.secondsToVideoTs(10));
  2687. audioSegmentStream.setVideoBaseMediaDecodeTime(clock.secondsToVideoTs(10 + videoGap));
  2688. audioSegmentStream.push({
  2689. channelcount: 2,
  2690. samplerate: sampleRate,
  2691. pts: clock.secondsToVideoTs(10 + audioGap),
  2692. dts: clock.secondsToVideoTs(10 + audioGap),
  2693. data: new Uint8Array([1])
  2694. });
  2695. audioSegmentStream.flush();
  2696. numSilentFrames = Math.floor(expectedFillSeconds / frameSeconds);
  2697. assert.equal(events.length, 1, 'a data event fired');
  2698. assert.equal(events[0].track.samples.length, 1 + numSilentFrames, 'generated samples');
  2699. assert.equal(events[0].track.samples[0].size, 364, 'silent sample');
  2700. assert.equal(events[0].track.samples[7].size, 364, 'silent sample');
  2701. assert.equal(events[0].track.samples[8].size, 1, 'normal sample');
  2702. boxes = mp4.tools.inspect(events[0].boxes);
  2703. audioBMDT = boxes[0].boxes[1].boxes[1].baseMediaDecodeTime;
  2704. assert.equal(
  2705. audioBMDT,
  2706. // should always be rounded up so as not to overfill
  2707. Math.ceil(startingAudioBMDT -
  2708. clock.secondsToAudioTs(numSilentFrames * frameSeconds, sampleRate)),
  2709. 'filled the gap to the nearest frame');
  2710. assert.equal(
  2711. Math.floor(clock.audioTsToVideoTs(audioBMDT, sampleRate) -
  2712. clock.secondsToVideoTs(10 + videoGap)),
  2713. Math.floor(clock.secondsToVideoTs(expectedFillSeconds) % frameDuration -
  2714. clock.secondsToVideoTs(offsetSeconds)),
  2715. 'filled all but frame remainder between video start and audio start');
  2716. });
  2717. QUnit.test('fills audio gaps with existing frame if odd sample rate', function(assert) {
  2718. var
  2719. events = [],
  2720. boxes,
  2721. numSilentFrames,
  2722. videoGap = 0.29,
  2723. audioGap = 0.49,
  2724. expectedFillSeconds = audioGap - videoGap,
  2725. sampleRate = 90e3, // we don't have matching silent frames
  2726. frameDuration = Math.ceil(90e3 / (sampleRate / 1024)),
  2727. frameSeconds = clock.videoTsToSeconds(frameDuration),
  2728. audioBMDT,
  2729. offsetSeconds = clock.videoTsToSeconds(111),
  2730. startingAudioBMDT = clock.secondsToAudioTs(10 + audioGap - offsetSeconds, sampleRate);
  2731. audioSegmentStream.on('data', function(event) {
  2732. events.push(event);
  2733. });
  2734. audioSegmentStream.setAudioAppendStart(clock.secondsToVideoTs(10));
  2735. audioSegmentStream.setVideoBaseMediaDecodeTime(clock.secondsToVideoTs(10 + videoGap));
  2736. audioSegmentStream.push({
  2737. channelcount: 2,
  2738. samplerate: sampleRate,
  2739. pts: clock.secondsToVideoTs(10 + audioGap),
  2740. dts: clock.secondsToVideoTs(10 + audioGap),
  2741. data: new Uint8Array([1])
  2742. });
  2743. audioSegmentStream.flush();
  2744. numSilentFrames = Math.floor(expectedFillSeconds / frameSeconds);
  2745. assert.equal(events.length, 1, 'a data event fired');
  2746. assert.equal(events[0].track.samples.length, 1 + numSilentFrames, 'generated samples');
  2747. assert.equal(events[0].track.samples[0].size, 1, 'copied sample');
  2748. assert.equal(events[0].track.samples[7].size, 1, 'copied sample');
  2749. assert.equal(events[0].track.samples[8].size, 1, 'normal sample');
  2750. boxes = mp4.tools.inspect(events[0].boxes);
  2751. audioBMDT = boxes[0].boxes[1].boxes[1].baseMediaDecodeTime;
  2752. assert.equal(
  2753. audioBMDT,
  2754. // should always be rounded up so as not to overfill
  2755. Math.ceil(startingAudioBMDT -
  2756. clock.secondsToAudioTs(numSilentFrames * frameSeconds, sampleRate)),
  2757. 'filled the gap to the nearest frame');
  2758. assert.equal(
  2759. Math.floor(clock.audioTsToVideoTs(audioBMDT, sampleRate) -
  2760. clock.secondsToVideoTs(10 + videoGap)),
  2761. Math.floor(clock.secondsToVideoTs(expectedFillSeconds) % frameDuration -
  2762. clock.secondsToVideoTs(offsetSeconds)),
  2763. 'filled all but frame remainder between video start and audio start');
  2764. });
  2765. QUnit.test('fills audio gaps with smaller of audio gap and audio-video gap', function(assert) {
  2766. var
  2767. events = [],
  2768. boxes,
  2769. offsetSeconds = clock.videoTsToSeconds(111),
  2770. videoGap = 0.29,
  2771. sampleRate = 44100,
  2772. frameDuration = Math.ceil(90e3 / (sampleRate / 1024)),
  2773. frameSeconds = clock.videoTsToSeconds(frameDuration),
  2774. // audio gap smaller, should be used as fill
  2775. numSilentFrames = 1,
  2776. // buffer for imprecise numbers
  2777. audioGap = frameSeconds + offsetSeconds + 0.001,
  2778. oldAudioEnd = 10.5,
  2779. audioBMDT;
  2780. audioSegmentStream.on('data', function(event) {
  2781. events.push(event);
  2782. });
  2783. audioSegmentStream.setAudioAppendStart(clock.secondsToVideoTs(oldAudioEnd));
  2784. audioSegmentStream.setVideoBaseMediaDecodeTime(clock.secondsToVideoTs(10 + videoGap));
  2785. audioSegmentStream.push({
  2786. channelcount: 2,
  2787. samplerate: sampleRate,
  2788. pts: clock.secondsToVideoTs(oldAudioEnd + audioGap),
  2789. dts: clock.secondsToVideoTs(oldAudioEnd + audioGap),
  2790. data: new Uint8Array([1])
  2791. });
  2792. audioSegmentStream.flush();
  2793. assert.equal(events.length, 1, 'a data event fired');
  2794. assert.equal(events[0].track.samples.length, 1 + numSilentFrames, 'generated samples');
  2795. assert.equal(events[0].track.samples[0].size, 364, 'silent sample');
  2796. assert.equal(events[0].track.samples[1].size, 1, 'normal sample');
  2797. boxes = mp4.tools.inspect(events[0].boxes);
  2798. audioBMDT = boxes[0].boxes[1].boxes[1].baseMediaDecodeTime;
  2799. assert.equal(
  2800. Math.floor(clock.secondsToVideoTs(oldAudioEnd + audioGap) -
  2801. clock.audioTsToVideoTs(audioBMDT, sampleRate) -
  2802. clock.secondsToVideoTs(offsetSeconds)),
  2803. Math.floor(frameDuration + 0.001),
  2804. 'filled length of audio gap only');
  2805. });
  2806. QUnit.test('does not fill audio gaps if no audio append start time', function(assert) {
  2807. var
  2808. events = [],
  2809. boxes,
  2810. videoGap = 0.29,
  2811. audioGap = 0.49;
  2812. audioSegmentStream.on('data', function(event) {
  2813. events.push(event);
  2814. });
  2815. audioSegmentStream.setVideoBaseMediaDecodeTime((10 + videoGap) * 90e3);
  2816. audioSegmentStream.push({
  2817. channelcount: 2,
  2818. samplerate: 90e3,
  2819. pts: (10 + audioGap) * 90e3,
  2820. dts: (10 + audioGap) * 90e3,
  2821. data: new Uint8Array([1])
  2822. });
  2823. audioSegmentStream.flush();
  2824. assert.equal(events.length, 1, 'a data event fired');
  2825. assert.equal(events[0].track.samples.length, 1, 'generated samples');
  2826. assert.equal(events[0].track.samples[0].size, 1, 'normal sample');
  2827. boxes = mp4.tools.inspect(events[0].boxes);
  2828. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  2829. (10 + audioGap) * 90e3 - 111,
  2830. 'did not fill gap');
  2831. });
  2832. QUnit.test('does not fill audio gap if no video base media decode time', function(assert) {
  2833. var
  2834. events = [],
  2835. boxes,
  2836. audioGap = 0.49;
  2837. audioSegmentStream.on('data', function(event) {
  2838. events.push(event);
  2839. });
  2840. audioSegmentStream.setAudioAppendStart(10 * 90e3);
  2841. audioSegmentStream.push({
  2842. channelcount: 2,
  2843. samplerate: 90e3,
  2844. pts: (10 + audioGap) * 90e3,
  2845. dts: (10 + audioGap) * 90e3,
  2846. data: new Uint8Array([1])
  2847. });
  2848. audioSegmentStream.flush();
  2849. assert.equal(events.length, 1, 'a data event fired');
  2850. assert.equal(events[0].track.samples.length, 1, 'generated samples');
  2851. assert.equal(events[0].track.samples[0].size, 1, 'normal sample');
  2852. boxes = mp4.tools.inspect(events[0].boxes);
  2853. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  2854. (10 + audioGap) * 90e3 - 111,
  2855. 'did not fill the gap');
  2856. });
  2857. QUnit.test('does not fill audio gaps greater than a half second', function(assert) {
  2858. var
  2859. events = [],
  2860. boxes,
  2861. videoGap = 0.01,
  2862. audioGap = videoGap + 0.51;
  2863. audioSegmentStream.on('data', function(event) {
  2864. events.push(event);
  2865. });
  2866. audioSegmentStream.setAudioAppendStart(10 * 90e3);
  2867. audioSegmentStream.setVideoBaseMediaDecodeTime((10 + videoGap) * 90e3);
  2868. audioSegmentStream.push({
  2869. channelcount: 2,
  2870. samplerate: 90e3,
  2871. pts: (10 + audioGap) * 90e3,
  2872. dts: (10 + audioGap) * 90e3,
  2873. data: new Uint8Array([1])
  2874. });
  2875. audioSegmentStream.flush();
  2876. assert.equal(events.length, 1, 'a data event fired');
  2877. assert.equal(events[0].track.samples.length, 1, 'generated samples');
  2878. assert.equal(events[0].track.samples[0].size, 1, 'normal sample');
  2879. boxes = mp4.tools.inspect(events[0].boxes);
  2880. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  2881. (10 + audioGap) * 90e3 - 111,
  2882. 'did not fill gap');
  2883. });
  2884. QUnit.test('does not fill audio gaps smaller than a frame duration', function(assert) {
  2885. var
  2886. events = [],
  2887. boxes,
  2888. offsetSeconds = clock.videoTsToSeconds(111),
  2889. // audio gap small enough that it shouldn't be filled
  2890. audioGap = 0.001,
  2891. newVideoStart = 10,
  2892. oldAudioEnd = 10.3,
  2893. newAudioStart = oldAudioEnd + audioGap + offsetSeconds;
  2894. audioSegmentStream.on('data', function(event) {
  2895. events.push(event);
  2896. });
  2897. // the real audio gap is tiny, but the gap between the new video and audio segments
  2898. // would be large enough to fill
  2899. audioSegmentStream.setAudioAppendStart(clock.secondsToVideoTs(oldAudioEnd));
  2900. audioSegmentStream.setVideoBaseMediaDecodeTime(clock.secondsToVideoTs(newVideoStart));
  2901. audioSegmentStream.push({
  2902. channelcount: 2,
  2903. samplerate: 90e3,
  2904. pts: clock.secondsToVideoTs(newAudioStart),
  2905. dts: clock.secondsToVideoTs(newAudioStart),
  2906. data: new Uint8Array([1])
  2907. });
  2908. audioSegmentStream.flush();
  2909. assert.equal(events.length, 1, 'a data event fired');
  2910. assert.equal(events[0].track.samples.length, 1, 'generated samples');
  2911. assert.equal(events[0].track.samples[0].size, 1, 'normal sample');
  2912. boxes = mp4.tools.inspect(events[0].boxes);
  2913. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  2914. clock.secondsToVideoTs(newAudioStart - offsetSeconds),
  2915. 'did not fill gap');
  2916. });
  2917. QUnit.test('ensures baseMediaDecodeTime for audio is not negative', function(assert) {
  2918. var events = [], boxes;
  2919. audioSegmentStream.on('data', function(event) {
  2920. events.push(event);
  2921. });
  2922. audioSegmentStream.track.timelineStartInfo.baseMediaDecodeTime = 10;
  2923. audioSegmentStream.setEarliestDts(101);
  2924. audioSegmentStream.push({
  2925. channelcount: 2,
  2926. samplerate: 90e3,
  2927. pts: 111 - 10 - 1, // before the earliest DTS
  2928. dts: 111 - 10 - 1, // before the earliest DTS
  2929. data: new Uint8Array([0])
  2930. });
  2931. audioSegmentStream.push({
  2932. channelcount: 2,
  2933. samplerate: 90e3,
  2934. pts: 111 - 10 + 2, // after the earliest DTS
  2935. dts: 111 - 10 + 2, // after the earliest DTS
  2936. data: new Uint8Array([1])
  2937. });
  2938. audioSegmentStream.flush();
  2939. assert.equal(events.length, 1, 'a data event fired');
  2940. assert.equal(events[0].track.samples.length, 1, 'generated only one sample');
  2941. boxes = mp4.tools.inspect(events[0].boxes);
  2942. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime, 2, 'kept the later sample');
  2943. });
  2944. QUnit.test('audio track metadata takes on the value of the last metadata seen', function(assert) {
  2945. var events = [];
  2946. audioSegmentStream.on('data', function(event) {
  2947. events.push(event);
  2948. });
  2949. audioSegmentStream.push({
  2950. channelcount: 2,
  2951. samplerate: 90e3,
  2952. pts: 100,
  2953. dts: 100,
  2954. data: new Uint8Array([0])
  2955. });
  2956. audioSegmentStream.push({
  2957. channelcount: 4,
  2958. samplerate: 10000,
  2959. pts: 111,
  2960. dts: 111,
  2961. data: new Uint8Array([1])
  2962. });
  2963. audioSegmentStream.flush();
  2964. assert.equal(events.length, 1, 'a data event fired');
  2965. assert.equal(events[0].track.samples.length, 2, 'generated two samples');
  2966. assert.equal(events[0].track.samplerate, 10000, 'kept the later samplerate');
  2967. assert.equal(events[0].track.channelcount, 4, 'kept the later channelcount');
  2968. });
  2969. QUnit.test('audio segment stream triggers segmentTimingInfo with timing info',
  2970. function(assert) {
  2971. var
  2972. events = [],
  2973. samplerate = 48000,
  2974. baseMediaDecodeTimeInVideoClock = 30,
  2975. audioFrameDurationInVideoClock = 90000 * 1024 / samplerate,
  2976. firstFrame = {
  2977. channelcount: 2,
  2978. samplerate: samplerate,
  2979. pts: 112,
  2980. dts: 111,
  2981. data: new Uint8Array([0])
  2982. },
  2983. secondFrame = {
  2984. channelcount: 2,
  2985. samplerate: samplerate,
  2986. pts: firstFrame.pts + audioFrameDurationInVideoClock,
  2987. dts: firstFrame.dts + audioFrameDurationInVideoClock,
  2988. data: new Uint8Array([1])
  2989. };
  2990. audioSegmentStream.on('segmentTimingInfo', function(event) {
  2991. events.push(event);
  2992. });
  2993. audioSegmentStream.track.timelineStartInfo.baseMediaDecodeTime =
  2994. baseMediaDecodeTimeInVideoClock;
  2995. audioSegmentStream.push(firstFrame);
  2996. audioSegmentStream.push(secondFrame);
  2997. audioSegmentStream.flush();
  2998. assert.equal(events.length, 1, 'a segmentTimingInfo event was fired');
  2999. assert.deepEqual(
  3000. events[0],
  3001. {
  3002. start: {
  3003. dts: baseMediaDecodeTimeInVideoClock,
  3004. pts: baseMediaDecodeTimeInVideoClock + (firstFrame.pts - firstFrame.dts)
  3005. },
  3006. end: {
  3007. dts: baseMediaDecodeTimeInVideoClock + (secondFrame.dts - firstFrame.dts) +
  3008. audioFrameDurationInVideoClock,
  3009. pts: baseMediaDecodeTimeInVideoClock + (secondFrame.pts - firstFrame.pts) +
  3010. audioFrameDurationInVideoClock
  3011. },
  3012. prependedContentDuration: 0,
  3013. baseMediaDecodeTime: baseMediaDecodeTimeInVideoClock
  3014. },
  3015. 'has correct segmentTimingInfo'
  3016. );
  3017. });
  3018. QUnit.module('Transmuxer - options');
  3019. QUnit.test('no options creates combined output', function(assert) {
  3020. var
  3021. segments = [],
  3022. boxes,
  3023. initSegment,
  3024. transmuxer = new Transmuxer();
  3025. transmuxer.on('data', function(segment) {
  3026. segments.push(segment);
  3027. });
  3028. transmuxer.push(packetize(PAT));
  3029. transmuxer.push(packetize(generatePMT({
  3030. hasVideo: true,
  3031. hasAudio: true
  3032. })));
  3033. transmuxer.push(packetize(audioPes([
  3034. 0x19, 0x47
  3035. ], true)));
  3036. transmuxer.push(packetize(videoPes([
  3037. 0x09, 0x01 // access_unit_delimiter_rbsp
  3038. ], true)));
  3039. transmuxer.push(packetize(videoPes([
  3040. 0x08, 0x01 // pic_parameter_set_rbsp
  3041. ], true)));
  3042. transmuxer.push(packetize(videoPes([
  3043. 0x07, // seq_parameter_set_rbsp
  3044. 0x27, 0x42, 0xe0, 0x0b,
  3045. 0xa9, 0x18, 0x60, 0x9d,
  3046. 0x80, 0x53, 0x06, 0x01,
  3047. 0x06, 0xb6, 0xc2, 0xb5,
  3048. 0xef, 0x7c, 0x04
  3049. ], false)));
  3050. transmuxer.push(packetize(videoPes([
  3051. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3052. ], true)));
  3053. transmuxer.flush();
  3054. assert.equal(segments.length, 1, 'generated a combined video and audio segment');
  3055. assert.equal(segments[0].type, 'combined', 'combined is the segment type');
  3056. boxes = mp4.tools.inspect(segments[0].data);
  3057. initSegment = mp4.tools.inspect(segments[0].initSegment);
  3058. assert.equal(initSegment.length, 2, 'generated 2 init segment boxes');
  3059. assert.equal('ftyp', initSegment[0].type, 'generated an ftyp box');
  3060. assert.equal('moov', initSegment[1].type, 'generated a single moov box');
  3061. assert.equal(boxes.length, 4, 'generated 4 top-level boxes');
  3062. assert.equal('moof', boxes[0].type, 'generated a first moof box');
  3063. assert.equal('mdat', boxes[1].type, 'generated a first mdat box');
  3064. assert.equal('moof', boxes[2].type, 'generated a second moof box');
  3065. assert.equal('mdat', boxes[3].type, 'generated a second mdat box');
  3066. });
  3067. QUnit.test('first sequence number option is used in mfhd box', function(assert) {
  3068. var
  3069. segments = [],
  3070. mfhds = [],
  3071. boxes,
  3072. transmuxer = new Transmuxer({ firstSequenceNumber: 10 });
  3073. transmuxer.on('data', function(segment) {
  3074. segments.push(segment);
  3075. });
  3076. transmuxer.push(packetize(PAT));
  3077. transmuxer.push(packetize(generatePMT({
  3078. hasVideo: true,
  3079. hasAudio: true
  3080. })));
  3081. transmuxer.push(packetize(audioPes([
  3082. 0x19, 0x47
  3083. ], true)));
  3084. transmuxer.push(packetize(videoPes([
  3085. 0x09, 0x01 // access_unit_delimiter_rbsp
  3086. ], true)));
  3087. transmuxer.push(packetize(videoPes([
  3088. 0x08, 0x01 // pic_parameter_set_rbsp
  3089. ], true)));
  3090. transmuxer.push(packetize(videoPes([
  3091. 0x07, // seq_parameter_set_rbsp
  3092. 0x27, 0x42, 0xe0, 0x0b,
  3093. 0xa9, 0x18, 0x60, 0x9d,
  3094. 0x80, 0x53, 0x06, 0x01,
  3095. 0x06, 0xb6, 0xc2, 0xb5,
  3096. 0xef, 0x7c, 0x04
  3097. ], false)));
  3098. transmuxer.push(packetize(videoPes([
  3099. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3100. ], true)));
  3101. transmuxer.flush();
  3102. assert.equal(segments.length, 1, 'generated a combined video and audio segment');
  3103. assert.equal(segments[0].type, 'combined', 'combined is the segment type');
  3104. boxes = mp4.tools.inspect(segments[0].data);
  3105. boxes.forEach(function(box) {
  3106. if (box.type === 'moof') {
  3107. box.boxes.forEach(function(moofBox) {
  3108. if (moofBox.type === 'mfhd') {
  3109. mfhds.push(moofBox);
  3110. }
  3111. });
  3112. }
  3113. });
  3114. assert.equal(mfhds.length, 2, 'muxed output has two mfhds');
  3115. assert.equal(mfhds[0].sequenceNumber, 10, 'first mfhd sequence starts at 10');
  3116. assert.equal(mfhds[1].sequenceNumber, 10, 'second mfhd sequence starts at 10');
  3117. });
  3118. QUnit.test('can specify that we want to generate separate audio and video segments', function(assert) {
  3119. var
  3120. segments = [],
  3121. segmentLengthOnDone,
  3122. boxes,
  3123. initSegment,
  3124. transmuxer = new Transmuxer({remux: false});
  3125. transmuxer.on('data', function(segment) {
  3126. segments.push(segment);
  3127. });
  3128. transmuxer.on('done', function(segment) {
  3129. if (!segmentLengthOnDone) {
  3130. segmentLengthOnDone = segments.length;
  3131. }
  3132. });
  3133. transmuxer.push(packetize(PAT));
  3134. transmuxer.push(packetize(generatePMT({
  3135. hasVideo: true,
  3136. hasAudio: true
  3137. })));
  3138. transmuxer.push(packetize(audioPes([
  3139. 0x19, 0x47
  3140. ], true)));
  3141. transmuxer.push(packetize(videoPes([
  3142. 0x09, 0x01 // access_unit_delimiter_rbsp
  3143. ], true)));
  3144. transmuxer.push(packetize(videoPes([
  3145. 0x08, 0x01 // pic_parameter_set_rbsp
  3146. ], true)));
  3147. transmuxer.push(packetize(videoPes([
  3148. 0x07, // seq_parameter_set_rbsp
  3149. 0x27, 0x42, 0xe0, 0x0b,
  3150. 0xa9, 0x18, 0x60, 0x9d,
  3151. 0x80, 0x53, 0x06, 0x01,
  3152. 0x06, 0xb6, 0xc2, 0xb5,
  3153. 0xef, 0x7c, 0x04
  3154. ], false)));
  3155. transmuxer.push(packetize(videoPes([
  3156. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3157. ], true)));
  3158. transmuxer.flush();
  3159. assert.equal(segmentLengthOnDone, 2, 'emitted both segments before triggering done');
  3160. assert.equal(segments.length, 2, 'generated a video and an audio segment');
  3161. assert.ok(segments[0].type === 'video' || segments[1].type === 'video', 'one segment is video');
  3162. assert.ok(segments[0].type === 'audio' || segments[1].type === 'audio', 'one segment is audio');
  3163. boxes = mp4.tools.inspect(segments[0].data);
  3164. initSegment = mp4.tools.inspect(segments[0].initSegment);
  3165. assert.equal(initSegment.length, 2, 'generated 2 top-level initSegment boxes');
  3166. assert.equal(boxes.length, 2, 'generated 2 top-level boxes');
  3167. assert.equal('ftyp', initSegment[0].type, 'generated an ftyp box');
  3168. assert.equal('moov', initSegment[1].type, 'generated a moov box');
  3169. assert.equal('moof', boxes[0].type, 'generated a moof box');
  3170. assert.equal('mdat', boxes[1].type, 'generated a mdat box');
  3171. boxes = mp4.tools.inspect(segments[1].data);
  3172. initSegment = mp4.tools.inspect(segments[1].initSegment);
  3173. assert.equal(initSegment.length, 2, 'generated 2 top-level initSegment boxes');
  3174. assert.equal(boxes.length, 2, 'generated 2 top-level boxes');
  3175. assert.equal('ftyp', initSegment[0].type, 'generated an ftyp box');
  3176. assert.equal('moov', initSegment[1].type, 'generated a moov box');
  3177. assert.equal('moof', boxes[0].type, 'generated a moof box');
  3178. assert.equal('mdat', boxes[1].type, 'generated a mdat box');
  3179. });
  3180. QUnit.test('adjusts caption and ID3 times when configured to adjust timestamps', function(assert) {
  3181. var transmuxer = new Transmuxer({ keepOriginalTimestamps: false });
  3182. var
  3183. segments = [],
  3184. captions = [];
  3185. transmuxer.on('data', function(segment) {
  3186. captions = captions.concat(segment.captions);
  3187. segments.push(segment);
  3188. });
  3189. transmuxer.push(packetize(PAT));
  3190. transmuxer.push(packetize(generatePMT({
  3191. hasVideo: true,
  3192. hasAudio: true
  3193. })));
  3194. transmuxer.push(packetize(audioPes([
  3195. 0x19, 0x47
  3196. ], true, 90000)));
  3197. transmuxer.push(packetize(videoPes([
  3198. 0x09, 0x01 // access_unit_delimiter_rbsp
  3199. ], true, 90000)));
  3200. transmuxer.push(packetize(videoPes([
  3201. 0x08, 0x01 // pic_parameter_set_rbsp
  3202. ], true, 90002)));
  3203. transmuxer.push(packetize(videoPes([
  3204. 0x07, // seq_parameter_set_rbsp
  3205. 0x27, 0x42, 0xe0, 0x0b,
  3206. 0xa9, 0x18, 0x60, 0x9d,
  3207. 0x80, 0x53, 0x06, 0x01,
  3208. 0x06, 0xb6, 0xc2, 0xb5,
  3209. 0xef, 0x7c, 0x04
  3210. ], false, 90002)));
  3211. transmuxer.push(packetize(videoPes([
  3212. 0x06, // sei_rbsp
  3213. 0x04, 0x29, 0xb5, 0x00,
  3214. 0x31, 0x47, 0x41, 0x39,
  3215. 0x34, 0x03, 0x52, 0xff,
  3216. 0xfc, 0x94, 0xae, 0xfc,
  3217. 0x94, 0x20, 0xfc, 0x91,
  3218. 0x40, 0xfc, 0xb0, 0xb0,
  3219. 0xfc, 0xba, 0xb0, 0xfc,
  3220. 0xb0, 0xba, 0xfc, 0xb0,
  3221. 0xb0, 0xfc, 0x94, 0x2f,
  3222. 0xfc, 0x94, 0x2f, 0xfc,
  3223. 0x94, 0x2f, 0xff, 0x80,
  3224. 0x00 // has an extra End Of Caption, so start and end times will be the same
  3225. ], true, 90002)));
  3226. transmuxer.push(packetize(videoPes([
  3227. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3228. ], true, 90004)));
  3229. transmuxer.flush();
  3230. assert.equal(segments.length, 1, 'generated a combined video and audio segment');
  3231. assert.equal(segments[0].type, 'combined', 'combined is the segment type');
  3232. assert.equal(captions.length, 1, 'got one caption');
  3233. assert.equal(captions[0].startPts, 90004, 'original pts value intact');
  3234. assert.equal(captions[0].startTime, (90004 - 90002) / 90e3, 'caption start time are based on original timeline');
  3235. assert.equal(captions[0].endTime, (90004 - 90002) / 90e3, 'caption end time are based on original timeline');
  3236. });
  3237. [
  3238. {options: {keepOriginalTimestamps: false}},
  3239. {options: {keepOriginalTimestamps: true}},
  3240. {options: {keepOriginalTimestamps: false, baseMediaDecodeTime: 15000}},
  3241. {options: {keepOriginalTimestamps: true, baseMediaDecodeTime: 15000}},
  3242. {options: {keepOriginalTimestamps: false}, baseMediaSetter: 15000},
  3243. {options: {keepOriginalTimestamps: true}, baseMediaSetter: 15000}
  3244. ].forEach(function(test) {
  3245. var createTransmuxer = function() {
  3246. var transmuxer = new Transmuxer(test.options);
  3247. if (test.baseMediaSetter) {
  3248. transmuxer.setBaseMediaDecodeTime(test.baseMediaSetter);
  3249. }
  3250. return transmuxer;
  3251. };
  3252. var name = '';
  3253. Object.keys(test.options).forEach(function(optionName) {
  3254. name += '' + optionName + ' ' + test.options[optionName] + ' ';
  3255. });
  3256. if (test.baseMediaSetter) {
  3257. name += 'baseMediaDecodeTime setter ' + test.baseMediaSetter;
  3258. }
  3259. QUnit.test('Audio frames after video not trimmed, ' + name, function(assert) {
  3260. var
  3261. segments = [],
  3262. earliestDts = 15000,
  3263. transmuxer = createTransmuxer();
  3264. transmuxer.on('data', function(segment) {
  3265. segments.push(segment);
  3266. });
  3267. // the following transmuxer pushes add tiny video and
  3268. // audio data to the transmuxer. When we add the data
  3269. // we also set the pts/dts time so that audio should
  3270. // not be trimmed.
  3271. transmuxer.push(packetize(PAT));
  3272. transmuxer.push(packetize(generatePMT({
  3273. hasVideo: true,
  3274. hasAudio: true
  3275. })));
  3276. transmuxer.push(packetize(audioPes([
  3277. 0x19, 0x47
  3278. ], true, earliestDts + 1)));
  3279. transmuxer.push(packetize(videoPes([
  3280. 0x09, 0x01 // access_unit_delimiter_rbsp
  3281. ], true, earliestDts)));
  3282. transmuxer.push(packetize(videoPes([
  3283. 0x08, 0x01 // pic_parameter_set_rbsp
  3284. ], true, earliestDts)));
  3285. transmuxer.push(packetize(videoPes([
  3286. 0x07, // seq_parameter_set_rbsp
  3287. 0x27, 0x42, 0xe0, 0x0b,
  3288. 0xa9, 0x18, 0x60, 0x9d,
  3289. 0x80, 0x53, 0x06, 0x01,
  3290. 0x06, 0xb6, 0xc2, 0xb5,
  3291. 0xef, 0x7c, 0x04
  3292. ], false, earliestDts)));
  3293. transmuxer.push(packetize(videoPes([
  3294. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3295. ], true, earliestDts)));
  3296. transmuxer.flush();
  3297. assert.equal(segments.length, 1, 'generated a combined segment');
  3298. // The audio frame is 10 bytes. The full data is 305 bytes without anything
  3299. // trimmed. If the audio frame was trimmed this will be 295 bytes.
  3300. assert.equal(segments[0].data.length, 305, 'trimmed audio frame');
  3301. });
  3302. QUnit.test('Audio frames trimmed before video, ' + name, function(assert) {
  3303. var
  3304. segments = [],
  3305. earliestDts = 15000,
  3306. baseTime = test.options.baseMediaDecodeTime || test.baseMediaSetter || 0,
  3307. transmuxer = createTransmuxer();
  3308. transmuxer.on('data', function(segment) {
  3309. segments.push(segment);
  3310. });
  3311. // the following transmuxer pushes add tiny video and
  3312. // audio data to the transmuxer. When we add the data
  3313. // we also set the pts/dts time so that audio should
  3314. // be trimmed.
  3315. transmuxer.push(packetize(PAT));
  3316. transmuxer.push(packetize(generatePMT({
  3317. hasVideo: true,
  3318. hasAudio: true
  3319. })));
  3320. transmuxer.push(packetize(audioPes([
  3321. 0x19, 0x47
  3322. ], true, earliestDts - baseTime - 1)));
  3323. transmuxer.push(packetize(videoPes([
  3324. 0x09, 0x01 // access_unit_delimiter_rbsp
  3325. ], true, earliestDts)));
  3326. transmuxer.push(packetize(videoPes([
  3327. 0x08, 0x01 // pic_parameter_set_rbsp
  3328. ], true, earliestDts)));
  3329. transmuxer.push(packetize(videoPes([
  3330. 0x07, // seq_parameter_set_rbsp
  3331. 0x27, 0x42, 0xe0, 0x0b,
  3332. 0xa9, 0x18, 0x60, 0x9d,
  3333. 0x80, 0x53, 0x06, 0x01,
  3334. 0x06, 0xb6, 0xc2, 0xb5,
  3335. 0xef, 0x7c, 0x04
  3336. ], false, earliestDts)));
  3337. transmuxer.push(packetize(videoPes([
  3338. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3339. ], true, earliestDts)));
  3340. transmuxer.flush();
  3341. assert.equal(segments.length, 1, 'generated a combined segment');
  3342. // The audio frame is 10 bytes. The full data is 305 bytes without anything
  3343. // trimmed. If the audio frame was trimmed this will be 295 bytes.
  3344. if (test.options.keepOriginalTimestamps && !baseTime) {
  3345. assert.equal(segments[0].data.length, 305, 'did not trim audio frame');
  3346. } else {
  3347. assert.equal(segments[0].data.length, 295, 'trimmed audio frame');
  3348. }
  3349. });
  3350. });
  3351. QUnit.test("doesn't adjust caption and ID3 times when configured to keep original timestamps", function(assert) {
  3352. var transmuxer = new Transmuxer({ keepOriginalTimestamps: true });
  3353. var
  3354. segments = [],
  3355. captions = [];
  3356. transmuxer.on('data', function(segment) {
  3357. captions = captions.concat(segment.captions);
  3358. segments.push(segment);
  3359. });
  3360. transmuxer.push(packetize(PAT));
  3361. transmuxer.push(packetize(generatePMT({
  3362. hasVideo: true,
  3363. hasAudio: true
  3364. })));
  3365. transmuxer.push(packetize(audioPes([
  3366. 0x19, 0x47
  3367. ], true, 90000)));
  3368. transmuxer.push(packetize(videoPes([
  3369. 0x09, 0x01 // access_unit_delimiter_rbsp
  3370. ], true, 90000)));
  3371. transmuxer.push(packetize(videoPes([
  3372. 0x08, 0x01 // pic_parameter_set_rbsp
  3373. ], true, 90002)));
  3374. transmuxer.push(packetize(videoPes([
  3375. 0x07, // seq_parameter_set_rbsp
  3376. 0x27, 0x42, 0xe0, 0x0b,
  3377. 0xa9, 0x18, 0x60, 0x9d,
  3378. 0x80, 0x53, 0x06, 0x01,
  3379. 0x06, 0xb6, 0xc2, 0xb5,
  3380. 0xef, 0x7c, 0x04
  3381. ], false, 90002)));
  3382. transmuxer.push(packetize(videoPes([
  3383. 0x06, // sei_rbsp
  3384. 0x04, 0x29, 0xb5, 0x00,
  3385. 0x31, 0x47, 0x41, 0x39,
  3386. 0x34, 0x03, 0x52, 0xff,
  3387. 0xfc, 0x94, 0xae, 0xfc,
  3388. 0x94, 0x20, 0xfc, 0x91,
  3389. 0x40, 0xfc, 0xb0, 0xb0,
  3390. 0xfc, 0xba, 0xb0, 0xfc,
  3391. 0xb0, 0xba, 0xfc, 0xb0,
  3392. 0xb0, 0xfc, 0x94, 0x2f,
  3393. 0xfc, 0x94, 0x2f, 0xfc,
  3394. 0x94, 0x2f, 0xff, 0x80,
  3395. 0x00 // has an extra End Of Caption, so start and end times will be the same
  3396. ], true, 90002)));
  3397. transmuxer.push(packetize(videoPes([
  3398. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3399. ], true, 90004)));
  3400. transmuxer.flush();
  3401. assert.equal(segments.length, 1, 'generated a combined video and audio segment');
  3402. assert.equal(segments[0].type, 'combined', 'combined is the segment type');
  3403. assert.equal(captions.length, 1, 'got one caption');
  3404. assert.equal(captions[0].startPts, 90004, 'original pts value intact');
  3405. assert.equal(captions[0].startTime, 90004 / 90e3, 'caption start time are based on original timeline');
  3406. assert.equal(captions[0].endTime, 90004 / 90e3, 'caption end time are based on original timeline');
  3407. });
  3408. QUnit.module('MP4 - Transmuxer', {
  3409. beforeEach: function() {
  3410. transmuxer = new Transmuxer();
  3411. }
  3412. });
  3413. QUnit.test('generates a video init segment', function(assert) {
  3414. var segments = [], boxes;
  3415. transmuxer.on('data', function(segment) {
  3416. segments.push(segment);
  3417. });
  3418. transmuxer.push(packetize(PAT));
  3419. transmuxer.push(packetize(generatePMT({
  3420. hasVideo: true
  3421. })));
  3422. transmuxer.push(packetize(videoPes([
  3423. 0x09, 0x01 // access_unit_delimiter_rbsp
  3424. ], true)));
  3425. transmuxer.push(packetize(videoPes([
  3426. 0x08, 0x01 // pic_parameter_set_rbsp
  3427. ], true)));
  3428. transmuxer.push(packetize(videoPes([
  3429. 0x07, // seq_parameter_set_rbsp
  3430. 0x27, 0x42, 0xe0, 0x0b,
  3431. 0xa9, 0x18, 0x60, 0x9d,
  3432. 0x87, 0x53, 0x06, 0x01,
  3433. 0x06, 0xb6, 0xc2, 0xb5,
  3434. 0xef, 0x7c, 0x04
  3435. ], false)));
  3436. transmuxer.push(packetize(videoPes([
  3437. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3438. ], true)));
  3439. transmuxer.flush();
  3440. assert.equal(segments.length, 1, 'generated a segment');
  3441. assert.ok(segments[0].data, 'wrote data in the init segment');
  3442. assert.equal(segments[0].type, 'video', 'video is the segment type');
  3443. assert.ok(segments[0].info, 'video info is alongside video segments/bytes');
  3444. mp4VideoProperties.forEach(function(prop) {
  3445. assert.ok(segments[0].info[prop], 'video info has ' + prop);
  3446. });
  3447. boxes = mp4.tools.inspect(segments[0].initSegment);
  3448. assert.equal('ftyp', boxes[0].type, 'generated an ftyp box');
  3449. assert.equal('moov', boxes[1].type, 'generated a moov box');
  3450. });
  3451. QUnit.test('transmuxer triggers video timing info event on flush', function(assert) {
  3452. var videoSegmentTimingInfoArr = [];
  3453. transmuxer.on('videoSegmentTimingInfo', function(videoSegmentTimingInfo) {
  3454. videoSegmentTimingInfoArr.push(videoSegmentTimingInfo);
  3455. });
  3456. transmuxer.push(packetize(PAT));
  3457. transmuxer.push(packetize(generatePMT({
  3458. hasVideo: true
  3459. })));
  3460. transmuxer.push(packetize(videoPes([
  3461. 0x09, 0x01 // access_unit_delimiter_rbsp
  3462. ], true)));
  3463. transmuxer.push(packetize(videoPes([
  3464. 0x08, 0x01 // pic_parameter_set_rbsp
  3465. ], true)));
  3466. transmuxer.push(packetize(videoPes([
  3467. 0x07, // seq_parameter_set_rbsp
  3468. 0x27, 0x42, 0xe0, 0x0b,
  3469. 0xa9, 0x18, 0x60, 0x9d,
  3470. 0x80, 0x53, 0x06, 0x01,
  3471. 0x06, 0xb6, 0xc2, 0xb5,
  3472. 0xef, 0x7c, 0x04
  3473. ], false)));
  3474. transmuxer.push(packetize(videoPes([
  3475. 0x05, 0x01 // slice_layer_without_partitioning_rbsp_idr
  3476. ], true)));
  3477. assert.equal(
  3478. videoSegmentTimingInfoArr.length,
  3479. 0,
  3480. 'has not triggered videoSegmentTimingInfo'
  3481. );
  3482. transmuxer.flush();
  3483. assert.equal(videoSegmentTimingInfoArr.length, 1, 'triggered videoSegmentTimingInfo');
  3484. });
  3485. QUnit.test('generates an audio init segment', function(assert) {
  3486. var segments = [], boxes;
  3487. transmuxer.on('data', function(segment) {
  3488. segments.push(segment);
  3489. });
  3490. transmuxer.push(packetize(PAT));
  3491. transmuxer.push(packetize(generatePMT({
  3492. hasAudio: true
  3493. })));
  3494. transmuxer.push(packetize(audioPes([
  3495. 0x19, 0x47
  3496. ], true)));
  3497. transmuxer.flush();
  3498. assert.equal(segments.length, 1, 'generated a segment');
  3499. assert.ok(segments[0].data, 'wrote data in the init segment');
  3500. assert.equal(segments[0].type, 'audio', 'audio is the segment type');
  3501. assert.ok(segments[0].info, 'audio info is alongside audio segments/bytes');
  3502. mp4AudioProperties.forEach(function(prop) {
  3503. assert.ok(segments[0].info[prop], 'audio info has ' + prop);
  3504. });
  3505. boxes = mp4.tools.inspect(segments[0].initSegment);
  3506. assert.equal('ftyp', boxes[0].type, 'generated an ftyp box');
  3507. assert.equal('moov', boxes[1].type, 'generated a moov box');
  3508. });
  3509. QUnit.test('buffers video samples until flushed', function(assert) {
  3510. var samples = [], offset, boxes, initSegment;
  3511. transmuxer.on('data', function(data) {
  3512. samples.push(data);
  3513. });
  3514. transmuxer.push(packetize(PAT));
  3515. transmuxer.push(packetize(generatePMT({
  3516. hasVideo: true
  3517. })));
  3518. // buffer a NAL
  3519. transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
  3520. transmuxer.push(packetize(videoPes([0x05, 0x02])));
  3521. // add an access_unit_delimiter_rbsp
  3522. transmuxer.push(packetize(videoPes([0x09, 0x03])));
  3523. transmuxer.push(packetize(videoPes([0x00, 0x04])));
  3524. transmuxer.push(packetize(videoPes([0x00, 0x05])));
  3525. // flush everything
  3526. transmuxer.flush();
  3527. assert.equal(samples.length, 1, 'emitted one event');
  3528. boxes = mp4.tools.inspect(samples[0].data);
  3529. initSegment = mp4.tools.inspect(samples[0].initSegment);
  3530. assert.equal(boxes.length, 2, 'generated two boxes');
  3531. assert.equal(initSegment.length, 2, 'generated two init segment boxes');
  3532. assert.equal(boxes[0].type, 'moof', 'the first box is a moof');
  3533. assert.equal(boxes[1].type, 'mdat', 'the second box is a mdat');
  3534. offset = boxes[0].size + 8;
  3535. assert.deepEqual(new Uint8Array(samples[0].data.subarray(offset)),
  3536. new Uint8Array([
  3537. 0, 0, 0, 2,
  3538. 0x09, 0x01,
  3539. 0, 0, 0, 2,
  3540. 0x05, 0x02,
  3541. 0, 0, 0, 2,
  3542. 0x09, 0x03,
  3543. 0, 0, 0, 2,
  3544. 0x00, 0x04,
  3545. 0, 0, 0, 2,
  3546. 0x00, 0x05]),
  3547. 'concatenated NALs into an mdat');
  3548. });
  3549. QUnit.test('creates a metadata stream', function(assert) {
  3550. transmuxer.push(packetize(PAT));
  3551. assert.ok(transmuxer.transmuxPipeline_.metadataStream, 'created a metadata stream');
  3552. });
  3553. QUnit.test('pipes timed metadata to the metadata stream', function(assert) {
  3554. var metadatas = [];
  3555. transmuxer.push(packetize(PAT));
  3556. transmuxer.transmuxPipeline_.metadataStream.on('data', function(data) {
  3557. metadatas.push(data);
  3558. });
  3559. transmuxer.push(packetize(PMT));
  3560. transmuxer.push(packetize(timedMetadataPes([0x03])));
  3561. transmuxer.flush();
  3562. assert.equal(metadatas.length, 1, 'emitted timed metadata');
  3563. });
  3564. QUnit.test('pipeline dynamically configures itself based on input', function(assert) {
  3565. var id3 = id3Generator;
  3566. transmuxer.push(packetize(PAT));
  3567. transmuxer.push(packetize(generatePMT({
  3568. hasAudio: true
  3569. })));
  3570. transmuxer.push(packetize(timedMetadataPes([0x03])));
  3571. transmuxer.flush();
  3572. assert.equal(transmuxer.transmuxPipeline_.type, 'ts', 'detected TS file data');
  3573. transmuxer.push(new Uint8Array(id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)).concat([0xFF, 0xF1])));
  3574. transmuxer.flush();
  3575. assert.equal(transmuxer.transmuxPipeline_.type, 'aac', 'detected AAC file data');
  3576. });
  3577. QUnit.test('pipeline retriggers log events', function(assert) {
  3578. var id3 = id3Generator;
  3579. var logs = [];
  3580. var checkLogs = function() {
  3581. Object.keys(transmuxer.transmuxPipeline_).forEach(function(key) {
  3582. var stream = transmuxer.transmuxPipeline_[key];
  3583. if (!stream.on || key === 'headOfPipeline') {
  3584. return;
  3585. }
  3586. stream.trigger('log', {level: 'foo', message: 'bar'});
  3587. assert.deepEqual(logs, [
  3588. {level: 'foo', message: 'bar', stream: key}
  3589. ], 'retriggers log from ' + key);
  3590. logs.length = 0;
  3591. });
  3592. };
  3593. transmuxer.on('log', function(log) {
  3594. logs.push(log);
  3595. });
  3596. transmuxer.push(packetize(PAT));
  3597. transmuxer.push(packetize(generatePMT({
  3598. hasAudio: true
  3599. })));
  3600. transmuxer.push(packetize(timedMetadataPes([0x03])));
  3601. transmuxer.flush();
  3602. transmuxer.push(packetize(PAT));
  3603. transmuxer.push(packetize(generatePMT({
  3604. hasAudio: true
  3605. })));
  3606. transmuxer.push(packetize(timedMetadataPes([0x03])));
  3607. transmuxer.flush();
  3608. assert.equal(transmuxer.transmuxPipeline_.type, 'ts', 'detected TS file data');
  3609. checkLogs();
  3610. transmuxer.push(new Uint8Array(id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)).concat([0xFF, 0xF1])));
  3611. transmuxer.flush();
  3612. transmuxer.push(new Uint8Array(id3.id3Tag(id3.id3Frame('PRIV', 0x00, 0x01)).concat([0xFF, 0xF1])));
  3613. transmuxer.flush();
  3614. assert.equal(transmuxer.transmuxPipeline_.type, 'aac', 'detected AAC file data');
  3615. checkLogs();
  3616. });
  3617. QUnit.test('reuses audio track object when the pipeline reconfigures itself', function(assert) {
  3618. var boxes, segments = [],
  3619. id3Tag = new Uint8Array(75),
  3620. streamTimestamp = 'com.apple.streaming.transportStreamTimestamp',
  3621. priv = 'PRIV',
  3622. i,
  3623. adtsPayload;
  3624. id3Tag[0] = 73;
  3625. id3Tag[1] = 68;
  3626. id3Tag[2] = 51;
  3627. id3Tag[3] = 4;
  3628. id3Tag[9] = 63;
  3629. id3Tag[17] = 53;
  3630. id3Tag[70] = 13;
  3631. id3Tag[71] = 187;
  3632. id3Tag[72] = 160;
  3633. id3Tag[73] = 0xFF;
  3634. id3Tag[74] = 0xF1;
  3635. for (i = 0; i < priv.length; i++) {
  3636. id3Tag[i + 10] = priv.charCodeAt(i);
  3637. }
  3638. for (i = 0; i < streamTimestamp.length; i++) {
  3639. id3Tag[i + 20] = streamTimestamp.charCodeAt(i);
  3640. }
  3641. transmuxer.on('data', function(segment) {
  3642. segments.push(segment);
  3643. });
  3644. transmuxer.push(packetize(PAT));
  3645. transmuxer.push(packetize(packetize(generatePMT({
  3646. hasAudio: true
  3647. }))));
  3648. transmuxer.push(packetize(audioPes([0x19, 0x47], true, 10000)));
  3649. transmuxer.flush();
  3650. boxes = mp4.tools.inspect(segments[0].data);
  3651. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  3652. 0,
  3653. 'first segment starts at 0 pts');
  3654. adtsPayload = new Uint8Array(adtsFrame(2).concat([0x19, 0x47]));
  3655. transmuxer.push(id3Tag);
  3656. transmuxer.push(adtsPayload);
  3657. transmuxer.flush();
  3658. boxes = mp4.tools.inspect(segments[1].data);
  3659. assert.equal(boxes[0].boxes[1].boxes[1].baseMediaDecodeTime,
  3660. // The first segment had a PTS of 10,000 and the second segment 900,000
  3661. // Audio PTS is specified in a clock equal to samplerate (44.1khz)
  3662. // So you have to take the different between the PTSs (890,000)
  3663. // and transform it from 90khz to 44.1khz clock
  3664. Math.floor((900000 - 10000) / (90000 / 44100)),
  3665. 'second segment starts at the right time');
  3666. });
  3667. validateTrack = function(track, metadata) {
  3668. var mdia;
  3669. QUnit.assert.equal(track.type, 'trak', 'wrote the track type');
  3670. QUnit.assert.equal(track.boxes.length, 2, 'wrote track children');
  3671. QUnit.assert.equal(track.boxes[0].type, 'tkhd', 'wrote the track header');
  3672. if (metadata) {
  3673. if (metadata.trackId) {
  3674. QUnit.assert.equal(track.boxes[0].trackId, metadata.trackId, 'wrote the track id');
  3675. }
  3676. if (metadata.width) {
  3677. QUnit.assert.equal(track.boxes[0].width, metadata.width, 'wrote the width');
  3678. }
  3679. if (metadata.height) {
  3680. QUnit.assert.equal(track.boxes[0].height, metadata.height, 'wrote the height');
  3681. }
  3682. }
  3683. mdia = track.boxes[1];
  3684. QUnit.assert.equal(mdia.type, 'mdia', 'wrote the media');
  3685. QUnit.assert.equal(mdia.boxes.length, 3, 'wrote the mdia children');
  3686. QUnit.assert.equal(mdia.boxes[0].type, 'mdhd', 'wrote the media header');
  3687. QUnit.assert.equal(mdia.boxes[0].language, 'und', 'the language is undefined');
  3688. QUnit.assert.equal(mdia.boxes[0].duration, 0xffffffff, 'the duration is at maximum');
  3689. QUnit.assert.equal(mdia.boxes[1].type, 'hdlr', 'wrote the media handler');
  3690. QUnit.assert.equal(mdia.boxes[2].type, 'minf', 'wrote the media info');
  3691. };
  3692. validateTrackFragment = function(track, segment, metadata, type) {
  3693. var tfhd, trun, sdtp, i, j, sample, nalUnitType;
  3694. QUnit.assert.equal(track.type, 'traf', 'wrote a track fragment');
  3695. if (type === 'video') {
  3696. QUnit.assert.equal(track.boxes.length, 4, 'wrote four track fragment children');
  3697. } else if (type === 'audio') {
  3698. QUnit.assert.equal(track.boxes.length, 3, 'wrote three track fragment children');
  3699. }
  3700. tfhd = track.boxes[0];
  3701. QUnit.assert.equal(tfhd.type, 'tfhd', 'wrote a track fragment header');
  3702. QUnit.assert.equal(tfhd.trackId, metadata.trackId, 'wrote the track id');
  3703. QUnit.assert.equal(track.boxes[1].type,
  3704. 'tfdt',
  3705. 'wrote a track fragment decode time box');
  3706. QUnit.assert.ok(track.boxes[1].baseMediaDecodeTime >= 0, 'base decode time is non-negative');
  3707. trun = track.boxes[2];
  3708. QUnit.assert.ok(trun.dataOffset >= 0, 'set data offset');
  3709. QUnit.assert.equal(trun.dataOffset,
  3710. metadata.mdatOffset + 8,
  3711. 'trun data offset is the size of the moof');
  3712. QUnit.assert.ok(trun.samples.length > 0, 'generated media samples');
  3713. for (i = 0, j = metadata.baseOffset + trun.dataOffset;
  3714. i < trun.samples.length;
  3715. i++) {
  3716. sample = trun.samples[i];
  3717. QUnit.assert.ok(sample.size > 0, 'wrote a positive size for sample ' + i);
  3718. if (type === 'video') {
  3719. QUnit.assert.ok(sample.duration > 0, 'wrote a positive duration for sample ' + i);
  3720. QUnit.assert.ok(sample.compositionTimeOffset >= 0,
  3721. 'wrote a positive composition time offset for sample ' + i);
  3722. QUnit.assert.ok(sample.flags, 'wrote sample flags');
  3723. QUnit.assert.equal(sample.flags.isLeading, 0, 'the leading nature is unknown');
  3724. QUnit.assert.notEqual(sample.flags.dependsOn, 0, 'sample dependency is not unknown');
  3725. QUnit.assert.notEqual(sample.flags.dependsOn, 4, 'sample dependency is valid');
  3726. nalUnitType = segment[j + 4] & 0x1F;
  3727. QUnit.assert.equal(nalUnitType, 9, 'samples begin with an access_unit_delimiter_rbsp');
  3728. QUnit.assert.equal(sample.flags.isDependedOn, 0, 'dependency of other samples is unknown');
  3729. QUnit.assert.equal(sample.flags.hasRedundancy, 0, 'sample redundancy is unknown');
  3730. QUnit.assert.equal(sample.flags.degradationPriority, 0, 'sample degradation priority is zero');
  3731. // If current sample is Key frame
  3732. if (sample.flags.dependsOn === 2) {
  3733. QUnit.assert.equal(sample.flags.isNonSyncSample, 0, 'samples_is_non_sync_sample flag is zero');
  3734. } else {
  3735. QUnit.assert.equal(sample.flags.isNonSyncSample, 1, 'samples_is_non_sync_sample flag is one');
  3736. }
  3737. } else {
  3738. QUnit.assert.equal(sample.duration, 1024,
  3739. 'aac sample duration is always 1024');
  3740. }
  3741. j += sample.size; // advance to the next sample in the mdat
  3742. }
  3743. if (type === 'video') {
  3744. sdtp = track.boxes[3];
  3745. QUnit.assert.equal(trun.samples.length,
  3746. sdtp.samples.length,
  3747. 'wrote an QUnit.equal number of trun and sdtp samples');
  3748. for (i = 0; i < sdtp.samples.length; i++) {
  3749. sample = sdtp.samples[i];
  3750. QUnit.assert.notEqual(sample.dependsOn, 0, 'sample dependency is not unknown');
  3751. QUnit.assert.equal(trun.samples[i].flags.dependsOn,
  3752. sample.dependsOn,
  3753. 'wrote a consistent dependsOn');
  3754. QUnit.assert.equal(trun.samples[i].flags.isDependedOn,
  3755. sample.isDependedOn,
  3756. 'wrote a consistent isDependedOn');
  3757. QUnit.assert.equal(trun.samples[i].flags.hasRedundancy,
  3758. sample.hasRedundancy,
  3759. 'wrote a consistent hasRedundancy');
  3760. }
  3761. }
  3762. };
  3763. QUnit.test('parses an example mp2t file and generates combined media segments', function(assert) {
  3764. var
  3765. segments = [],
  3766. i, j, boxes, mfhd, trackType = 'audio', trackId = 257, baseOffset = 0, initSegment;
  3767. transmuxer.on('data', function(segment) {
  3768. if (segment.type === 'combined') {
  3769. segments.push(segment);
  3770. }
  3771. });
  3772. transmuxer.push(testSegment);
  3773. transmuxer.flush();
  3774. assert.equal(segments.length, 1, 'generated one combined segment');
  3775. boxes = mp4.tools.inspect(segments[0].data);
  3776. initSegment = mp4.tools.inspect(segments[0].initSegment);
  3777. assert.equal(boxes.length, 4, 'combined segments are composed of 4 boxes');
  3778. assert.equal(initSegment.length, 2, 'init segment is composed of 2 boxes');
  3779. assert.equal(initSegment[0].type, 'ftyp', 'the first box is an ftyp');
  3780. assert.equal(initSegment[1].type, 'moov', 'the second box is a moov');
  3781. assert.equal(initSegment[1].boxes[0].type, 'mvhd', 'generated an mvhd');
  3782. validateTrack(initSegment[1].boxes[1], {
  3783. trackId: 256
  3784. });
  3785. validateTrack(initSegment[1].boxes[2], {
  3786. trackId: 257
  3787. });
  3788. for (i = 0; i < boxes.length; i += 2) {
  3789. assert.equal(boxes[i].type, 'moof', 'first box is a moof');
  3790. assert.equal(boxes[i].boxes.length, 2, 'the moof has two children');
  3791. mfhd = boxes[i].boxes[0];
  3792. assert.equal(mfhd.type, 'mfhd', 'mfhd is a child of the moof');
  3793. assert.equal(boxes[i + 1].type, 'mdat', 'second box is an mdat');
  3794. // Only do even numbered boxes because the odd-offsets will be mdat
  3795. if (i % 2 === 0) {
  3796. for (j = 0; j < i; j++) {
  3797. baseOffset += boxes[j].size;
  3798. }
  3799. validateTrackFragment(boxes[i].boxes[1], segments[0].data, {
  3800. trackId: trackId,
  3801. width: 388,
  3802. height: 300,
  3803. baseOffset: baseOffset,
  3804. mdatOffset: boxes[i].size
  3805. }, trackType);
  3806. trackId--;
  3807. baseOffset = 0;
  3808. trackType = 'video';
  3809. }
  3810. }
  3811. });
  3812. QUnit.test('can be reused for multiple TS segments', function(assert) {
  3813. var
  3814. boxes = [],
  3815. initSegments = [];
  3816. transmuxer.on('data', function(segment) {
  3817. if (segment.type === 'combined') {
  3818. boxes.push(mp4.tools.inspect(segment.data));
  3819. initSegments.push(mp4.tools.inspect(segment.initSegment));
  3820. }
  3821. });
  3822. transmuxer.push(testSegment);
  3823. transmuxer.flush();
  3824. transmuxer.push(testSegment);
  3825. transmuxer.flush();
  3826. assert.equal(boxes.length, 2, 'generated two combined segments');
  3827. assert.equal(initSegments.length, 2, 'generated two combined init segments');
  3828. assert.deepEqual(initSegments[0][0], initSegments[1][0], 'generated identical ftyps');
  3829. assert.deepEqual(initSegments[0][1], initSegments[1][1], 'generated identical moovs');
  3830. assert.deepEqual(boxes[0][0].boxes[1],
  3831. boxes[1][0].boxes[1],
  3832. 'generated identical video trafs');
  3833. assert.equal(boxes[0][0].boxes[0].sequenceNumber,
  3834. 0,
  3835. 'set the correct video sequence number');
  3836. assert.equal(boxes[1][0].boxes[0].sequenceNumber,
  3837. 1,
  3838. 'set the correct video sequence number');
  3839. assert.deepEqual(boxes[0][1],
  3840. boxes[1][1],
  3841. 'generated identical video mdats');
  3842. assert.deepEqual(boxes[0][2].boxes[3],
  3843. boxes[1][2].boxes[3],
  3844. 'generated identical audio trafs');
  3845. assert.equal(boxes[0][2].boxes[0].sequenceNumber,
  3846. 0,
  3847. 'set the correct audio sequence number');
  3848. assert.equal(boxes[1][2].boxes[0].sequenceNumber,
  3849. 1,
  3850. 'set the correct audio sequence number');
  3851. assert.deepEqual(boxes[0][3],
  3852. boxes[1][3],
  3853. 'generated identical audio mdats');
  3854. });
  3855. QUnit.module('NalByteStream', {
  3856. beforeEach: function() {
  3857. nalByteStream = new NalByteStream();
  3858. }
  3859. });
  3860. QUnit.test('parses nal units with 4-byte start code', function(assert) {
  3861. var nalUnits = [];
  3862. nalByteStream.on('data', function(data) {
  3863. nalUnits.push(data);
  3864. });
  3865. nalByteStream.push({
  3866. data: new Uint8Array([
  3867. 0x00, 0x00, 0x00, 0x01, // start code
  3868. 0x09, 0xFF, // Payload
  3869. 0x00, 0x00, 0x00 // end code
  3870. ])
  3871. });
  3872. assert.equal(nalUnits.length, 1, 'found one nal');
  3873. assert.deepEqual(nalUnits[0], new Uint8Array([0x09, 0xFF]), 'has the proper payload');
  3874. });
  3875. QUnit.test('parses nal units with 3-byte start code', function(assert) {
  3876. var nalUnits = [];
  3877. nalByteStream.on('data', function(data) {
  3878. nalUnits.push(data);
  3879. });
  3880. nalByteStream.push({
  3881. data: new Uint8Array([
  3882. 0x00, 0x00, 0x01, // start code
  3883. 0x09, 0xFF, // Payload
  3884. 0x00, 0x00, 0x00 // end code
  3885. ])
  3886. });
  3887. assert.equal(nalUnits.length, 1, 'found one nal');
  3888. assert.deepEqual(nalUnits[0], new Uint8Array([0x09, 0xFF]), 'has the proper payload');
  3889. });
  3890. QUnit.test('does not emit empty nal units', function(assert) {
  3891. var dataTriggerCount = 0;
  3892. nalByteStream.on('data', function(data) {
  3893. dataTriggerCount++;
  3894. });
  3895. // An empty nal unit is just two start codes:
  3896. nalByteStream.push({
  3897. data: new Uint8Array([
  3898. 0x00, 0x00, 0x00, 0x01, // start code
  3899. 0x00, 0x00, 0x00, 0x01 // start code
  3900. ])
  3901. });
  3902. assert.equal(dataTriggerCount, 0, 'emmited no nal units');
  3903. });
  3904. QUnit.test('parses multiple nal units', function(assert) {
  3905. var nalUnits = [];
  3906. nalByteStream.on('data', function(data) {
  3907. nalUnits.push(data);
  3908. });
  3909. nalByteStream.push({
  3910. data: new Uint8Array([
  3911. 0x00, 0x00, 0x01, // start code
  3912. 0x09, 0xFF, // Payload
  3913. 0x00, 0x00, 0x00, // end code
  3914. 0x00, 0x00, 0x01, // start code
  3915. 0x12, 0xDD, // Payload
  3916. 0x00, 0x00, 0x00 // end code
  3917. ])
  3918. });
  3919. assert.equal(nalUnits.length, 2, 'found two nals');
  3920. assert.deepEqual(nalUnits[0], new Uint8Array([0x09, 0xFF]), 'has the proper payload');
  3921. assert.deepEqual(nalUnits[1], new Uint8Array([0x12, 0xDD]), 'has the proper payload');
  3922. });
  3923. QUnit.test('parses nal units surrounded by an unreasonable amount of zero-bytes', function(assert) {
  3924. var nalUnits = [];
  3925. nalByteStream.on('data', function(data) {
  3926. nalUnits.push(data);
  3927. });
  3928. nalByteStream.push({
  3929. data: new Uint8Array([
  3930. 0x00, 0x00, 0x00,
  3931. 0x00, 0x00, 0x00,
  3932. 0x00, 0x00, 0x00,
  3933. 0x00, 0x00, 0x00,
  3934. 0x00, 0x00, 0x00,
  3935. 0x00, 0x00, 0x00,
  3936. 0x00, 0x00, 0x00,
  3937. 0x00, 0x00, 0x01, // start code
  3938. 0x09, 0xFF, // Payload
  3939. 0x00, 0x00, 0x00,
  3940. 0x00, 0x00, 0x00,
  3941. 0x00, 0x00, 0x00,
  3942. 0x00, 0x00, 0x00,
  3943. 0x00, 0x00, 0x00,
  3944. 0x00, 0x00, 0x00,
  3945. 0x00, 0x00, 0x00,
  3946. 0x00, 0x00, 0x00,
  3947. 0x00, 0x00, 0x00, // end code
  3948. 0x00, 0x00, 0x01, // start code
  3949. 0x12, 0xDD, // Payload
  3950. 0x00, 0x00, 0x00, // end code
  3951. 0x00, 0x00, 0x00,
  3952. 0x00, 0x00, 0x00,
  3953. 0x00, 0x00, 0x00,
  3954. 0x00, 0x00, 0x00,
  3955. 0x00, 0x00, 0x00,
  3956. 0x00, 0x00, 0x00
  3957. ])
  3958. });
  3959. assert.equal(nalUnits.length, 2, 'found two nals');
  3960. assert.deepEqual(nalUnits[0], new Uint8Array([0x09, 0xFF]), 'has the proper payload');
  3961. assert.deepEqual(nalUnits[1], new Uint8Array([0x12, 0xDD]), 'has the proper payload');
  3962. });
  3963. QUnit.test('parses nal units split across multiple packets', function(assert) {
  3964. var nalUnits = [];
  3965. nalByteStream.on('data', function(data) {
  3966. nalUnits.push(data);
  3967. });
  3968. nalByteStream.push({
  3969. data: new Uint8Array([
  3970. 0x00, 0x00, 0x01, // start code
  3971. 0x09, 0xFF // Partial payload
  3972. ])
  3973. });
  3974. nalByteStream.push({
  3975. data: new Uint8Array([
  3976. 0x12, 0xDD, // Partial Payload
  3977. 0x00, 0x00, 0x00 // end code
  3978. ])
  3979. });
  3980. assert.equal(nalUnits.length, 1, 'found one nal');
  3981. assert.deepEqual(nalUnits[0], new Uint8Array([0x09, 0xFF, 0x12, 0xDD]), 'has the proper payload');
  3982. });
  3983. QUnit.module('FLV - Transmuxer', {
  3984. beforeEach: function() {
  3985. transmuxer = new FlvTransmuxer();
  3986. }
  3987. });
  3988. QUnit.test('generates video tags', function(assert) {
  3989. var segments = [];
  3990. transmuxer.on('data', function(segment) {
  3991. segments.push(segment);
  3992. });
  3993. transmuxer.push(packetize(PAT));
  3994. transmuxer.push(packetize(generatePMT({
  3995. hasVideo: true
  3996. })));
  3997. transmuxer.push(packetize(videoPes([
  3998. 0x09, 0x01 // access_unit_delimiter_rbsp
  3999. ], true)));
  4000. transmuxer.push(packetize(videoPes([
  4001. 0x09, 0x01 // access_unit_delimiter_rbsp
  4002. ], true)));
  4003. transmuxer.flush();
  4004. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4005. assert.equal(segments[0].tags.videoTags.length, 2, 'generated a two video tags');
  4006. });
  4007. QUnit.test('drops nalUnits at the start of a segment not preceeded by an access_unit_delimiter_rbsp', function(assert) {
  4008. var segments = [];
  4009. transmuxer.on('data', function(segment) {
  4010. segments.push(segment);
  4011. });
  4012. transmuxer.push(packetize(PAT));
  4013. transmuxer.push(packetize(generatePMT({
  4014. hasVideo: true
  4015. })));
  4016. transmuxer.push(packetize(videoPes([
  4017. 0x08, 0x01 // pic_parameter_set_rbsp
  4018. ], true)));
  4019. transmuxer.push(packetize(videoPes([
  4020. 0x07, // seq_parameter_set_rbsp
  4021. 0x27, 0x42, 0xe0, 0x0b,
  4022. 0xa9, 0x18, 0x60, 0x9d,
  4023. 0x80, 0x53, 0x06, 0x01,
  4024. 0x06, 0xb6, 0xc2, 0xb5,
  4025. 0xef, 0x7c, 0x04
  4026. ], false)));
  4027. transmuxer.push(packetize(videoPes([
  4028. 0x09, 0x01 // access_unit_delimiter_rbsp
  4029. ], true)));
  4030. transmuxer.flush();
  4031. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4032. assert.equal(segments[0].tags.videoTags.length, 1, 'generated a single video tag');
  4033. });
  4034. QUnit.test('generates audio tags', function(assert) {
  4035. var segments = [];
  4036. transmuxer.on('data', function(segment) {
  4037. segments.push(segment);
  4038. });
  4039. transmuxer.push(packetize(PAT));
  4040. transmuxer.push(packetize(generatePMT({
  4041. hasAudio: true
  4042. })));
  4043. transmuxer.push(packetize(audioPes([
  4044. 0x19, 0x47
  4045. ], true)));
  4046. transmuxer.flush();
  4047. assert.equal(segments[0].tags.audioTags.length, 3, 'generated three audio tags');
  4048. assert.equal(segments[0].tags.videoTags.length, 0, 'generated no video tags');
  4049. });
  4050. QUnit.test('buffers video samples until flushed', function(assert) {
  4051. var segments = [];
  4052. transmuxer.on('data', function(data) {
  4053. segments.push(data);
  4054. });
  4055. transmuxer.push(packetize(PAT));
  4056. transmuxer.push(packetize(generatePMT({
  4057. hasVideo: true
  4058. })));
  4059. // buffer a NAL
  4060. transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
  4061. transmuxer.push(packetize(videoPes([0x00, 0x02])));
  4062. // add an access_unit_delimiter_rbsp
  4063. transmuxer.push(packetize(videoPes([0x09, 0x03])));
  4064. transmuxer.push(packetize(videoPes([0x00, 0x04])));
  4065. transmuxer.push(packetize(videoPes([0x00, 0x05])));
  4066. // flush everything
  4067. transmuxer.flush();
  4068. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4069. assert.equal(segments[0].tags.videoTags.length, 2, 'generated two video tags');
  4070. });
  4071. QUnit.test('does not buffer a duplicate video sample on subsequent flushes', function(assert) {
  4072. var segments = [];
  4073. transmuxer.on('data', function(data) {
  4074. segments.push(data);
  4075. });
  4076. transmuxer.push(packetize(PAT));
  4077. transmuxer.push(packetize(generatePMT({
  4078. hasVideo: true
  4079. })));
  4080. // buffer a NAL
  4081. transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
  4082. transmuxer.push(packetize(videoPes([0x00, 0x02])));
  4083. // add an access_unit_delimiter_rbsp
  4084. transmuxer.push(packetize(videoPes([0x09, 0x03])));
  4085. transmuxer.push(packetize(videoPes([0x00, 0x04])));
  4086. transmuxer.push(packetize(videoPes([0x00, 0x05])));
  4087. // flush everything
  4088. transmuxer.flush();
  4089. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4090. assert.equal(segments[0].tags.videoTags.length, 2, 'generated two video tags');
  4091. segments = [];
  4092. transmuxer.push(packetize(PAT));
  4093. transmuxer.push(packetize(generatePMT({
  4094. hasVideo: true
  4095. })));
  4096. // buffer a NAL
  4097. transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
  4098. transmuxer.push(packetize(videoPes([0x00, 0x02])));
  4099. // add an access_unit_delimiter_rbsp
  4100. transmuxer.push(packetize(videoPes([0x09, 0x03])));
  4101. transmuxer.push(packetize(videoPes([0x00, 0x04])));
  4102. transmuxer.push(packetize(videoPes([0x00, 0x05])));
  4103. // flush everything
  4104. transmuxer.flush();
  4105. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4106. assert.equal(segments[0].tags.videoTags.length, 2, 'generated two video tags');
  4107. });
  4108. QUnit.test('emits done event when no audio data is present', function(assert) {
  4109. var segments = [];
  4110. var done = false;
  4111. transmuxer.on('data', function(data) {
  4112. segments.push(data);
  4113. });
  4114. transmuxer.on('done', function() {
  4115. done = true;
  4116. });
  4117. transmuxer.push(packetize(PAT));
  4118. transmuxer.push(packetize(generatePMT({
  4119. hasVideo: true,
  4120. hasAudio: true
  4121. })));
  4122. // buffer a NAL
  4123. transmuxer.push(packetize(videoPes([0x09, 0x01], true)));
  4124. transmuxer.push(packetize(videoPes([0x00, 0x02])));
  4125. // add an access_unit_delimiter_rbsp
  4126. transmuxer.push(packetize(videoPes([0x09, 0x03])));
  4127. transmuxer.push(packetize(videoPes([0x00, 0x04])));
  4128. transmuxer.push(packetize(videoPes([0x00, 0x05])));
  4129. // flush everything
  4130. transmuxer.flush();
  4131. assert.equal(segments[0].tags.audioTags.length, 0, 'generated no audio tags');
  4132. assert.equal(segments[0].tags.videoTags.length, 2, 'generated two video tags');
  4133. assert.ok(done, 'emitted done event even though no audio data was given');
  4134. });