| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502 |
- #ifndef MINIMP4_H
- #define MINIMP4_H
- /*
- https://github.com/aspt/mp4
- https://github.com/lieff/minimp4
- To the extent possible under law, the author(s) have dedicated all copyright and related and neighboring rights to this software to the public domain worldwide.
- This software is distributed without any warranty.
- See <http://creativecommons.org/publicdomain/zero/1.0/>.
- */
- #include <stdio.h>
- #include <stdint.h>
- #include <stdlib.h>
- #include <string.h>
- #include <limits.h>
- #include <assert.h>
- #ifdef __cplusplus
- extern "C" {
- #endif
- #define MINIMP4_MIN(x, y) ((x) < (y) ? (x) : (y))
- /************************************************************************/
- /* Build configuration */
- /************************************************************************/
- #define FIX_BAD_ANDROID_META_BOX 1
- #define MAX_CHUNKS_DEPTH 64 // Max chunks nesting level
- #define MINIMP4_MAX_SPS 32
- #define MINIMP4_MAX_PPS 256
- #define MINIMP4_TRANSCODE_SPS_ID 1
- // Support indexing of MP4 files over 4 GB.
- // If disabled, files with 64-bit offset fields is still supported,
- // but error signaled if such field contains too big offset
- // This switch affect return type of MP4D_frame_offset() function
- #define MINIMP4_ALLOW_64BIT 1
- #define MP4D_TRACE_SUPPORTED 0 // Debug trace
- #define MP4D_TRACE_TIMESTAMPS 1
- // Support parsing of supplementary information, not necessary for decoding:
- // duration, language, bitrate, metadata tags, etc
- #define MP4D_INFO_SUPPORTED 1
- // Enable code, which prints to stdout supplementary MP4 information:
- #define MP4D_PRINT_INFO_SUPPORTED 0
- #define MP4D_AVC_SUPPORTED 1
- #define MP4D_HEVC_SUPPORTED 1
- #define MP4D_TIMESTAMPS_SUPPORTED 1
- // Enable TrackFragmentBaseMediaDecodeTimeBox support
- #define MP4D_TFDT_SUPPORT 0
- /************************************************************************/
- /* Some values of MP4(E/D)_track_t->object_type_indication */
- /************************************************************************/
- // MPEG-4 AAC (all profiles)
- #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 0x40
- // MPEG-2 AAC, Main profile
- #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE 0x66
- // MPEG-2 AAC, LC profile
- #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE 0x67
- // MPEG-2 AAC, SSR profile
- #define MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE 0x68
- // H.264 (AVC) video
- #define MP4_OBJECT_TYPE_AVC 0x21
- // H.265 (HEVC) video
- #define MP4_OBJECT_TYPE_HEVC 0x23
- // http://www.mp4ra.org/object.html 0xC0-E0 && 0xE2 - 0xFE are specified as "user private"
- #define MP4_OBJECT_TYPE_USER_PRIVATE 0xC0
- /************************************************************************/
- /* API error codes */
- /************************************************************************/
- #define MP4E_STATUS_OK 0
- #define MP4E_STATUS_BAD_ARGUMENTS -1
- #define MP4E_STATUS_NO_MEMORY -2
- #define MP4E_STATUS_FILE_WRITE_ERROR -3
- #define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4
- /************************************************************************/
- /* Sample kind for MP4E_put_sample() */
- /************************************************************************/
- #define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame
- #define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame)
- #define MP4E_SAMPLE_CONTINUATION 2 // Not a sample, but continuation of previous sample (new slice)
- /************************************************************************/
- /* Portable 64-bit type definition */
- /************************************************************************/
- #if MINIMP4_ALLOW_64BIT
- typedef uint64_t boxsize_t;
- #else
- typedef unsigned int boxsize_t;
- #endif
- typedef boxsize_t MP4D_file_offset_t;
- /************************************************************************/
- /* Some values of MP4D_track_t->handler_type */
- /************************************************************************/
- // Video track : 'vide'
- #define MP4D_HANDLER_TYPE_VIDE 0x76696465
- // Audio track : 'soun'
- #define MP4D_HANDLER_TYPE_SOUN 0x736F756E
- // General MPEG-4 systems streams (without specific handler).
- // Used for private stream, as suggested in http://www.mp4ra.org/handler.html
- #define MP4E_HANDLER_TYPE_GESM 0x6765736D
- #define HEVC_NAL_VPS 32
- #define HEVC_NAL_SPS 33
- #define HEVC_NAL_PPS 34
- #define HEVC_NAL_BLA_W_LP 16
- #define HEVC_NAL_CRA_NUT 21
- /************************************************************************/
- /* Data structures */
- /************************************************************************/
- typedef struct MP4E_mux_tag MP4E_mux_t;
- typedef enum
- {
- e_audio,
- e_video,
- e_private
- } track_media_kind_t;
- typedef struct
- {
- // MP4 object type code, which defined codec class for the track.
- // See MP4E_OBJECT_TYPE_* values for some codecs
- unsigned object_type_indication;
- // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc...
- unsigned char language[4];
- track_media_kind_t track_media_kind;
- // 90000 for video, sample rate for audio
- unsigned time_scale;
- unsigned default_duration;
- union
- {
- struct
- {
- // number of channels in the audio track.
- unsigned channelcount;
- } a;
- struct
- {
- int width;
- int height;
- } v;
- } u;
- } MP4E_track_t;
- typedef struct MP4D_sample_to_chunk_t_tag MP4D_sample_to_chunk_t;
- typedef struct
- {
- /************************************************************************/
- /* mandatory public data */
- /************************************************************************/
- // How many 'samples' in the track
- // The 'sample' is MP4 term, denoting audio or video frame
- unsigned sample_count;
- // Decoder-specific info (DSI) data
- unsigned char *dsi;
- // DSI data size
- unsigned dsi_bytes;
- // MP4 object type code
- // case 0x00: return "Forbidden";
- // case 0x01: return "Systems ISO/IEC 14496-1";
- // case 0x02: return "Systems ISO/IEC 14496-1";
- // case 0x20: return "Visual ISO/IEC 14496-2";
- // case 0x40: return "Audio ISO/IEC 14496-3";
- // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile";
- // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile";
- // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile";
- // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile";
- // case 0x64: return "Visual ISO/IEC 13818-2 High Profile";
- // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile";
- // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile";
- // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile";
- // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile";
- // case 0x69: return "Audio ISO/IEC 13818-3";
- // case 0x6A: return "Visual ISO/IEC 11172-2";
- // case 0x6B: return "Audio ISO/IEC 11172-3";
- // case 0x6C: return "Visual ISO/IEC 10918-1";
- unsigned object_type_indication;
- #if MP4D_INFO_SUPPORTED
- /************************************************************************/
- /* informational public data */
- /************************************************************************/
- // handler_type when present in a media box, is an integer containing one of
- // the following values, or a value from a derived specification:
- // 'vide' Video track
- // 'soun' Audio track
- // 'hint' Hint track
- unsigned handler_type;
- // Track duration: 64-bit value split into 2 variables
- unsigned duration_hi;
- unsigned duration_lo;
- // duration scale: duration = timescale*seconds
- unsigned timescale;
- // Average bitrate, bits per second
- unsigned avg_bitrate_bps;
- // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc...
- unsigned char language[4];
- // MP4 stream type
- // case 0x00: return "Forbidden";
- // case 0x01: return "ObjectDescriptorStream";
- // case 0x02: return "ClockReferenceStream";
- // case 0x03: return "SceneDescriptionStream";
- // case 0x04: return "VisualStream";
- // case 0x05: return "AudioStream";
- // case 0x06: return "MPEG7Stream";
- // case 0x07: return "IPMPStream";
- // case 0x08: return "ObjectContentInfoStream";
- // case 0x09: return "MPEGJStream";
- unsigned stream_type;
- union
- {
- // for handler_type == 'soun' tracks
- struct
- {
- unsigned channelcount;
- unsigned samplerate_hz;
- } audio;
- // for handler_type == 'vide' tracks
- struct
- {
- unsigned width;
- unsigned height;
- } video;
- } SampleDescription;
- #endif
- /************************************************************************/
- /* private data: MP4 indexes */
- /************************************************************************/
- unsigned *entry_size;
- unsigned sample_to_chunk_count;
- struct MP4D_sample_to_chunk_t_tag *sample_to_chunk;
- unsigned chunk_count;
- MP4D_file_offset_t *chunk_offset;
- #if MP4D_TIMESTAMPS_SUPPORTED
- unsigned *timestamp;
- unsigned *duration;
- #endif
- } MP4D_track_t;
- typedef struct MP4D_demux_tag
- {
- /************************************************************************/
- /* mandatory public data */
- /************************************************************************/
- int64_t read_pos;
- int64_t read_size;
- MP4D_track_t *track;
- int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token);
- void *token;
- unsigned track_count; // number of tracks in the movie
- #if MP4D_INFO_SUPPORTED
- /************************************************************************/
- /* informational public data */
- /************************************************************************/
- // Movie duration: 64-bit value split into 2 variables
- unsigned duration_hi;
- unsigned duration_lo;
- // duration scale: duration = timescale*seconds
- unsigned timescale;
- // Metadata tag (optional)
- // Tags provided 'as-is', without any re-encoding
- struct
- {
- unsigned char *title;
- unsigned char *artist;
- unsigned char *album;
- unsigned char *year;
- unsigned char *comment;
- unsigned char *genre;
- } tag;
- #endif
- } MP4D_demux_t;
- struct MP4D_sample_to_chunk_t_tag
- {
- unsigned first_chunk;
- unsigned samples_per_chunk;
- };
- typedef struct
- {
- void *sps_cache[MINIMP4_MAX_SPS];
- void *pps_cache[MINIMP4_MAX_PPS];
- int sps_bytes[MINIMP4_MAX_SPS];
- int pps_bytes[MINIMP4_MAX_PPS];
- int map_sps[MINIMP4_MAX_SPS];
- int map_pps[MINIMP4_MAX_PPS];
- } h264_sps_id_patcher_t;
- typedef struct mp4_h26x_writer_tag
- {
- #if MINIMP4_TRANSCODE_SPS_ID
- h264_sps_id_patcher_t sps_patcher;
- #endif
- MP4E_mux_t *mux;
- int mux_track_id, is_hevc, need_vps, need_sps, need_pps, need_idr;
- } mp4_h26x_writer_t;
- int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc);
- void mp4_h26x_write_close(mp4_h26x_writer_t *h);
- int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next);
- /************************************************************************/
- /* API */
- /************************************************************************/
- /**
- * Parse given input stream as MP4 file. Allocate and store data indexes.
- * return 1 on success, 0 on failure
- * The MP4 indexes may be stored at the end of stream, so this
- * function may parse all stream.
- * It is guaranteed that function will read/seek sequentially,
- * and will never jump back.
- */
- int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size);
- /**
- * Return position and size for given sample from given track. The 'sample' is a
- * MP4 term for 'frame'
- *
- * frame_bytes [OUT] - return coded frame size in bytes
- * timestamp [OUT] - return frame timestamp (in mp4->timescale units)
- * duration [OUT] - return frame duration (in mp4->timescale units)
- *
- * function return offset for the frame
- */
- MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned int ntrack, unsigned int nsample, unsigned int *frame_bytes, unsigned *timestamp, unsigned *duration);
- /**
- * De-allocated memory
- */
- void MP4D_close(MP4D_demux_t *mp4);
- /**
- * Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS
- * Return pointer to internal mp4 memory, it must not be free()-ed
- *
- * Example: process all SPS in MP4 file:
- * while (sps = MP4D_read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes))
- * {
- * process(sps, sps_bytes);
- * sps_count++;
- * }
- */
- const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes);
- const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes);
- #if MP4D_PRINT_INFO_SUPPORTED
- /**
- * Print MP4 information to stdout.
- * Uses printf() as well as floating-point functions
- * Given as implementation example and for test purposes
- */
- void MP4D_printf_info(const MP4D_demux_t *mp4);
- #endif
- /**
- * Allocates and initialize mp4 multiplexor
- * Given file handler is transparent to the MP4 library, and used only as
- * argument for given fwrite_callback() function. By appropriate definition
- * of callback function application may use any other file output API (for
- * example C++ streams, or Win32 file functions)
- *
- * return multiplexor handle on success; NULL on failure
- */
- MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token,
- int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token));
- /**
- * Add new track
- * The track_data parameter does not referred by the multiplexer after function
- * return, and may be allocated in short-time memory. The dsi member of
- * track_data parameter is mandatory.
- *
- * return ID of added track, or error code MP4E_STATUS_*
- */
- int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data);
- /**
- * Add new sample to specified track
- * The tracks numbered starting with 0, according to order of MP4E_add_track() calls
- * 'kind' is one of MP4E_SAMPLE_... defines
- *
- * return error code MP4E_STATUS_*
- *
- * Example:
- * MP4E_put_sample(mux, 0, data, data_bytes, duration, MP4E_SAMPLE_DEFAULT);
- */
- int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, int kind);
- /**
- * Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer.
- * The close operation takes a time and disk space, since it writes MP4 file
- * indexes. Please note that this function does not closes file handle,
- * which was passed to open function.
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_close(MP4E_mux_t *mux);
- /**
- * Set Decoder Specific Info (DSI)
- * Can be used for audio and private tracks.
- * MUST be used for AAC track.
- * Only one DSI can be set. It is an error to set DSI again
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes);
- /**
- * Set VPS data. MUST be used for HEVC (H.265) track.
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes);
- /**
- * Set SPS data. MUST be used for AVC (H.264) track. Up to 32 different SPS can be used in one track.
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes);
- /**
- * Set PPS data. MUST be used for AVC (H.264) track. Up to 256 different PPS can be used in one track.
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes);
- /**
- * Set or replace ASCII test comment for the file. Set comment to NULL to remove comment.
- *
- * return error code MP4E_STATUS_*
- */
- int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment);
- #ifdef __cplusplus
- }
- #endif
- #endif //MINIMP4_H
- #if defined(MINIMP4_IMPLEMENTATION) && !defined(MINIMP4_IMPLEMENTATION_GUARD)
- #define MINIMP4_IMPLEMENTATION_GUARD
- #define FOUR_CHAR_INT(a, b, c, d) (((uint32_t)(a) << 24) | ((b) << 16) | ((c) << 8) | (d))
- enum
- {
- BOX_co64 = FOUR_CHAR_INT( 'c', 'o', '6', '4' ),//ChunkLargeOffsetAtomType
- BOX_stco = FOUR_CHAR_INT( 's', 't', 'c', 'o' ),//ChunkOffsetAtomType
- BOX_crhd = FOUR_CHAR_INT( 'c', 'r', 'h', 'd' ),//ClockReferenceMediaHeaderAtomType
- BOX_ctts = FOUR_CHAR_INT( 'c', 't', 't', 's' ),//CompositionOffsetAtomType
- BOX_cprt = FOUR_CHAR_INT( 'c', 'p', 'r', 't' ),//CopyrightAtomType
- BOX_url_ = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),//DataEntryURLAtomType
- BOX_urn_ = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),//DataEntryURNAtomType
- BOX_dinf = FOUR_CHAR_INT( 'd', 'i', 'n', 'f' ),//DataInformationAtomType
- BOX_dref = FOUR_CHAR_INT( 'd', 'r', 'e', 'f' ),//DataReferenceAtomType
- BOX_stdp = FOUR_CHAR_INT( 's', 't', 'd', 'p' ),//DegradationPriorityAtomType
- BOX_edts = FOUR_CHAR_INT( 'e', 'd', 't', 's' ),//EditAtomType
- BOX_elst = FOUR_CHAR_INT( 'e', 'l', 's', 't' ),//EditListAtomType
- BOX_uuid = FOUR_CHAR_INT( 'u', 'u', 'i', 'd' ),//ExtendedAtomType
- BOX_free = FOUR_CHAR_INT( 'f', 'r', 'e', 'e' ),//FreeSpaceAtomType
- BOX_hdlr = FOUR_CHAR_INT( 'h', 'd', 'l', 'r' ),//HandlerAtomType
- BOX_hmhd = FOUR_CHAR_INT( 'h', 'm', 'h', 'd' ),//HintMediaHeaderAtomType
- BOX_hint = FOUR_CHAR_INT( 'h', 'i', 'n', 't' ),//HintTrackReferenceAtomType
- BOX_mdia = FOUR_CHAR_INT( 'm', 'd', 'i', 'a' ),//MediaAtomType
- BOX_mdat = FOUR_CHAR_INT( 'm', 'd', 'a', 't' ),//MediaDataAtomType
- BOX_mdhd = FOUR_CHAR_INT( 'm', 'd', 'h', 'd' ),//MediaHeaderAtomType
- BOX_minf = FOUR_CHAR_INT( 'm', 'i', 'n', 'f' ),//MediaInformationAtomType
- BOX_moov = FOUR_CHAR_INT( 'm', 'o', 'o', 'v' ),//MovieAtomType
- BOX_mvhd = FOUR_CHAR_INT( 'm', 'v', 'h', 'd' ),//MovieHeaderAtomType
- BOX_stsd = FOUR_CHAR_INT( 's', 't', 's', 'd' ),//SampleDescriptionAtomType
- BOX_stsz = FOUR_CHAR_INT( 's', 't', 's', 'z' ),//SampleSizeAtomType
- BOX_stz2 = FOUR_CHAR_INT( 's', 't', 'z', '2' ),//CompactSampleSizeAtomType
- BOX_stbl = FOUR_CHAR_INT( 's', 't', 'b', 'l' ),//SampleTableAtomType
- BOX_stsc = FOUR_CHAR_INT( 's', 't', 's', 'c' ),//SampleToChunkAtomType
- BOX_stsh = FOUR_CHAR_INT( 's', 't', 's', 'h' ),//ShadowSyncAtomType
- BOX_skip = FOUR_CHAR_INT( 's', 'k', 'i', 'p' ),//SkipAtomType
- BOX_smhd = FOUR_CHAR_INT( 's', 'm', 'h', 'd' ),//SoundMediaHeaderAtomType
- BOX_stss = FOUR_CHAR_INT( 's', 't', 's', 's' ),//SyncSampleAtomType
- BOX_stts = FOUR_CHAR_INT( 's', 't', 't', 's' ),//TimeToSampleAtomType
- BOX_trak = FOUR_CHAR_INT( 't', 'r', 'a', 'k' ),//TrackAtomType
- BOX_tkhd = FOUR_CHAR_INT( 't', 'k', 'h', 'd' ),//TrackHeaderAtomType
- BOX_tref = FOUR_CHAR_INT( 't', 'r', 'e', 'f' ),//TrackReferenceAtomType
- BOX_udta = FOUR_CHAR_INT( 'u', 'd', 't', 'a' ),//UserDataAtomType
- BOX_vmhd = FOUR_CHAR_INT( 'v', 'm', 'h', 'd' ),//VideoMediaHeaderAtomType
- BOX_url = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),
- BOX_urn = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),
- BOX_gnrv = FOUR_CHAR_INT( 'g', 'n', 'r', 'v' ),//GenericVisualSampleEntryAtomType
- BOX_gnra = FOUR_CHAR_INT( 'g', 'n', 'r', 'a' ),//GenericAudioSampleEntryAtomType
- //V2 atoms
- BOX_ftyp = FOUR_CHAR_INT( 'f', 't', 'y', 'p' ),//FileTypeAtomType
- BOX_padb = FOUR_CHAR_INT( 'p', 'a', 'd', 'b' ),//PaddingBitsAtomType
- //MP4 Atoms
- BOX_sdhd = FOUR_CHAR_INT( 's', 'd', 'h', 'd' ),//SceneDescriptionMediaHeaderAtomType
- BOX_dpnd = FOUR_CHAR_INT( 'd', 'p', 'n', 'd' ),//StreamDependenceAtomType
- BOX_iods = FOUR_CHAR_INT( 'i', 'o', 'd', 's' ),//ObjectDescriptorAtomType
- BOX_odhd = FOUR_CHAR_INT( 'o', 'd', 'h', 'd' ),//ObjectDescriptorMediaHeaderAtomType
- BOX_mpod = FOUR_CHAR_INT( 'm', 'p', 'o', 'd' ),//ODTrackReferenceAtomType
- BOX_nmhd = FOUR_CHAR_INT( 'n', 'm', 'h', 'd' ),//MPEGMediaHeaderAtomType
- BOX_esds = FOUR_CHAR_INT( 'e', 's', 'd', 's' ),//ESDAtomType
- BOX_sync = FOUR_CHAR_INT( 's', 'y', 'n', 'c' ),//OCRReferenceAtomType
- BOX_ipir = FOUR_CHAR_INT( 'i', 'p', 'i', 'r' ),//IPIReferenceAtomType
- BOX_mp4s = FOUR_CHAR_INT( 'm', 'p', '4', 's' ),//MPEGSampleEntryAtomType
- BOX_mp4a = FOUR_CHAR_INT( 'm', 'p', '4', 'a' ),//MPEGAudioSampleEntryAtomType
- BOX_mp4v = FOUR_CHAR_INT( 'm', 'p', '4', 'v' ),//MPEGVisualSampleEntryAtomType
- // http://www.itscj.ipsj.or.jp/sc29/open/29view/29n7644t.doc
- BOX_avc1 = FOUR_CHAR_INT( 'a', 'v', 'c', '1' ),
- BOX_avc2 = FOUR_CHAR_INT( 'a', 'v', 'c', '2' ),
- BOX_svc1 = FOUR_CHAR_INT( 's', 'v', 'c', '1' ),
- BOX_avcC = FOUR_CHAR_INT( 'a', 'v', 'c', 'C' ),
- BOX_svcC = FOUR_CHAR_INT( 's', 'v', 'c', 'C' ),
- BOX_btrt = FOUR_CHAR_INT( 'b', 't', 'r', 't' ),
- BOX_m4ds = FOUR_CHAR_INT( 'm', '4', 'd', 's' ),
- BOX_seib = FOUR_CHAR_INT( 's', 'e', 'i', 'b' ),
- // H264/HEVC
- BOX_hev1 = FOUR_CHAR_INT( 'h', 'e', 'v', '1' ),
- BOX_hvc1 = FOUR_CHAR_INT( 'h', 'v', 'c', '1' ),
- BOX_hvcC = FOUR_CHAR_INT( 'h', 'v', 'c', 'C' ),
- //3GPP atoms
- BOX_samr = FOUR_CHAR_INT( 's', 'a', 'm', 'r' ),//AMRSampleEntryAtomType
- BOX_sawb = FOUR_CHAR_INT( 's', 'a', 'w', 'b' ),//WB_AMRSampleEntryAtomType
- BOX_damr = FOUR_CHAR_INT( 'd', 'a', 'm', 'r' ),//AMRConfigAtomType
- BOX_s263 = FOUR_CHAR_INT( 's', '2', '6', '3' ),//H263SampleEntryAtomType
- BOX_d263 = FOUR_CHAR_INT( 'd', '2', '6', '3' ),//H263ConfigAtomType
- //V2 atoms - Movie Fragments
- BOX_mvex = FOUR_CHAR_INT( 'm', 'v', 'e', 'x' ),//MovieExtendsAtomType
- BOX_trex = FOUR_CHAR_INT( 't', 'r', 'e', 'x' ),//TrackExtendsAtomType
- BOX_moof = FOUR_CHAR_INT( 'm', 'o', 'o', 'f' ),//MovieFragmentAtomType
- BOX_mfhd = FOUR_CHAR_INT( 'm', 'f', 'h', 'd' ),//MovieFragmentHeaderAtomType
- BOX_traf = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ),//TrackFragmentAtomType
- BOX_tfhd = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ),//TrackFragmentHeaderAtomType
- BOX_tfdt = FOUR_CHAR_INT( 't', 'f', 'd', 't' ),//TrackFragmentBaseMediaDecodeTimeBox
- BOX_trun = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ),//TrackFragmentRunAtomType
- BOX_mehd = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),//MovieExtendsHeaderBox
- // Object Descriptors (OD) data coding
- // These takes only 1 byte; this implementation translate <od_tag> to
- // <od_tag> + OD_BASE to keep API uniform and safe for string functions
- OD_BASE = FOUR_CHAR_INT( '$', '$', '$', '0' ),//
- OD_ESD = FOUR_CHAR_INT( '$', '$', '$', '3' ),//SDescriptor_Tag
- OD_DCD = FOUR_CHAR_INT( '$', '$', '$', '4' ),//DecoderConfigDescriptor_Tag
- OD_DSI = FOUR_CHAR_INT( '$', '$', '$', '5' ),//DecoderSpecificInfo_Tag
- OD_SLC = FOUR_CHAR_INT( '$', '$', '$', '6' ),//SLConfigDescriptor_Tag
- BOX_meta = FOUR_CHAR_INT( 'm', 'e', 't', 'a' ),
- BOX_ilst = FOUR_CHAR_INT( 'i', 'l', 's', 't' ),
- // Metagata tags, see http://atomicparsley.sourceforge.net/mpeg-4files.html
- BOX_calb = FOUR_CHAR_INT( '\xa9', 'a', 'l', 'b'), // album
- BOX_cart = FOUR_CHAR_INT( '\xa9', 'a', 'r', 't'), // artist
- BOX_aART = FOUR_CHAR_INT( 'a', 'A', 'R', 'T' ), // album artist
- BOX_ccmt = FOUR_CHAR_INT( '\xa9', 'c', 'm', 't'), // comment
- BOX_cday = FOUR_CHAR_INT( '\xa9', 'd', 'a', 'y'), // year (as string)
- BOX_cnam = FOUR_CHAR_INT( '\xa9', 'n', 'a', 'm'), // title
- BOX_cgen = FOUR_CHAR_INT( '\xa9', 'g', 'e', 'n'), // custom genre (as string or as byte!)
- BOX_trkn = FOUR_CHAR_INT( 't', 'r', 'k', 'n'), // track number (byte)
- BOX_disk = FOUR_CHAR_INT( 'd', 'i', 's', 'k'), // disk number (byte)
- BOX_cwrt = FOUR_CHAR_INT( '\xa9', 'w', 'r', 't'), // composer
- BOX_ctoo = FOUR_CHAR_INT( '\xa9', 't', 'o', 'o'), // encoder
- BOX_tmpo = FOUR_CHAR_INT( 't', 'm', 'p', 'o'), // bpm (byte)
- BOX_cpil = FOUR_CHAR_INT( 'c', 'p', 'i', 'l'), // compilation (byte)
- BOX_covr = FOUR_CHAR_INT( 'c', 'o', 'v', 'r'), // cover art (JPEG/PNG)
- BOX_rtng = FOUR_CHAR_INT( 'r', 't', 'n', 'g'), // rating/advisory (byte)
- BOX_cgrp = FOUR_CHAR_INT( '\xa9', 'g', 'r', 'p'), // grouping
- BOX_stik = FOUR_CHAR_INT( 's', 't', 'i', 'k'), // stik (byte) 0 = Movie 1 = Normal 2 = Audiobook 5 = Whacked Bookmark 6 = Music Video 9 = Short Film 10 = TV Show 11 = Booklet 14 = Ringtone
- BOX_pcst = FOUR_CHAR_INT( 'p', 'c', 's', 't'), // podcast (byte)
- BOX_catg = FOUR_CHAR_INT( 'c', 'a', 't', 'g'), // category
- BOX_keyw = FOUR_CHAR_INT( 'k', 'e', 'y', 'w'), // keyword
- BOX_purl = FOUR_CHAR_INT( 'p', 'u', 'r', 'l'), // podcast URL (byte)
- BOX_egid = FOUR_CHAR_INT( 'e', 'g', 'i', 'd'), // episode global unique ID (byte)
- BOX_desc = FOUR_CHAR_INT( 'd', 'e', 's', 'c'), // description
- BOX_clyr = FOUR_CHAR_INT( '\xa9', 'l', 'y', 'r'), // lyrics (may be > 255 bytes)
- BOX_tven = FOUR_CHAR_INT( 't', 'v', 'e', 'n'), // tv episode number
- BOX_tves = FOUR_CHAR_INT( 't', 'v', 'e', 's'), // tv episode (byte)
- BOX_tvnn = FOUR_CHAR_INT( 't', 'v', 'n', 'n'), // tv network name
- BOX_tvsh = FOUR_CHAR_INT( 't', 'v', 's', 'h'), // tv show name
- BOX_tvsn = FOUR_CHAR_INT( 't', 'v', 's', 'n'), // tv season (byte)
- BOX_purd = FOUR_CHAR_INT( 'p', 'u', 'r', 'd'), // purchase date
- BOX_pgap = FOUR_CHAR_INT( 'p', 'g', 'a', 'p'), // Gapless Playback (byte)
- //BOX_aart = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ), // Album artist
- BOX_cART = FOUR_CHAR_INT( '\xa9', 'A', 'R', 'T'), // artist
- BOX_gnre = FOUR_CHAR_INT( 'g', 'n', 'r', 'e'),
- // 3GPP metatags (http://cpansearch.perl.org/src/JHAR/MP4-Info-1.12/Info.pm)
- BOX_auth = FOUR_CHAR_INT( 'a', 'u', 't', 'h'), // author
- BOX_titl = FOUR_CHAR_INT( 't', 'i', 't', 'l'), // title
- BOX_dscp = FOUR_CHAR_INT( 'd', 's', 'c', 'p'), // description
- BOX_perf = FOUR_CHAR_INT( 'p', 'e', 'r', 'f'), // performer
- BOX_mean = FOUR_CHAR_INT( 'm', 'e', 'a', 'n'), //
- BOX_name = FOUR_CHAR_INT( 'n', 'a', 'm', 'e'), //
- BOX_data = FOUR_CHAR_INT( 'd', 'a', 't', 'a'), //
- // these from http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2008-September/053151.html
- BOX_albm = FOUR_CHAR_INT( 'a', 'l', 'b', 'm'), // album
- BOX_yrrc = FOUR_CHAR_INT( 'y', 'r', 'r', 'c') // album
- };
- // Video track : 'vide'
- #define MP4E_HANDLER_TYPE_VIDE 0x76696465
- // Audio track : 'soun'
- #define MP4E_HANDLER_TYPE_SOUN 0x736F756E
- // General MPEG-4 systems streams (without specific handler).
- // Used for private stream, as suggested in http://www.mp4ra.org/handler.html
- #define MP4E_HANDLER_TYPE_GESM 0x6765736D
- typedef struct
- {
- boxsize_t size;
- boxsize_t offset;
- unsigned duration;
- unsigned flag_random_access;
- } sample_t;
- typedef struct {
- unsigned char *data;
- int bytes;
- int capacity;
- } minimp4_vector_t;
- typedef struct
- {
- MP4E_track_t info;
- minimp4_vector_t smpl; // sample descriptor
- minimp4_vector_t pending_sample;
- minimp4_vector_t vsps; // or dsi for audio
- minimp4_vector_t vpps; // not used for audio
- minimp4_vector_t vvps; // used for HEVC
- } track_t;
- typedef struct MP4E_mux_tag
- {
- minimp4_vector_t tracks;
- int64_t write_pos;
- int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token);
- void *token;
- char *text_comment;
- int sequential_mode_flag;
- int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode
- int fragments_count; // # of fragments in 'fragmentation' mode
- } MP4E_mux_t;
- static const unsigned char box_ftyp[] = {
- #if 1
- 0,0,0,0x18,'f','t','y','p',
- 'm','p','4','2',0,0,0,0,
- 'm','p','4','2','i','s','o','m',
- #else
- // as in ffmpeg
- 0,0,0,0x20,'f','t','y','p',
- 'i','s','o','m',0,0,2,0,
- 'm','p','4','1','i','s','o','m',
- 'i','s','o','2','a','v','c','1',
- #endif
- };
- /**
- * Endian-independent byte-write macros
- */
- #define WR(x, n) *p++ = (unsigned char)((x) >> 8*n)
- #define WRITE_1(x) WR(x, 0);
- #define WRITE_2(x) WR(x, 1); WR(x, 0);
- #define WRITE_3(x) WR(x, 2); WR(x, 1); WR(x, 0);
- #define WRITE_4(x) WR(x, 3); WR(x, 2); WR(x, 1); WR(x, 0);
- #define WR4(p, x) (p)[0] = (char)((x) >> 8*3); (p)[1] = (char)((x) >> 8*2); (p)[2] = (char)((x) >> 8*1); (p)[3] = (char)((x));
- // Finish atom: update atom size field
- #define END_ATOM --stack; WR4((unsigned char*)*stack, p - *stack);
- // Initiate atom: save position of size field on stack
- #define ATOM(x) *stack++ = p; p += 4; WRITE_4(x);
- // Atom with 'FullAtomVersionFlags' field
- #define ATOM_FULL(x, flag) ATOM(x); WRITE_4(flag);
- #define ERR(func) { int err = func; if (err) return err; }
- /**
- Allocate vector with given size, return 1 on success, 0 on fail
- */
- static int minimp4_vector_init(minimp4_vector_t *h, int capacity)
- {
- h->bytes = 0;
- h->capacity = capacity;
- h->data = capacity ? (unsigned char *)malloc(capacity) : NULL;
- return !capacity || !!h->data;
- }
- /**
- Deallocates vector memory
- */
- static void minimp4_vector_reset(minimp4_vector_t *h)
- {
- if (h->data)
- free(h->data);
- memset(h, 0, sizeof(minimp4_vector_t));
- }
- /**
- Reallocate vector memory to the given size
- */
- static int minimp4_vector_grow(minimp4_vector_t *h, int bytes)
- {
- void *p;
- int new_size = h->capacity*2 + 1024;
- if (new_size < h->capacity + bytes)
- new_size = h->capacity + bytes + 1024;
- p = realloc(h->data, new_size);
- if (!p)
- return 0;
- h->data = (unsigned char*)p;
- h->capacity = new_size;
- return 1;
- }
- /**
- Allocates given number of bytes at the end of vector data, increasing
- vector memory if necessary.
- Return allocated memory.
- */
- static unsigned char *minimp4_vector_alloc_tail(minimp4_vector_t *h, int bytes)
- {
- unsigned char *p;
- if (!h->data && !minimp4_vector_init(h, 2*bytes + 1024))
- return NULL;
- if ((h->capacity - h->bytes) < bytes && !minimp4_vector_grow(h, bytes))
- return NULL;
- assert(h->data);
- assert((h->capacity - h->bytes) >= bytes);
- p = h->data + h->bytes;
- h->bytes += bytes;
- return p;
- }
- /**
- Append data to the end of the vector (accumulate ot enqueue)
- */
- static unsigned char *minimp4_vector_put(minimp4_vector_t *h, const void *buf, int bytes)
- {
- unsigned char *tail = minimp4_vector_alloc_tail(h, bytes);
- if (tail)
- memcpy(tail, buf, bytes);
- return tail;
- }
- /**
- * Allocates and initialize mp4 multiplexer
- * return multiplexor handle on success; NULL on failure
- */
- MP4E_mux_t *MP4E_open(int sequential_mode_flag, int enable_fragmentation, void *token,
- int (*write_callback)(int64_t offset, const void *buffer, size_t size, void *token))
- {
- if (write_callback(0, box_ftyp, sizeof(box_ftyp), token)) // Write fixed header: 'ftyp' box
- return 0;
- MP4E_mux_t *mux = (MP4E_mux_t*)malloc(sizeof(MP4E_mux_t));
- if (!mux)
- return mux;
- mux->sequential_mode_flag = sequential_mode_flag || enable_fragmentation;
- mux->enable_fragmentation = enable_fragmentation;
- mux->fragments_count = 0;
- mux->write_callback = write_callback;
- mux->token = token;
- mux->text_comment = NULL;
- mux->write_pos = sizeof(box_ftyp);
- if (!mux->sequential_mode_flag)
- { // Write filler, which would be updated later
- if (mux->write_callback(mux->write_pos, box_ftyp, 8, mux->token))
- {
- free(mux);
- return 0;
- }
- mux->write_pos += 16; // box_ftyp + box_free for 32bit or 64bit size encoding
- }
- minimp4_vector_init(&mux->tracks, 2*sizeof(track_t));
- return mux;
- }
- /**
- * Add new track
- */
- int MP4E_add_track(MP4E_mux_t *mux, const MP4E_track_t *track_data)
- {
- track_t *tr;
- int ntr = mux->tracks.bytes / sizeof(track_t);
- if (!mux || !track_data)
- return MP4E_STATUS_BAD_ARGUMENTS;
- tr = (track_t*)minimp4_vector_alloc_tail(&mux->tracks, sizeof(track_t));
- if (!tr)
- return MP4E_STATUS_NO_MEMORY;
- memset(tr, 0, sizeof(track_t));
- memcpy(&tr->info, track_data, sizeof(*track_data));
- if (!minimp4_vector_init(&tr->smpl, 256))
- return MP4E_STATUS_NO_MEMORY;
- minimp4_vector_init(&tr->vsps, 0);
- minimp4_vector_init(&tr->vpps, 0);
- minimp4_vector_init(&tr->pending_sample, 0);
- return ntr;
- }
- static const unsigned char *next_dsi(const unsigned char *p, const unsigned char *end, int *bytes)
- {
- if (p < end + 2)
- {
- *bytes = p[0]*256 + p[1];
- return p + 2;
- } else
- return NULL;
- }
- static int append_mem(minimp4_vector_t *v, const void *mem, int bytes)
- {
- int i;
- unsigned char size[2];
- const unsigned char *p = v->data;
- for (i = 0; i + 2 < v->bytes;)
- {
- int cb = p[i]*256 + p[i + 1];
- if (cb == bytes && !memcmp(p + i + 2, mem, cb))
- return 1;
- i += 2 + cb;
- }
- size[0] = bytes >> 8;
- size[1] = bytes;
- return minimp4_vector_put(v, size, 2) && minimp4_vector_put(v, mem, bytes);
- }
- static int items_count(minimp4_vector_t *v)
- {
- int i, count = 0;
- const unsigned char *p = v->data;
- for (i = 0; i + 2 < v->bytes;)
- {
- int cb = p[i]*256 + p[i + 1];
- count++;
- i += 2 + cb;
- }
- return count;
- }
- int MP4E_set_dsi(MP4E_mux_t *mux, int track_id, const void *dsi, int bytes)
- {
- track_t* tr = ((track_t*)mux->tracks.data) + track_id;
- assert(tr->info.track_media_kind == e_audio ||
- tr->info.track_media_kind == e_private);
- if (tr->vsps.bytes)
- return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; // only one DSI allowed
- return append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
- }
- int MP4E_set_vps(MP4E_mux_t *mux, int track_id, const void *vps, int bytes)
- {
- track_t* tr = ((track_t*)mux->tracks.data) + track_id;
- assert(tr->info.track_media_kind == e_video);
- return append_mem(&tr->vvps, vps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
- }
- int MP4E_set_sps(MP4E_mux_t *mux, int track_id, const void *sps, int bytes)
- {
- track_t* tr = ((track_t*)mux->tracks.data) + track_id;
- assert(tr->info.track_media_kind == e_video);
- return append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
- }
- int MP4E_set_pps(MP4E_mux_t *mux, int track_id, const void *pps, int bytes)
- {
- track_t* tr = ((track_t*)mux->tracks.data) + track_id;
- assert(tr->info.track_media_kind == e_video);
- return append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY;
- }
- static unsigned get_duration(const track_t *tr)
- {
- unsigned i, sum_duration = 0;
- const sample_t *s = (const sample_t *)tr->smpl.data;
- for (i = 0; i < tr->smpl.bytes/sizeof(sample_t); i++)
- {
- sum_duration += s[i].duration;
- }
- return sum_duration;
- }
- static int write_pending_data(MP4E_mux_t *mux, track_t *tr)
- {
- // if have pending sample && have at least one sample in the index
- if (tr->pending_sample.bytes > 0 && tr->smpl.bytes >= sizeof(sample_t))
- {
- // Complete pending sample
- sample_t *smpl_desc;
- unsigned char base[8], *p = base;
- assert(mux->sequential_mode_flag);
- // Write each sample to a separate atom
- assert(mux->sequential_mode_flag); // Separate atom needed for sequential_mode only
- WRITE_4(tr->pending_sample.bytes + 8);
- WRITE_4(BOX_mdat);
- ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
- mux->write_pos += p - base;
- // Update sample descriptor with size and offset
- smpl_desc = ((sample_t*)minimp4_vector_alloc_tail(&tr->smpl, 0)) - 1;
- smpl_desc->size = tr->pending_sample.bytes;
- smpl_desc->offset = (boxsize_t)mux->write_pos;
- // Write data
- ERR(mux->write_callback(mux->write_pos, tr->pending_sample.data, tr->pending_sample.bytes, mux->token));
- mux->write_pos += tr->pending_sample.bytes;
- // reset buffer
- tr->pending_sample.bytes = 0;
- }
- return MP4E_STATUS_OK;
- }
- static int add_sample_descriptor(MP4E_mux_t *mux, track_t *tr, int data_bytes, int duration, int kind)
- {
- sample_t smp;
- smp.size = data_bytes;
- smp.offset = (boxsize_t)mux->write_pos;
- smp.duration = (duration ? duration : tr->info.default_duration);
- smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS);
- return NULL != minimp4_vector_put(&tr->smpl, &smp, sizeof(sample_t));
- }
- static int mp4e_flush_index(MP4E_mux_t *mux);
- /**
- * Write Movie Fragment: 'moof' box
- */
- static int mp4e_write_fragment_header(MP4E_mux_t *mux, int track_num, int data_bytes, int duration, int kind
- #if MP4D_TFDT_SUPPORT
- , uint64_t timestamp
- #endif
- )
- {
- unsigned char base[888], *p = base;
- unsigned char *stack_base[20]; // atoms nesting stack
- unsigned char **stack = stack_base;
- unsigned char *pdata_offset;
- unsigned flags;
- enum
- {
- default_sample_duration_present = 0x000008,
- default_sample_flags_present = 0x000020,
- } e;
- track_t *tr = ((track_t*)mux->tracks.data) + track_num;
- ATOM(BOX_moof)
- ATOM_FULL(BOX_mfhd, 0)
- WRITE_4(mux->fragments_count); // start from 1
- END_ATOM
- ATOM(BOX_traf)
- flags = 0;
- if (tr->info.track_media_kind == e_video)
- flags |= 0x20; // default-sample-flags-present
- else
- flags |= 0x08; // default-sample-duration-present
- flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008;
- ATOM_FULL(BOX_tfhd, flags)
- WRITE_4(track_num + 1); // track_ID
- if (tr->info.track_media_kind == e_video)
- {
- WRITE_4(0x1010000); // default_sample_flags
- } else
- {
- WRITE_4(duration);
- }
- END_ATOM
- #if MP4D_TFDT_SUPPORT
- ATOM_FULL(BOX_tfdt, 0x01000000) // version 1
- WRITE_4(timestamp >> 32); // upper timestamp
- WRITE_4(timestamp & 0xffffffff); // lower timestamp
- END_ATOM
- #endif
- if (tr->info.track_media_kind == e_audio)
- {
- flags = 0;
- flags |= 0x001; // data-offset-present
- flags |= 0x200; // sample-size-present
- ATOM_FULL(BOX_trun, flags)
- WRITE_4(1); // sample_count
- pdata_offset = p; p += 4; // save ptr to data_offset
- WRITE_4(data_bytes);// sample_size
- END_ATOM
- } else if (kind == MP4E_SAMPLE_RANDOM_ACCESS)
- {
- flags = 0;
- flags |= 0x001; // data-offset-present
- flags |= 0x004; // first-sample-flags-present
- flags |= 0x100; // sample-duration-present
- flags |= 0x200; // sample-size-present
- ATOM_FULL(BOX_trun, flags)
- WRITE_4(1); // sample_count
- pdata_offset = p; p += 4; // save ptr to data_offset
- WRITE_4(0x2000000); // first_sample_flags
- WRITE_4(duration); // sample_duration
- WRITE_4(data_bytes);// sample_size
- END_ATOM
- } else
- {
- flags = 0;
- flags |= 0x001; // data-offset-present
- flags |= 0x100; // sample-duration-present
- flags |= 0x200; // sample-size-present
- ATOM_FULL(BOX_trun, flags)
- WRITE_4(1); // sample_count
- pdata_offset = p; p += 4; // save ptr to data_offset
- WRITE_4(duration); // sample_duration
- WRITE_4(data_bytes);// sample_size
- END_ATOM
- }
- END_ATOM
- END_ATOM
- WR4(pdata_offset, (p - base) + 8);
- ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
- mux->write_pos += p - base;
- return MP4E_STATUS_OK;
- }
- static int mp4e_write_mdat_box(MP4E_mux_t *mux, uint32_t size)
- {
- unsigned char base[8], *p = base;
- WRITE_4(size);
- WRITE_4(BOX_mdat);
- ERR(mux->write_callback(mux->write_pos, base, p - base, mux->token));
- mux->write_pos += p - base;
- return MP4E_STATUS_OK;
- }
- /**
- * Add new sample to specified track
- */
- int MP4E_put_sample(MP4E_mux_t *mux, int track_num, const void *data, int data_bytes, int duration, int kind)
- {
- track_t *tr;
- if (!mux || !data)
- return MP4E_STATUS_BAD_ARGUMENTS;
- tr = ((track_t*)mux->tracks.data) + track_num;
- if (mux->enable_fragmentation)
- {
- #if MP4D_TFDT_SUPPORT
- // NOTE: assume a constant `duration` to calculate current timestamp
- uint64_t timestamp = (uint64_t)mux->fragments_count * duration;
- #endif
- if (!mux->fragments_count++)
- ERR(mp4e_flush_index(mux)); // write file headers before 1st sample
- // write MOOF + MDAT + sample data
- #if MP4D_TFDT_SUPPORT
- ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind, timestamp));
- #else
- ERR(mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind));
- #endif
- // write MDAT box for each sample
- ERR(mp4e_write_mdat_box(mux, data_bytes + 8));
- ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token));
- mux->write_pos += data_bytes;
- return MP4E_STATUS_OK;
- }
- if (kind != MP4E_SAMPLE_CONTINUATION)
- {
- if (mux->sequential_mode_flag)
- ERR(write_pending_data(mux, tr));
- if (!add_sample_descriptor(mux, tr, data_bytes, duration, kind))
- return MP4E_STATUS_NO_MEMORY;
- } else
- {
- if (!mux->sequential_mode_flag)
- {
- sample_t *smpl_desc;
- if (tr->smpl.bytes < sizeof(sample_t))
- return MP4E_STATUS_NO_MEMORY; // write continuation, but there are no samples in the index
- // Accumulate size of the continuation in the sample descriptor
- smpl_desc = (sample_t*)(tr->smpl.data + tr->smpl.bytes) - 1;
- smpl_desc->size += data_bytes;
- }
- }
- if (mux->sequential_mode_flag)
- {
- if (!minimp4_vector_put(&tr->pending_sample, data, data_bytes))
- return MP4E_STATUS_NO_MEMORY;
- } else
- {
- ERR(mux->write_callback(mux->write_pos, data, data_bytes, mux->token));
- mux->write_pos += data_bytes;
- }
- return MP4E_STATUS_OK;
- }
- /**
- * calculate size of length field of OD box
- */
- static int od_size_of_size(int size)
- {
- int i, size_of_size = 1;
- for (i = size; i > 0x7F; i -= 0x7F)
- size_of_size++;
- return size_of_size;
- }
- /**
- * Add or remove MP4 file text comment according to Apple specs:
- * https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1
- * http://atomicparsley.sourceforge.net/mpeg-4files.html
- * note that ISO did not specify comment format.
- */
- int MP4E_set_text_comment(MP4E_mux_t *mux, const char *comment)
- {
- if (!mux || !comment)
- return MP4E_STATUS_BAD_ARGUMENTS;
- if (mux->text_comment)
- free(mux->text_comment);
- mux->text_comment = strdup(comment);
- if (!mux->text_comment)
- return MP4E_STATUS_NO_MEMORY;
- return MP4E_STATUS_OK;
- }
- /**
- * Write file index 'moov' box with all its boxes and indexes
- */
- static int mp4e_flush_index(MP4E_mux_t *mux)
- {
- unsigned char *stack_base[20]; // atoms nesting stack
- unsigned char **stack = stack_base;
- unsigned char *base, *p;
- unsigned int ntr, index_bytes, ntracks = mux->tracks.bytes / sizeof(track_t);
- int i, err;
- // How much memory needed for indexes
- // Experimental data:
- // file with 1 track = 560 bytes
- // file with 2 tracks = 972 bytes
- // track size = 412 bytes;
- // file header size = 148 bytes
- #define FILE_HEADER_BYTES 256
- #define TRACK_HEADER_BYTES 512
- index_bytes = FILE_HEADER_BYTES;
- if (mux->text_comment)
- index_bytes += 128 + strlen(mux->text_comment);
- for (ntr = 0; ntr < ntracks; ntr++)
- {
- track_t *tr = ((track_t*)mux->tracks.data) + ntr;
- index_bytes += TRACK_HEADER_BYTES; // fixed amount (implementation-dependent)
- // may need extra 4 bytes for duration field + 4 bytes for worst-case random access box
- index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t);
- index_bytes += tr->vsps.bytes;
- index_bytes += tr->vpps.bytes;
- ERR(write_pending_data(mux, tr));
- }
- base = (unsigned char*)malloc(index_bytes);
- if (!base)
- return MP4E_STATUS_NO_MEMORY;
- p = base;
- if (!mux->sequential_mode_flag)
- {
- // update size of mdat box.
- // One of 2 points, which requires random file access.
- // Second is optional duration update at beginning of file in fragmentation mode.
- // This can be avoided using "till eof" size code, but in this case indexes must be
- // written before the mdat....
- int64_t size = mux->write_pos - sizeof(box_ftyp);
- const int64_t size_limit = (int64_t)(uint64_t)0xfffffffe;
- if (size > size_limit)
- {
- WRITE_4(1);
- WRITE_4(BOX_mdat);
- WRITE_4((size >> 32) & 0xffffffff);
- WRITE_4(size & 0xffffffff);
- } else
- {
- WRITE_4(8);
- WRITE_4(BOX_free);
- WRITE_4(size - 8);
- WRITE_4(BOX_mdat);
- }
- ERR(mux->write_callback(sizeof(box_ftyp), base, p - base, mux->token));
- p = base;
- }
- // Write index atoms; order taken from Table 1 of [1]
- #define MOOV_TIMESCALE 1000
- ATOM(BOX_moov);
- ATOM_FULL(BOX_mvhd, 0);
- WRITE_4(0); // creation_time
- WRITE_4(0); // modification_time
- if (ntracks)
- {
- track_t *tr = ((track_t*)mux->tracks.data) + 0; // take 1st track
- unsigned duration = get_duration(tr);
- duration = (unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale);
- WRITE_4(MOOV_TIMESCALE); // duration
- WRITE_4(duration); // duration
- }
- WRITE_4(0x00010000); // rate
- WRITE_2(0x0100); // volume
- WRITE_2(0); // reserved
- WRITE_4(0); // reserved
- WRITE_4(0); // reserved
- // matrix[9]
- WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0);
- WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0);
- WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000);
- // pre_defined[6]
- WRITE_4(0); WRITE_4(0); WRITE_4(0);
- WRITE_4(0); WRITE_4(0); WRITE_4(0);
- //next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be
- //added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be
- //larger than the largest track-ID in use.
- WRITE_4(ntracks + 1);
- END_ATOM;
- for (ntr = 0; ntr < ntracks; ntr++)
- {
- track_t *tr = ((track_t*)mux->tracks.data) + ntr;
- unsigned duration = get_duration(tr);
- int samples_count = tr->smpl.bytes / sizeof(sample_t);
- const sample_t *sample = (const sample_t *)tr->smpl.data;
- unsigned handler_type;
- const char *handler_ascii = NULL;
- if (mux->enable_fragmentation)
- samples_count = 0;
- else if (samples_count <= 0)
- continue; // skip empty track
- switch (tr->info.track_media_kind)
- {
- case e_audio:
- handler_type = MP4E_HANDLER_TYPE_SOUN;
- handler_ascii = "SoundHandler";
- break;
- case e_video:
- handler_type = MP4E_HANDLER_TYPE_VIDE;
- handler_ascii = "VideoHandler";
- break;
- case e_private:
- handler_type = MP4E_HANDLER_TYPE_GESM;
- break;
- default:
- return MP4E_STATUS_BAD_ARGUMENTS;
- }
- ATOM(BOX_trak);
- ATOM_FULL(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview
- WRITE_4(0); // creation_time
- WRITE_4(0); // modification_time
- WRITE_4(ntr + 1); // track_ID
- WRITE_4(0); // reserved
- WRITE_4((unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale));
- WRITE_4(0); WRITE_4(0); // reserved[2]
- WRITE_2(0); // layer
- WRITE_2(0); // alternate_group
- WRITE_2(0x0100); // volume {if track_is_audio 0x0100 else 0};
- WRITE_2(0); // reserved
- // matrix[9]
- WRITE_4(0x00010000); WRITE_4(0); WRITE_4(0);
- WRITE_4(0); WRITE_4(0x00010000); WRITE_4(0);
- WRITE_4(0); WRITE_4(0); WRITE_4(0x40000000);
- if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private)
- {
- WRITE_4(0); // width
- WRITE_4(0); // height
- } else
- {
- WRITE_4(tr->info.u.v.width*0x10000); // width
- WRITE_4(tr->info.u.v.height*0x10000); // height
- }
- END_ATOM;
- ATOM(BOX_mdia);
- ATOM_FULL(BOX_mdhd, 0);
- WRITE_4(0); // creation_time
- WRITE_4(0); // modification_time
- WRITE_4(tr->info.time_scale);
- WRITE_4(duration); // duration
- {
- int lang_code = ((tr->info.language[0] & 31) << 10) | ((tr->info.language[1] & 31) << 5) | (tr->info.language[2] & 31);
- WRITE_2(lang_code); // language
- }
- WRITE_2(0); // pre_defined
- END_ATOM;
- ATOM_FULL(BOX_hdlr, 0);
- WRITE_4(0); // pre_defined
- WRITE_4(handler_type); // handler_type
- WRITE_4(0); WRITE_4(0); WRITE_4(0); // reserved[3]
- // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes).
- // set mdia hdlr name field to what quicktime uses.
- // Sony smartphone may fail to decode short files w/o handler name
- if (handler_ascii)
- {
- for (i = 0; i < (int)strlen(handler_ascii) + 1; i++)
- {
- WRITE_1(handler_ascii[i]);
- }
- } else
- {
- WRITE_4(0);
- }
- END_ATOM;
- ATOM(BOX_minf);
- if (tr->info.track_media_kind == e_audio)
- {
- // Sound Media Header Box
- ATOM_FULL(BOX_smhd, 0);
- WRITE_2(0); // balance
- WRITE_2(0); // reserved
- END_ATOM;
- }
- if (tr->info.track_media_kind == e_video)
- {
- // mandatory Video Media Header Box
- ATOM_FULL(BOX_vmhd, 1);
- WRITE_2(0); // graphicsmode
- WRITE_2(0); WRITE_2(0); WRITE_2(0); // opcolor[3]
- END_ATOM;
- }
- ATOM(BOX_dinf);
- ATOM_FULL(BOX_dref, 0);
- WRITE_4(1); // entry_count
- // If the flag is set indicating that the data is in the same file as this box, then no string (not even an empty one)
- // shall be supplied in the entry field.
- // ASP the correct way to avoid supply the string, is to use flag 1
- // otherwise ISO reference demux crashes
- ATOM_FULL(BOX_url, 1);
- END_ATOM;
- END_ATOM;
- END_ATOM;
- ATOM(BOX_stbl);
- ATOM_FULL(BOX_stsd, 0);
- WRITE_4(1); // entry_count;
- if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private)
- {
- // AudioSampleEntry() assume MP4E_HANDLER_TYPE_SOUN
- if (tr->info.track_media_kind == e_audio)
- {
- ATOM(BOX_mp4a);
- } else
- {
- ATOM(BOX_mp4s);
- }
- // SampleEntry
- WRITE_4(0); WRITE_2(0); // reserved[6]
- WRITE_2(1); // data_reference_index; - this is a tag for descriptor below
- if (tr->info.track_media_kind == e_audio)
- {
- // AudioSampleEntry
- WRITE_4(0); WRITE_4(0); // reserved[2]
- WRITE_2(tr->info.u.a.channelcount); // channelcount
- WRITE_2(16); // samplesize
- WRITE_4(0); // pre_defined+reserved
- WRITE_4((tr->info.time_scale << 16)); // samplerate == = {timescale of media}<<16;
- }
- ATOM_FULL(BOX_esds, 0);
- if (tr->vsps.bytes > 0)
- {
- int dsi_bytes = tr->vsps.bytes - 2; // - two bytes size field
- int dsi_size_size = od_size_of_size(dsi_bytes);
- int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1 + 1 + 3 + 4 + 4);
- int dcd_size_size = od_size_of_size(dcd_bytes);
- int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3;
- #define WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WRITE_1(0x00ff); } while (size > 0x7F); WRITE_1(size)
- WRITE_1(3); // OD_ESD
- WRITE_OD_LEN(esd_bytes);
- WRITE_2(0); // ES_ID(2) // TODO - what is this?
- WRITE_1(0); // flags(1)
- WRITE_1(4); // OD_DCD
- WRITE_OD_LEN(dcd_bytes);
- if (tr->info.track_media_kind == e_audio)
- {
- WRITE_1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD
- WRITE_1(5 << 2); // stream_type == AudioStream
- } else
- {
- // http://xhelmboyx.tripod.com/formats/mp4-layout.txt
- WRITE_1(208); // 208 = private video
- WRITE_1(32 << 2); // stream_type == user private
- }
- WRITE_3(tr->info.u.a.channelcount * 6144/8); // bufferSizeDB in bytes, constant as in reference decoder
- WRITE_4(0); // maxBitrate TODO
- WRITE_4(0); // avg_bitrate_bps TODO
- WRITE_1(5); // OD_DSI
- WRITE_OD_LEN(dsi_bytes);
- for (i = 0; i < dsi_bytes; i++)
- {
- WRITE_1(tr->vsps.data[2 + i]);
- }
- }
- END_ATOM;
- END_ATOM;
- }
- if (tr->info.track_media_kind == e_video && (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication || MP4_OBJECT_TYPE_HEVC == tr->info.object_type_indication))
- {
- int numOfSequenceParameterSets = items_count(&tr->vsps);
- int numOfPictureParameterSets = items_count(&tr->vpps);
- if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication)
- {
- ATOM(BOX_avc1);
- } else
- {
- ATOM(BOX_hvc1);
- }
- // VisualSampleEntry 8.16.2
- // extends SampleEntry
- WRITE_2(0); // reserved
- WRITE_2(0); // reserved
- WRITE_2(0); // reserved
- WRITE_2(1); // data_reference_index
- WRITE_2(0); // pre_defined
- WRITE_2(0); // reserved
- WRITE_4(0); // pre_defined
- WRITE_4(0); // pre_defined
- WRITE_4(0); // pre_defined
- WRITE_2(tr->info.u.v.width);
- WRITE_2(tr->info.u.v.height);
- WRITE_4(0x00480000); // horizresolution = 72 dpi
- WRITE_4(0x00480000); // vertresolution = 72 dpi
- WRITE_4(0); // reserved
- WRITE_2(1); // frame_count
- for (i = 0; i < 32; i++)
- {
- WRITE_1(0); // compressorname
- }
- WRITE_2(24); // depth
- WRITE_2(-1); // pre_defined
- if (MP4_OBJECT_TYPE_AVC == tr->info.object_type_indication)
- {
- ATOM(BOX_avcC);
- // AVCDecoderConfigurationRecord 5.2.4.1.1
- WRITE_1(1); // configurationVersion
- WRITE_1(tr->vsps.data[2 + 1]);
- WRITE_1(tr->vsps.data[2 + 2]);
- WRITE_1(tr->vsps.data[2 + 3]);
- WRITE_1(255); // 0xfc + NALU_len - 1
- WRITE_1(0xe0 | numOfSequenceParameterSets);
- for (i = 0; i < tr->vsps.bytes; i++)
- {
- WRITE_1(tr->vsps.data[i]);
- }
- WRITE_1(numOfPictureParameterSets);
- for (i = 0; i < tr->vpps.bytes; i++)
- {
- WRITE_1(tr->vpps.data[i]);
- }
- } else
- {
- int numOfVPS = items_count(&tr->vpps);
- ATOM(BOX_hvcC);
- // TODO: read actual params from stream
- WRITE_1(1); // configurationVersion
- WRITE_1(1); // Profile Space (2), Tier (1), Profile (5)
- WRITE_4(0x60000000); // Profile Compatibility
- WRITE_2(0); // progressive, interlaced, non packed constraint, frame only constraint flags
- WRITE_4(0); // constraint indicator flags
- WRITE_1(0); // level_idc
- WRITE_2(0xf000); // Min Spatial Segmentation
- WRITE_1(0xfc); // Parallelism Type
- WRITE_1(0xfc); // Chroma Format
- WRITE_1(0xf8); // Luma Depth
- WRITE_1(0xf8); // Chroma Depth
- WRITE_2(0); // Avg Frame Rate
- WRITE_1(3); // ConstantFrameRate (2), NumTemporalLayers (3), TemporalIdNested (1), LengthSizeMinusOne (2)
- WRITE_1(3); // Num Of Arrays
- WRITE_1((1 << 7) | (HEVC_NAL_VPS & 0x3f)); // Array Completeness + NAL Unit Type
- WRITE_2(numOfVPS);
- for (i = 0; i < tr->vvps.bytes; i++)
- {
- WRITE_1(tr->vvps.data[i]);
- }
- WRITE_1((1 << 7) | (HEVC_NAL_SPS & 0x3f));
- WRITE_2(numOfSequenceParameterSets);
- for (i = 0; i < tr->vsps.bytes; i++)
- {
- WRITE_1(tr->vsps.data[i]);
- }
- WRITE_1((1 << 7) | (HEVC_NAL_PPS & 0x3f));
- WRITE_2(numOfPictureParameterSets);
- for (i = 0; i < tr->vpps.bytes; i++)
- {
- WRITE_1(tr->vpps.data[i]);
- }
- }
- END_ATOM;
- END_ATOM;
- }
- END_ATOM;
- /************************************************************************/
- /* indexes */
- /************************************************************************/
- // Time to Sample Box
- ATOM_FULL(BOX_stts, 0);
- {
- unsigned char *pentry_count = p;
- int cnt = 1, entry_count = 0;
- WRITE_4(0);
- for (i = 0; i < samples_count; i++, cnt++)
- {
- if (i == (samples_count - 1) || sample[i].duration != sample[i + 1].duration)
- {
- WRITE_4(cnt);
- WRITE_4(sample[i].duration);
- cnt = 0;
- entry_count++;
- }
- }
- WR4(pentry_count, entry_count);
- }
- END_ATOM;
- // Sample To Chunk Box
- ATOM_FULL(BOX_stsc, 0);
- if (mux->enable_fragmentation)
- {
- WRITE_4(0); // entry_count
- } else
- {
- WRITE_4(1); // entry_count
- WRITE_4(1); // first_chunk;
- WRITE_4(1); // samples_per_chunk;
- WRITE_4(1); // sample_description_index;
- }
- END_ATOM;
- // Sample Size Box
- ATOM_FULL(BOX_stsz, 0);
- WRITE_4(0); // sample_size If this field is set to 0, then the samples have different sizes, and those sizes
- // are stored in the sample size table.
- WRITE_4(samples_count); // sample_count;
- for (i = 0; i < samples_count; i++)
- {
- WRITE_4(sample[i].size);
- }
- END_ATOM;
- // Chunk Offset Box
- int is_64_bit = 0;
- if (samples_count && sample[samples_count - 1].offset > 0xffffffff)
- is_64_bit = 1;
- if (!is_64_bit)
- {
- ATOM_FULL(BOX_stco, 0);
- WRITE_4(samples_count);
- for (i = 0; i < samples_count; i++)
- {
- WRITE_4(sample[i].offset);
- }
- } else
- {
- ATOM_FULL(BOX_co64, 0);
- WRITE_4(samples_count);
- for (i = 0; i < samples_count; i++)
- {
- WRITE_4((sample[i].offset >> 32) & 0xffffffff);
- WRITE_4(sample[i].offset & 0xffffffff);
- }
- }
- END_ATOM;
- // Sync Sample Box
- {
- int ra_count = 0;
- for (i = 0; i < samples_count; i++)
- {
- ra_count += !!sample[i].flag_random_access;
- }
- if (ra_count != samples_count)
- {
- // If the sync sample box is not present, every sample is a random access point.
- ATOM_FULL(BOX_stss, 0);
- WRITE_4(ra_count);
- for (i = 0; i < samples_count; i++)
- {
- if (sample[i].flag_random_access)
- {
- WRITE_4(i + 1);
- }
- }
- END_ATOM;
- }
- }
- END_ATOM;
- END_ATOM;
- END_ATOM;
- END_ATOM;
- } // tracks loop
- if (mux->text_comment)
- {
- ATOM(BOX_udta);
- ATOM_FULL(BOX_meta, 0);
- ATOM_FULL(BOX_hdlr, 0);
- WRITE_4(0); // pre_defined
- #define MP4E_HANDLER_TYPE_MDIR 0x6d646972
- WRITE_4(MP4E_HANDLER_TYPE_MDIR); // handler_type
- WRITE_4(0); WRITE_4(0); WRITE_4(0); // reserved[3]
- WRITE_4(0); // name is a null-terminated string in UTF-8 characters which gives a human-readable name for the track type (for debugging and inspection purposes).
- END_ATOM;
- ATOM(BOX_ilst);
- ATOM(BOX_ccmt);
- ATOM(BOX_data);
- WRITE_4(1); // type
- WRITE_4(0); // lang
- for (i = 0; i < (int)strlen(mux->text_comment) + 1; i++)
- {
- WRITE_1(mux->text_comment[i]);
- }
- END_ATOM;
- END_ATOM;
- END_ATOM;
- END_ATOM;
- END_ATOM;
- }
- if (mux->enable_fragmentation)
- {
- track_t *tr = ((track_t*)mux->tracks.data) + 0;
- uint32_t movie_duration = get_duration(tr);
- ATOM(BOX_mvex);
- ATOM_FULL(BOX_mehd, 0);
- WRITE_4(movie_duration); // duration
- END_ATOM;
- for (ntr = 0; ntr < ntracks; ntr++)
- {
- ATOM_FULL(BOX_trex, 0);
- WRITE_4(ntr + 1); // track_ID
- WRITE_4(1); // default_sample_description_index
- WRITE_4(0); // default_sample_duration
- WRITE_4(0); // default_sample_size
- WRITE_4(0); // default_sample_flags
- END_ATOM;
- }
- END_ATOM;
- }
- END_ATOM; // moov atom
- assert((unsigned)(p - base) <= index_bytes);
- err = mux->write_callback(mux->write_pos, base, p - base, mux->token);
- mux->write_pos += p - base;
- free(base);
- return err;
- }
- int MP4E_close(MP4E_mux_t *mux)
- {
- int err = MP4E_STATUS_OK;
- unsigned ntr, ntracks;
- if (!mux)
- return MP4E_STATUS_BAD_ARGUMENTS;
- if (!mux->enable_fragmentation)
- err = mp4e_flush_index(mux);
- if (mux->text_comment)
- free(mux->text_comment);
- ntracks = mux->tracks.bytes / sizeof(track_t);
- for (ntr = 0; ntr < ntracks; ntr++)
- {
- track_t *tr = ((track_t*)mux->tracks.data) + ntr;
- minimp4_vector_reset(&tr->vsps);
- minimp4_vector_reset(&tr->vpps);
- minimp4_vector_reset(&tr->smpl);
- minimp4_vector_reset(&tr->pending_sample);
- }
- minimp4_vector_reset(&mux->tracks);
- free(mux);
- return err;
- }
- typedef uint32_t bs_item_t;
- #define BS_BITS 32
- typedef struct
- {
- // Look-ahead bit cache: MSB aligned, 17 bits guaranteed, zero stuffing
- unsigned int cache;
- // Bit counter = 16 - (number of bits in wCache)
- // cache refilled when cache_free_bits >= 0
- int cache_free_bits;
- // Current read position
- const uint16_t *buf;
- // original data buffer
- const uint16_t *origin;
- // original data buffer length, bytes
- unsigned origin_bytes;
- } bit_reader_t;
- #define LOAD_SHORT(x) ((uint16_t)(x << 8) | (x >> 8))
- static unsigned int show_bits(bit_reader_t *bs, int n)
- {
- unsigned int retval;
- assert(n > 0 && n <= 16);
- retval = (unsigned int)(bs->cache >> (32 - n));
- return retval;
- }
- static void flush_bits(bit_reader_t *bs, int n)
- {
- assert(n >= 0 && n <= 16);
- bs->cache <<= n;
- bs->cache_free_bits += n;
- if (bs->cache_free_bits >= 0)
- {
- bs->cache |= ((uint32_t)LOAD_SHORT(*bs->buf)) << bs->cache_free_bits;
- bs->buf++;
- bs->cache_free_bits -= 16;
- }
- }
- static unsigned int get_bits(bit_reader_t *bs, int n)
- {
- unsigned int retval = show_bits(bs, n);
- flush_bits(bs, n);
- return retval;
- }
- static void set_pos_bits(bit_reader_t *bs, unsigned pos_bits)
- {
- assert((int)pos_bits >= 0);
- bs->buf = bs->origin + pos_bits/16;
- bs->cache = 0;
- bs->cache_free_bits = 16;
- flush_bits(bs, 0);
- flush_bits(bs, pos_bits & 15);
- }
- static unsigned get_pos_bits(const bit_reader_t *bs)
- {
- // Current bitbuffer position =
- // position of next wobits in the internal buffer
- // minus bs, available in bit cache wobits
- unsigned pos_bits = (unsigned)(bs->buf - bs->origin)*16;
- pos_bits -= 16 - bs->cache_free_bits;
- assert((int)pos_bits >= 0);
- return pos_bits;
- }
- static int remaining_bits(const bit_reader_t *bs)
- {
- return bs->origin_bytes * 8 - get_pos_bits(bs);
- }
- static void init_bits(bit_reader_t *bs, const void *data, unsigned data_bytes)
- {
- bs->origin = (const uint16_t *)data;
- bs->origin_bytes = data_bytes;
- set_pos_bits(bs, 0);
- }
- #define GetBits(n) get_bits(bs, n)
- /**
- * Unsigned Golomb code
- */
- static int ue_bits(bit_reader_t *bs)
- {
- int clz;
- int val;
- for (clz = 0; !get_bits(bs, 1); clz++) {}
- //get_bits(bs, clz + 1);
- val = (1 << clz) - 1 + (clz ? get_bits(bs, clz) : 0);
- return val;
- }
- #if MINIMP4_TRANSCODE_SPS_ID
- /**
- * Output bitstream
- */
- typedef struct
- {
- int shift; // bit position in the cache
- uint32_t cache; // bit cache
- bs_item_t *buf; // current position
- bs_item_t *origin; // initial position
- } bs_t;
- #define SWAP32(x) (uint32_t)((((x) >> 24) & 0xFF) | (((x) >> 8) & 0xFF00) | (((x) << 8) & 0xFF0000) | ((x & 0xFF) << 24))
- static void h264e_bs_put_bits(bs_t *bs, unsigned n, unsigned val)
- {
- assert(!(val >> n));
- bs->shift -= n;
- assert((unsigned)n <= 32);
- if (bs->shift < 0)
- {
- assert(-bs->shift < 32);
- bs->cache |= val >> -bs->shift;
- *bs->buf++ = SWAP32(bs->cache);
- bs->shift = 32 + bs->shift;
- bs->cache = 0;
- }
- bs->cache |= val << bs->shift;
- }
- static void h264e_bs_flush(bs_t *bs)
- {
- *bs->buf = SWAP32(bs->cache);
- }
- static unsigned h264e_bs_get_pos_bits(const bs_t *bs)
- {
- unsigned pos_bits = (unsigned)((bs->buf - bs->origin)*BS_BITS);
- pos_bits += BS_BITS - bs->shift;
- assert((int)pos_bits >= 0);
- return pos_bits;
- }
- static unsigned h264e_bs_byte_align(bs_t *bs)
- {
- int pos = h264e_bs_get_pos_bits(bs);
- h264e_bs_put_bits(bs, -pos & 7, 0);
- return pos + (-pos & 7);
- }
- /**
- * Golomb code
- * 0 => 1
- * 1 => 01 0
- * 2 => 01 1
- * 3 => 001 00
- * 4 => 001 01
- *
- * [0] => 1
- * [1..2] => 01x
- * [3..6] => 001xx
- * [7..14] => 0001xxx
- *
- */
- static void h264e_bs_put_golomb(bs_t *bs, unsigned val)
- {
- int size = 0;
- unsigned t = val + 1;
- do
- {
- size++;
- } while (t >>= 1);
- h264e_bs_put_bits(bs, 2*size - 1, val + 1);
- }
- static void h264e_bs_init_bits(bs_t *bs, void *data)
- {
- bs->origin = (bs_item_t*)data;
- bs->buf = bs->origin;
- bs->shift = BS_BITS;
- bs->cache = 0;
- }
- static int find_mem_cache(void *cache[], int cache_bytes[], int cache_size, void *mem, int bytes)
- {
- int i;
- if (!bytes)
- return -1;
- for (i = 0; i < cache_size; i++)
- {
- if (cache_bytes[i] == bytes && !memcmp(mem, cache[i], bytes))
- return i; // found
- }
- for (i = 0; i < cache_size; i++)
- {
- if (!cache_bytes[i])
- {
- cache[i] = malloc(bytes);
- if (cache[i])
- {
- memcpy(cache[i], mem, bytes);
- cache_bytes[i] = bytes;
- }
- return i; // put in
- }
- }
- return -1; // no room
- }
- /**
- * 7.4.1.1. "Encapsulation of an SODB within an RBSP"
- */
- static int remove_nal_escapes(unsigned char *dst, const unsigned char *src, int h264_data_bytes)
- {
- int i = 0, j = 0, zero_cnt = 0;
- for (j = 0; j < h264_data_bytes; j++)
- {
- if (zero_cnt == 2 && src[j] <= 3)
- {
- if (src[j] == 3)
- {
- if (j == h264_data_bytes - 1)
- {
- // cabac_zero_word: no action
- } else if (src[j + 1] <= 3)
- {
- j++;
- zero_cnt = 0;
- } else
- {
- // TODO: assume end-of-nal
- //return 0;
- }
- } else
- return 0;
- }
- dst[i++] = src[j];
- if (src[j])
- zero_cnt = 0;
- else
- zero_cnt++;
- }
- //while (--j > i) src[j] = 0;
- return i;
- }
- /**
- * Put NAL escape codes to the output bitstream
- */
- static int nal_put_esc(uint8_t *d, const uint8_t *s, int n)
- {
- int i, j = 4, cntz = 0;
- d[0] = d[1] = d[2] = 0; d[3] = 1; // start code
- for (i = 0; i < n; i++)
- {
- uint8_t byte = *s++;
- if (cntz == 2 && byte <= 3)
- {
- d[j++] = 3;
- cntz = 0;
- }
- if (byte)
- cntz = 0;
- else
- cntz++;
- d[j++] = byte;
- }
- return j;
- }
- static void copy_bits(bit_reader_t *bs, bs_t *bd)
- {
- unsigned cb, bits;
- int bit_count = remaining_bits(bs);
- while (bit_count > 7)
- {
- cb = MINIMP4_MIN(bit_count - 7, 8);
- bits = GetBits(cb);
- h264e_bs_put_bits(bd, cb, bits);
- bit_count -= cb;
- }
- // cut extra zeros after stop-bit
- bits = GetBits(bit_count);
- for (; bit_count && ~bits & 1; bit_count--)
- {
- bits >>= 1;
- }
- if (bit_count)
- {
- h264e_bs_put_bits(bd, bit_count, bits);
- }
- }
- static int change_sps_id(bit_reader_t *bs, bs_t *bd, int new_id, int *old_id)
- {
- unsigned bits, sps_id, i, bytes;
- for (i = 0; i < 3; i++)
- {
- bits = GetBits(8);
- h264e_bs_put_bits(bd, 8, bits);
- }
- sps_id = ue_bits(bs); // max = 31
- *old_id = sps_id;
- sps_id = new_id;
- assert(sps_id <= 31);
- h264e_bs_put_golomb(bd, sps_id);
- copy_bits(bs, bd);
- bytes = h264e_bs_byte_align(bd) / 8;
- h264e_bs_flush(bd);
- return bytes;
- }
- static int patch_pps(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd, int new_pps_id, int *old_id)
- {
- int bytes;
- unsigned pps_id = ue_bits(bs); // max = 255
- unsigned sps_id = ue_bits(bs); // max = 31
- *old_id = pps_id;
- sps_id = h->map_sps[sps_id];
- pps_id = new_pps_id;
- assert(sps_id <= 31);
- assert(pps_id <= 255);
- h264e_bs_put_golomb(bd, pps_id);
- h264e_bs_put_golomb(bd, sps_id);
- copy_bits(bs, bd);
- bytes = h264e_bs_byte_align(bd) / 8;
- h264e_bs_flush(bd);
- return bytes;
- }
- static void patch_slice_header(h264_sps_id_patcher_t *h, bit_reader_t *bs, bs_t *bd)
- {
- unsigned first_mb_in_slice = ue_bits(bs);
- unsigned slice_type = ue_bits(bs);
- unsigned pps_id = ue_bits(bs);
- pps_id = h->map_pps[pps_id];
- assert(pps_id <= 255);
- h264e_bs_put_golomb(bd, first_mb_in_slice);
- h264e_bs_put_golomb(bd, slice_type);
- h264e_bs_put_golomb(bd, pps_id);
- copy_bits(bs, bd);
- }
- static int transcode_nalu(h264_sps_id_patcher_t *h, const unsigned char *src, int nalu_bytes, unsigned char *dst)
- {
- int old_id;
- bit_reader_t bst[1];
- bs_t bdt[1];
- bit_reader_t bs[1];
- bs_t bd[1];
- int payload_type = src[0] & 31;
- *dst = *src;
- h264e_bs_init_bits(bd, dst + 1);
- init_bits(bs, src + 1, nalu_bytes - 1);
- h264e_bs_init_bits(bdt, dst + 1);
- init_bits(bst, src + 1, nalu_bytes - 1);
- switch(payload_type)
- {
- case 7:
- {
- int cb = change_sps_id(bst, bdt, 0, &old_id);
- int id = find_mem_cache(h->sps_cache, h->sps_bytes, MINIMP4_MAX_SPS, dst + 1, cb);
- if (id == -1)
- return 0;
- h->map_sps[old_id] = id;
- change_sps_id(bs, bd, id, &old_id);
- }
- break;
- case 8:
- {
- int cb = patch_pps(h, bst, bdt, 0, &old_id);
- int id = find_mem_cache(h->pps_cache, h->pps_bytes, MINIMP4_MAX_PPS, dst + 1, cb);
- if (id == -1)
- return 0;
- h->map_pps[old_id] = id;
- patch_pps(h, bs, bd, id, &old_id);
- }
- break;
- case 1:
- case 2:
- case 5:
- patch_slice_header(h, bs, bd);
- break;
- default:
- memcpy(dst, src, nalu_bytes);
- return nalu_bytes;
- }
- nalu_bytes = 1 + h264e_bs_byte_align(bd) / 8;
- h264e_bs_flush(bd);
- return nalu_bytes;
- }
- #endif
- /**
- * Set pointer just after start code (00 .. 00 01), or to EOF if not found:
- *
- * NZ NZ ... NZ 00 00 00 00 01 xx xx ... xx (EOF)
- * ^ ^
- * non-zero head.............. here ....... or here if no start code found
- *
- */
- static const uint8_t *find_start_code(const uint8_t *h264_data, int h264_data_bytes, int *zcount)
- {
- const uint8_t *eof = h264_data + h264_data_bytes;
- const uint8_t *p = h264_data;
- do
- {
- int zero_cnt = 1;
- const uint8_t* found = (uint8_t*)memchr(p, 0, eof - p);
- p = found ? found : eof;
- while (p + zero_cnt < eof && !p[zero_cnt]) zero_cnt++;
- if (zero_cnt >= 2 && p[zero_cnt] == 1)
- {
- *zcount = zero_cnt + 1;
- return p + zero_cnt + 1;
- }
- p += zero_cnt;
- } while (p < eof);
- *zcount = 0;
- return eof;
- }
- /**
- * Locate NAL unit in given buffer, and calculate it's length
- */
- static const uint8_t *find_nal_unit(const uint8_t *h264_data, int h264_data_bytes, int *pnal_unit_bytes)
- {
- const uint8_t *eof = h264_data + h264_data_bytes;
- int zcount;
- const uint8_t *start = find_start_code(h264_data, h264_data_bytes, &zcount);
- const uint8_t *stop = start;
- if (start)
- {
- stop = find_start_code(start, (int)(eof - start), &zcount);
- while (stop > start && !stop[-1])
- {
- stop--;
- }
- }
- *pnal_unit_bytes = (int)(stop - start - zcount);
- return start;
- }
- int mp4_h26x_write_init(mp4_h26x_writer_t *h, MP4E_mux_t *mux, int width, int height, int is_hevc)
- {
- MP4E_track_t tr;
- tr.track_media_kind = e_video;
- tr.language[0] = 'u';
- tr.language[1] = 'n';
- tr.language[2] = 'd';
- tr.language[3] = 0;
- tr.object_type_indication = is_hevc ? MP4_OBJECT_TYPE_HEVC : MP4_OBJECT_TYPE_AVC;
- tr.time_scale = 90000;
- tr.default_duration = 0;
- tr.u.v.width = width;
- tr.u.v.height = height;
- h->mux_track_id = MP4E_add_track(mux, &tr);
- h->mux = mux;
- h->is_hevc = is_hevc;
- h->need_vps = is_hevc;
- h->need_sps = 1;
- h->need_pps = 1;
- h->need_idr = 1;
- #if MINIMP4_TRANSCODE_SPS_ID
- memset(&h->sps_patcher, 0, sizeof(h264_sps_id_patcher_t));
- #endif
- return MP4E_STATUS_OK;
- }
- void mp4_h26x_write_close(mp4_h26x_writer_t *h)
- {
- #if MINIMP4_TRANSCODE_SPS_ID
- h264_sps_id_patcher_t *p = &h->sps_patcher;
- int i;
- for (i = 0; i < MINIMP4_MAX_SPS; i++)
- {
- if (p->sps_cache[i])
- free(p->sps_cache[i]);
- }
- for (i = 0; i < MINIMP4_MAX_PPS; i++)
- {
- if (p->pps_cache[i])
- free(p->pps_cache[i]);
- }
- #endif
- memset(h, 0, sizeof(*h));
- }
- static int mp4_h265_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int sizeof_nal, unsigned timeStamp90kHz_next)
- {
- int payload_type = (nal[0] >> 1) & 0x3f;
- int is_intra = payload_type >= HEVC_NAL_BLA_W_LP && payload_type <= HEVC_NAL_CRA_NUT;
- int err = MP4E_STATUS_OK;
- //printf("payload_type=%d, intra=%d\n", payload_type, is_intra);
- if (is_intra && !h->need_sps && !h->need_pps && !h->need_vps)
- h->need_idr = 0;
- switch (payload_type)
- {
- case HEVC_NAL_VPS:
- MP4E_set_vps(h->mux, h->mux_track_id, nal, sizeof_nal);
- h->need_vps = 0;
- break;
- case HEVC_NAL_SPS:
- MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal);
- h->need_sps = 0;
- break;
- case HEVC_NAL_PPS:
- MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal);
- h->need_pps = 0;
- break;
- default:
- if (h->need_vps || h->need_sps || h->need_pps || h->need_idr)
- return MP4E_STATUS_BAD_ARGUMENTS;
- {
- unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal);
- if (!tmp)
- return MP4E_STATUS_NO_MEMORY;
- int sample_kind = MP4E_SAMPLE_DEFAULT;
- tmp[0] = (unsigned char)(sizeof_nal >> 24);
- tmp[1] = (unsigned char)(sizeof_nal >> 16);
- tmp[2] = (unsigned char)(sizeof_nal >> 8);
- tmp[3] = (unsigned char)(sizeof_nal);
- memcpy(tmp + 4, nal, sizeof_nal);
- if (is_intra)
- sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
- err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind);
- free(tmp);
- }
- break;
- }
- return err;
- }
- int mp4_h26x_write_nal(mp4_h26x_writer_t *h, const unsigned char *nal, int length, unsigned timeStamp90kHz_next)
- {
- const unsigned char *eof = nal + length;
- int payload_type, sizeof_nal, err = MP4E_STATUS_OK;
- for (;;nal++)
- {
- #if MINIMP4_TRANSCODE_SPS_ID
- unsigned char *nal1, *nal2;
- #endif
- nal = find_nal_unit(nal, (int)(eof - nal), &sizeof_nal);
- if (!sizeof_nal)
- break;
- if (h->is_hevc)
- {
- ERR(mp4_h265_write_nal(h, nal, sizeof_nal, timeStamp90kHz_next));
- continue;
- }
- payload_type = nal[0] & 31;
- if (9 == payload_type)
- continue; // access unit delimiter, nothing to be done
- #if MINIMP4_TRANSCODE_SPS_ID
- // Transcode SPS, PPS and slice headers, reassigning ID's for SPS and PPS:
- // - assign unique ID's to different SPS and PPS
- // - assign same ID's to equal (except ID) SPS and PPS
- // - save all different SPS and PPS
- nal1 = (unsigned char *)malloc(sizeof_nal*17/16 + 32);
- if (!nal1)
- return MP4E_STATUS_NO_MEMORY;
- nal2 = (unsigned char *)malloc(sizeof_nal*17/16 + 32);
- if (!nal2)
- {
- free(nal1);
- return MP4E_STATUS_NO_MEMORY;
- }
- sizeof_nal = remove_nal_escapes(nal2, nal, sizeof_nal);
- if (!sizeof_nal)
- {
- exit_with_free:
- free(nal1);
- free(nal2);
- return MP4E_STATUS_BAD_ARGUMENTS;
- }
- sizeof_nal = transcode_nalu(&h->sps_patcher, nal2, sizeof_nal, nal1);
- sizeof_nal = nal_put_esc(nal2, nal1, sizeof_nal);
- switch (payload_type) {
- case 7:
- MP4E_set_sps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4);
- h->need_sps = 0;
- break;
- case 8:
- if (h->need_sps)
- goto exit_with_free;
- MP4E_set_pps(h->mux, h->mux_track_id, nal2 + 4, sizeof_nal - 4);
- h->need_pps = 0;
- break;
- case 5:
- if (h->need_sps)
- goto exit_with_free;
- h->need_idr = 0;
- // flow through
- default:
- if (h->need_sps)
- goto exit_with_free;
- if (!h->need_pps && !h->need_idr)
- {
- bit_reader_t bs[1];
- init_bits(bs, nal + 1, sizeof_nal - 4 - 1);
- unsigned first_mb_in_slice = ue_bits(bs);
- //unsigned slice_type = ue_bits(bs);
- int sample_kind = MP4E_SAMPLE_DEFAULT;
- nal2[0] = (unsigned char)((sizeof_nal - 4) >> 24);
- nal2[1] = (unsigned char)((sizeof_nal - 4) >> 16);
- nal2[2] = (unsigned char)((sizeof_nal - 4) >> 8);
- nal2[3] = (unsigned char)((sizeof_nal - 4));
- if (first_mb_in_slice)
- sample_kind = MP4E_SAMPLE_CONTINUATION;
- else if (payload_type == 5)
- sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
- err = MP4E_put_sample(h->mux, h->mux_track_id, nal2, sizeof_nal, timeStamp90kHz_next, sample_kind);
- }
- break;
- }
- free(nal1);
- free(nal2);
- #else
- // No SPS/PPS transcoding
- // This branch assumes that encoder use correct SPS/PPS ID's
- switch (payload_type) {
- case 7:
- MP4E_set_sps(h->mux, h->mux_track_id, nal, sizeof_nal);
- h->need_sps = 0;
- break;
- case 8:
- MP4E_set_pps(h->mux, h->mux_track_id, nal, sizeof_nal);
- h->need_pps = 0;
- break;
- case 5:
- if (h->need_sps)
- return MP4E_STATUS_BAD_ARGUMENTS;
- h->need_idr = 0;
- // flow through
- default:
- if (h->need_sps)
- return MP4E_STATUS_BAD_ARGUMENTS;
- if (!h->need_pps && !h->need_idr)
- {
- bit_reader_t bs[1];
- unsigned char *tmp = (unsigned char *)malloc(4 + sizeof_nal);
- if (!tmp)
- return MP4E_STATUS_NO_MEMORY;
- init_bits(bs, nal + 1, sizeof_nal - 1);
- unsigned first_mb_in_slice = ue_bits(bs);
- int sample_kind = MP4E_SAMPLE_DEFAULT;
- tmp[0] = (unsigned char)(sizeof_nal >> 24);
- tmp[1] = (unsigned char)(sizeof_nal >> 16);
- tmp[2] = (unsigned char)(sizeof_nal >> 8);
- tmp[3] = (unsigned char)(sizeof_nal);
- memcpy(tmp + 4, nal, sizeof_nal);
- if (first_mb_in_slice)
- sample_kind = MP4E_SAMPLE_CONTINUATION;
- else if (payload_type == 5)
- sample_kind = MP4E_SAMPLE_RANDOM_ACCESS;
- err = MP4E_put_sample(h->mux, h->mux_track_id, tmp, 4 + sizeof_nal, timeStamp90kHz_next, sample_kind);
- free(tmp);
- }
- break;
- }
- #endif
- if (err)
- break;
- }
- return err;
- }
- #if MP4D_TRACE_SUPPORTED
- # define TRACE(x) printf x
- #else
- # define TRACE(x)
- #endif
- #define NELEM(x) (sizeof(x) / sizeof((x)[0]))
- static int minimp4_fgets(MP4D_demux_t *mp4)
- {
- uint8_t c;
- if (mp4->read_callback(mp4->read_pos, &c, 1, mp4->token))
- return -1;
- mp4->read_pos++;
- return c;
- }
- /**
- * Read given number of bytes from input stream
- * Used to read box headers
- */
- static unsigned minimp4_read(MP4D_demux_t *mp4, int nb, int *eof_flag)
- {
- uint32_t v = 0; int last_byte;
- switch (nb)
- {
- case 4: v = (v << 8) | minimp4_fgets(mp4);
- case 3: v = (v << 8) | minimp4_fgets(mp4);
- case 2: v = (v << 8) | minimp4_fgets(mp4);
- default:
- case 1: v = (v << 8) | (last_byte = minimp4_fgets(mp4));
- }
- if (last_byte < 0)
- {
- *eof_flag = 1;
- }
- return v;
- }
- /**
- * Read given number of bytes, but no more than *payload_bytes specifies...
- * Used to read box payload
- */
- static uint32_t read_payload(MP4D_demux_t *mp4, unsigned nb, boxsize_t *payload_bytes, int *eof_flag)
- {
- if (*payload_bytes < nb)
- {
- *eof_flag = 1;
- nb = (int)*payload_bytes;
- }
- *payload_bytes -= nb;
- return minimp4_read(mp4, nb, eof_flag);
- }
- /**
- * Skips given number of bytes.
- * Avoid math operations with fpos_t
- */
- static void my_fseek(MP4D_demux_t *mp4, boxsize_t pos, int *eof_flag)
- {
- mp4->read_pos += pos;
- if (mp4->read_pos >= mp4->read_size)
- *eof_flag = 1;
- }
- #define READ(n) read_payload(mp4, n, &payload_bytes, &eof_flag)
- #define SKIP(n) { boxsize_t t = MINIMP4_MIN(payload_bytes, n); my_fseek(mp4, t, &eof_flag); payload_bytes -= t; }
- #define MALLOC(t, p, size) p = (t)malloc(size); if (!(p)) { ERROR("out of memory"); }
- /*
- * On error: release resources.
- */
- #define RETURN_ERROR(mess) { \
- TRACE(("\nMP4 ERROR: " mess)); \
- MP4D_close(mp4); \
- return 0; \
- }
- /*
- * Any errors, occurred on top-level hierarchy is passed to exit check: 'if (!mp4->track_count) ... '
- */
- #define ERROR(mess) \
- if (!depth) \
- break; \
- else \
- RETURN_ERROR(mess);
- typedef enum { BOX_ATOM, BOX_OD } boxtype_t;
- int MP4D_open(MP4D_demux_t *mp4, int (*read_callback)(int64_t offset, void *buffer, size_t size, void *token), void *token, int64_t file_size)
- {
- // box stack size
- int depth = 0;
- struct
- {
- // remaining bytes for box in the stack
- boxsize_t bytes;
- // kind of box children's: OD chunks handled in the same manner as name chunks
- boxtype_t format;
- } stack[MAX_CHUNKS_DEPTH];
- #if MP4D_TRACE_SUPPORTED
- // path of current element: List0/List1/... etc
- uint32_t box_path[MAX_CHUNKS_DEPTH];
- #endif
- int eof_flag = 0;
- unsigned i;
- MP4D_track_t *tr = NULL;
- if (!mp4 || !read_callback)
- {
- TRACE(("\nERROR: invlaid arguments!"));
- return 0;
- }
- memset(mp4, 0, sizeof(MP4D_demux_t));
- mp4->read_callback = read_callback;
- mp4->token = token;
- mp4->read_size = file_size;
- stack[0].format = BOX_ATOM; // start with atom box
- stack[0].bytes = 0; // never accessed
- do
- {
- // List of boxes, derived from 'FullBox'
- // ~~~~~~~~~~~~~~~~~~~~~
- // need read version field and check version for these boxes
- static const struct
- {
- uint32_t name;
- unsigned max_version;
- unsigned use_track_flag;
- } g_fullbox[] =
- {
- #if MP4D_INFO_SUPPORTED
- {BOX_mdhd, 1, 1},
- {BOX_mvhd, 1, 0},
- {BOX_hdlr, 0, 0},
- {BOX_meta, 0, 0}, // Android can produce meta box without 'FullBox' field, comment this line to simulate the bug
- #endif
- #if MP4D_TRACE_TIMESTAMPS
- {BOX_stts, 0, 0},
- {BOX_ctts, 0, 0},
- #endif
- {BOX_stz2, 0, 1},
- {BOX_stsz, 0, 1},
- {BOX_stsc, 0, 1},
- {BOX_stco, 0, 1},
- {BOX_co64, 0, 1},
- {BOX_stsd, 0, 0},
- {BOX_esds, 0, 1} // esds does not use track, but switches to OD mode. Check here, to avoid OD check
- };
- // List of boxes, which contains other boxes ('envelopes')
- // Parser will descend down for boxes in this list, otherwise parsing will proceed to
- // the next sibling box
- // OD boxes handled in the same way as atom boxes...
- static const struct
- {
- uint32_t name;
- boxtype_t type;
- } g_envelope_box[] =
- {
- {BOX_esds, BOX_OD}, // TODO: BOX_esds can be used for both audio and video, but this code supports audio only!
- {OD_ESD, BOX_OD},
- {OD_DCD, BOX_OD},
- {OD_DSI, BOX_OD},
- {BOX_trak, BOX_ATOM},
- {BOX_moov, BOX_ATOM},
- //{BOX_moof, BOX_ATOM},
- {BOX_mdia, BOX_ATOM},
- {BOX_tref, BOX_ATOM},
- {BOX_minf, BOX_ATOM},
- {BOX_dinf, BOX_ATOM},
- {BOX_stbl, BOX_ATOM},
- {BOX_stsd, BOX_ATOM},
- {BOX_mp4a, BOX_ATOM},
- {BOX_mp4s, BOX_ATOM},
- #if MP4D_AVC_SUPPORTED
- {BOX_mp4v, BOX_ATOM},
- {BOX_avc1, BOX_ATOM},
- //{BOX_avc2, BOX_ATOM},
- //{BOX_svc1, BOX_ATOM},
- #endif
- #if MP4D_HEVC_SUPPORTED
- {BOX_hvc1, BOX_ATOM},
- #endif
- {BOX_udta, BOX_ATOM},
- {BOX_meta, BOX_ATOM},
- {BOX_ilst, BOX_ATOM}
- };
- uint32_t FullAtomVersionAndFlags = 0;
- boxsize_t payload_bytes;
- boxsize_t box_bytes;
- uint32_t box_name;
- #if MP4D_INFO_SUPPORTED
- unsigned char **ptag = NULL;
- #endif
- int read_bytes = 0;
- // Read header box type and it's length
- if (stack[depth].format == BOX_ATOM)
- {
- box_bytes = minimp4_read(mp4, 4, &eof_flag);
- #if FIX_BAD_ANDROID_META_BOX
- broken_android_meta_hack:
- #endif
- if (eof_flag)
- break; // normal exit
- if (box_bytes >= 2 && box_bytes < 8)
- {
- ERROR("invalid box size (broken file?)");
- }
- box_name = minimp4_read(mp4, 4, &eof_flag);
- read_bytes = 8;
- // Decode box size
- if (box_bytes == 0 || // standard indication of 'till eof' size
- box_bytes == (boxsize_t)0xFFFFFFFFU // some files uses non-standard 'till eof' signaling
- )
- {
- box_bytes = ~(boxsize_t)0;
- }
- payload_bytes = box_bytes - 8;
- if (box_bytes == 1) // 64-bit sizes
- {
- TRACE(("\n64-bit chunk encountered"));
- box_bytes = minimp4_read(mp4, 4, &eof_flag);
- #if MP4D_64BIT_SUPPORTED
- box_bytes <<= 32;
- box_bytes |= minimp4_read(mp4, 4, &eof_flag);
- #else
- if (box_bytes)
- {
- ERROR("UNSUPPORTED FEATURE: MP4BoxHeader(): 64-bit boxes not supported!");
- }
- box_bytes = minimp4_read(mp4, 4, &eof_flag);
- #endif
- if (box_bytes < 16)
- {
- ERROR("invalid box size (broken file?)");
- }
- payload_bytes = box_bytes - 16;
- }
- // Read and check box version for some boxes
- for (i = 0; i < NELEM(g_fullbox); i++)
- {
- if (box_name == g_fullbox[i].name)
- {
- FullAtomVersionAndFlags = READ(4);
- read_bytes += 4;
- #if FIX_BAD_ANDROID_META_BOX
- // Fix invalid BOX_meta, found in some Android-produced MP4
- // This branch is optional: bad box would be skipped
- if (box_name == BOX_meta)
- {
- if (FullAtomVersionAndFlags >= 8 && FullAtomVersionAndFlags < payload_bytes)
- {
- if (box_bytes > stack[depth].bytes)
- {
- ERROR("broken file structure!");
- }
- stack[depth].bytes -= box_bytes;;
- depth++;
- stack[depth].bytes = payload_bytes + 4; // +4 need for missing header
- stack[depth].format = BOX_ATOM;
- box_bytes = FullAtomVersionAndFlags;
- TRACE(("Bad metadata box detected (Android bug?)!\n"));
- goto broken_android_meta_hack;
- }
- }
- #endif // FIX_BAD_ANDROID_META_BOX
- if ((FullAtomVersionAndFlags >> 24) > g_fullbox[i].max_version)
- {
- ERROR("unsupported box version!");
- }
- if (g_fullbox[i].use_track_flag && !tr)
- {
- ERROR("broken file structure!");
- }
- }
- }
- } else // stack[depth].format == BOX_OD
- {
- int val;
- box_name = OD_BASE + minimp4_read(mp4, 1, &eof_flag); // 1-byte box type
- read_bytes += 1;
- if (eof_flag)
- break;
- payload_bytes = 0;
- box_bytes = 1;
- do
- {
- val = minimp4_read(mp4, 1, &eof_flag);
- read_bytes += 1;
- if (eof_flag)
- {
- ERROR("premature EOF!");
- }
- payload_bytes = (payload_bytes << 7) | (val & 0x7F);
- box_bytes++;
- } while (val & 0x80);
- box_bytes += payload_bytes;
- }
- #if MP4D_TRACE_SUPPORTED
- box_path[depth] = (box_name >> 24) | (box_name << 24) | ((box_name >> 8) & 0x0000FF00) | ((box_name << 8) & 0x00FF0000);
- TRACE(("%2d %8d %.*s (%d bytes remains for sibilings) \n", depth, (int)box_bytes, depth*4, (char*)box_path, (int)stack[depth].bytes));
- #endif
- // Check that box size <= parent size
- if (depth)
- {
- // Skip box with bad size
- assert(box_bytes > 0);
- if (box_bytes > stack[depth].bytes)
- {
- TRACE(("Wrong %c%c%c%c box size: broken file?\n", (box_name >> 24)&255, (box_name >> 16)&255, (box_name >> 8)&255, box_name&255));
- box_bytes = stack[depth].bytes;
- box_name = 0;
- payload_bytes = box_bytes - read_bytes;
- }
- stack[depth].bytes -= box_bytes;
- }
- // Read box header
- switch(box_name)
- {
- case BOX_stz2: //ISO/IEC 14496-1 Page 38. Section 8.17.2 - Sample Size Box.
- case BOX_stsz:
- {
- int size = 0;
- uint32_t sample_size = READ(4);
- tr->sample_count = READ(4);
- MALLOC(unsigned int*, tr->entry_size, tr->sample_count*4);
- for (i = 0; i < tr->sample_count; i++)
- {
- if (box_name == BOX_stsz)
- {
- tr->entry_size[i] = (sample_size?sample_size:READ(4));
- } else
- {
- switch (sample_size & 0xFF)
- {
- case 16:
- tr->entry_size[i] = READ(2);
- break;
- case 8:
- tr->entry_size[i] = READ(1);
- break;
- case 4:
- if (i & 1)
- {
- tr->entry_size[i] = size & 15;
- } else
- {
- size = READ(1);
- tr->entry_size[i] = (size >> 4);
- }
- break;
- }
- }
- }
- }
- break;
- case BOX_stsc: //ISO/IEC 14496-12 Page 38. Section 8.18 - Sample To Chunk Box.
- tr->sample_to_chunk_count = READ(4);
- MALLOC(MP4D_sample_to_chunk_t*, tr->sample_to_chunk, tr->sample_to_chunk_count*sizeof(tr->sample_to_chunk[0]));
- for (i = 0; i < tr->sample_to_chunk_count; i++)
- {
- tr->sample_to_chunk[i].first_chunk = READ(4);
- tr->sample_to_chunk[i].samples_per_chunk = READ(4);
- SKIP(4); // sample_description_index
- }
- break;
- #if MP4D_TRACE_TIMESTAMPS || MP4D_TIMESTAMPS_SUPPORTED
- case BOX_stts:
- {
- unsigned count = READ(4);
- unsigned j, k = 0, ts = 0, ts_count = count;
- #if MP4D_TIMESTAMPS_SUPPORTED
- MALLOC(unsigned int*, tr->timestamp, ts_count*4);
- MALLOC(unsigned int*, tr->duration, ts_count*4);
- #endif
- for (i = 0; i < count; i++)
- {
- unsigned sc = READ(4);
- int d = READ(4);
- TRACE(("sample %8d count %8d duration %8d\n", i, sc, d));
- #if MP4D_TIMESTAMPS_SUPPORTED
- if (k + sc > ts_count)
- {
- ts_count = k + sc;
- tr->timestamp = (unsigned int*)realloc(tr->timestamp, ts_count * sizeof(unsigned));
- tr->duration = (unsigned int*)realloc(tr->duration, ts_count * sizeof(unsigned));
- }
- for (j = 0; j < sc; j++)
- {
- tr->duration[k] = d;
- tr->timestamp[k++] = ts;
- ts += d;
- }
- #endif
- }
- }
- break;
- case BOX_ctts:
- {
- unsigned count = READ(4);
- for (i = 0; i < count; i++)
- {
- int sc = READ(4);
- int d = READ(4);
- (void)sc;
- (void)d;
- TRACE(("sample %8d count %8d decoding to composition offset %8d\n", i, sc, d));
- }
- }
- break;
- #endif
- case BOX_stco: //ISO/IEC 14496-12 Page 39. Section 8.19 - Chunk Offset Box.
- case BOX_co64:
- tr->chunk_count = READ(4);
- MALLOC(MP4D_file_offset_t*, tr->chunk_offset, tr->chunk_count*sizeof(MP4D_file_offset_t));
- for (i = 0; i < tr->chunk_count; i++)
- {
- tr->chunk_offset[i] = READ(4);
- if (box_name == BOX_co64)
- {
- #if !MP4D_64BIT_SUPPORTED
- if (tr->chunk_offset[i])
- {
- ERROR("UNSUPPORTED FEATURE: 64-bit chunk_offset not supported!");
- }
- #endif
- tr->chunk_offset[i] <<= 32;
- tr->chunk_offset[i] |= READ(4);
- }
- }
- break;
- #if MP4D_INFO_SUPPORTED
- case BOX_mvhd:
- SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4);
- mp4->timescale = READ(4);
- mp4->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0;
- mp4->duration_lo = READ(4);
- SKIP(4 + 2 + 2 + 4*2 + 4*9 + 4*6 + 4);
- break;
- case BOX_mdhd:
- SKIP(((FullAtomVersionAndFlags >> 24) == 1) ? 8 + 8 : 4 + 4);
- tr->timescale = READ(4);
- tr->duration_hi = ((FullAtomVersionAndFlags >> 24) == 1) ? READ(4) : 0;
- tr->duration_lo = READ(4);
- {
- int ISO_639_2_T = READ(2);
- tr->language[2] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5;
- tr->language[1] = (ISO_639_2_T & 31) + 0x60; ISO_639_2_T >>= 5;
- tr->language[0] = (ISO_639_2_T & 31) + 0x60;
- }
- // the rest of this box is skipped by default ...
- break;
- case BOX_hdlr:
- if (tr) // When this box is within 'meta' box, the track may not be avaialable
- {
- SKIP(4); // pre_defined
- tr->handler_type = READ(4);
- }
- // typically hdlr box does not contain any useful info.
- // the rest of this box is skipped by default ...
- break;
- case BOX_btrt:
- if (!tr)
- {
- ERROR("broken file structure!");
- }
- SKIP(4 + 4);
- tr->avg_bitrate_bps = READ(4);
- break;
- // Set pointer to tag to be read...
- case BOX_calb: ptag = &mp4->tag.album; break;
- case BOX_cART: ptag = &mp4->tag.artist; break;
- case BOX_cnam: ptag = &mp4->tag.title; break;
- case BOX_cday: ptag = &mp4->tag.year; break;
- case BOX_ccmt: ptag = &mp4->tag.comment; break;
- case BOX_cgen: ptag = &mp4->tag.genre; break;
- #endif
- case BOX_stsd:
- SKIP(4); // entry_count, BOX_mp4a & BOX_mp4v boxes follows immediately
- break;
- case BOX_mp4s: // private stream
- if (!tr)
- {
- ERROR("broken file structure!");
- }
- SKIP(6*1 + 2/*Base SampleEntry*/);
- break;
- case BOX_mp4a:
- if (!tr)
- {
- ERROR("broken file structure!");
- }
- #if MP4D_INFO_SUPPORTED
- SKIP(6*1+2/*Base SampleEntry*/ + 4*2);
- tr->SampleDescription.audio.channelcount = READ(2);
- SKIP(2/*samplesize*/ + 2 + 2);
- tr->SampleDescription.audio.samplerate_hz = READ(4) >> 16;
- #else
- SKIP(28);
- #endif
- break;
- #if MP4D_AVC_SUPPORTED
- case BOX_avc1: // AVCSampleEntry extends VisualSampleEntry
- // case BOX_avc2: - no test
- // case BOX_svc1: - no test
- case BOX_mp4v:
- if (!tr)
- {
- ERROR("broken file structure!");
- }
- #if MP4D_INFO_SUPPORTED
- SKIP(6*1 + 2/*Base SampleEntry*/ + 2 + 2 + 4*3);
- tr->SampleDescription.video.width = READ(2);
- tr->SampleDescription.video.height = READ(2);
- // frame_count is always 1
- // compressorname is rarely set..
- SKIP(4 + 4 + 4 + 2/*frame_count*/ + 32/*compressorname*/ + 2 + 2);
- #else
- SKIP(78);
- #endif
- // ^^^ end of VisualSampleEntry
- // now follows for BOX_avc1:
- // BOX_avcC
- // BOX_btrt (optional)
- // BOX_m4ds (optional)
- // for BOX_mp4v:
- // BOX_esds
- break;
- case BOX_avcC: // AVCDecoderConfigurationRecord()
- // hack: AAC-specific DSI field reused (for it have same purpoose as sps/pps)
- // TODO: check this hack if BOX_esds co-exist with BOX_avcC
- tr->object_type_indication = MP4_OBJECT_TYPE_AVC;
- tr->dsi = (unsigned char*)malloc((size_t)box_bytes);
- tr->dsi_bytes = (unsigned)box_bytes;
- {
- int spspps;
- unsigned char *p = tr->dsi;
- unsigned int configurationVersion = READ(1);
- unsigned int AVCProfileIndication = READ(1);
- unsigned int profile_compatibility = READ(1);
- unsigned int AVCLevelIndication = READ(1);
- //bit(6) reserved =
- unsigned int lengthSizeMinusOne = READ(1) & 3;
- (void)configurationVersion;
- (void)AVCProfileIndication;
- (void)profile_compatibility;
- (void)AVCLevelIndication;
- (void)lengthSizeMinusOne;
- for (spspps = 0; spspps < 2; spspps++)
- {
- unsigned int numOfSequenceParameterSets= READ(1);
- if (!spspps)
- {
- numOfSequenceParameterSets &= 31; // clears 3 msb for SPS
- }
- *p++ = numOfSequenceParameterSets;
- for (i = 0; i < numOfSequenceParameterSets; i++)
- {
- unsigned k, sequenceParameterSetLength = READ(2);
- *p++ = sequenceParameterSetLength >> 8;
- *p++ = sequenceParameterSetLength ;
- for (k = 0; k < sequenceParameterSetLength; k++)
- {
- *p++ = READ(1);
- }
- }
- }
- }
- break;
- #endif // MP4D_AVC_SUPPORTED
- case OD_ESD:
- {
- unsigned flags = READ(3); // ES_ID(2) + flags(1)
- if (flags & 0x80) // steamdependflag
- {
- SKIP(2); // dependsOnESID
- }
- if (flags & 0x40) // urlflag
- {
- unsigned bytecount = READ(1);
- SKIP(bytecount); // skip URL
- }
- if (flags & 0x20) // ocrflag (was reserved in MPEG-4 v.1)
- {
- SKIP(2); // OCRESID
- }
- break;
- }
- case OD_DCD: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor.
- assert(tr); // ensured by g_fullbox[] check
- tr->object_type_indication = READ(1);
- #if MP4D_INFO_SUPPORTED
- tr->stream_type = READ(1) >> 2;
- SKIP(3/*bufferSizeDB*/ + 4/*maxBitrate*/);
- tr->avg_bitrate_bps = READ(4);
- #else
- SKIP(1+3+4+4);
- #endif
- break;
- case OD_DSI: //ISO/IEC 14496-1 Page 28. Section 8.6.5 - DecoderConfigDescriptor.
- assert(tr); // ensured by g_fullbox[] check
- if (!tr->dsi && payload_bytes)
- {
- MALLOC(unsigned char*, tr->dsi, (int)payload_bytes);
- for (i = 0; i < payload_bytes; i++)
- {
- tr->dsi[i] = minimp4_read(mp4, 1, &eof_flag); // These bytes available due to check above
- }
- tr->dsi_bytes = i;
- payload_bytes -= i;
- break;
- }
- default:
- TRACE(("[%c%c%c%c] %d\n", box_name >> 24, box_name >> 16, box_name >> 8, box_name, (int)payload_bytes));
- }
- #if MP4D_INFO_SUPPORTED
- // Read tag is tag pointer is set
- if (ptag && !*ptag && payload_bytes > 16)
- {
- #if 0
- uint32_t size = READ(4);
- uint32_t data = READ(4);
- uint32_t class = READ(4);
- uint32_t x1 = READ(4);
- TRACE(("%2d %2d %2d ", size, class, x1));
- #else
- SKIP(4 + 4 + 4 + 4);
- #endif
- MALLOC(unsigned char*, *ptag, (unsigned)payload_bytes + 1);
- for (i = 0; payload_bytes != 0; i++)
- {
- (*ptag)[i] = READ(1);
- }
- (*ptag)[i] = 0; // zero-terminated string
- }
- #endif
- if (box_name == BOX_trak)
- {
- // New track found: allocate memory using realloc()
- // Typically there are 1 audio track for AAC audio file,
- // 4 tracks for movie file,
- // 3-5 tracks for scalable audio (CELP+AAC)
- // and up to 50 tracks for BSAC scalable audio
- void *mem = realloc(mp4->track, (mp4->track_count + 1)*sizeof(MP4D_track_t));
- if (!mem)
- {
- // if realloc fails, it does not deallocate old pointer!
- ERROR("out of memory");
- }
- mp4->track = (MP4D_track_t*)mem;
- tr = mp4->track + mp4->track_count++;
- memset(tr, 0, sizeof(MP4D_track_t));
- } else if (box_name == BOX_meta)
- {
- tr = NULL; // Avoid update of 'hdlr' box, which may contains in the 'meta' box
- }
- // If this box is envelope, save it's size in box stack
- for (i = 0; i < NELEM(g_envelope_box); i++)
- {
- if (box_name == g_envelope_box[i].name)
- {
- if (++depth >= MAX_CHUNKS_DEPTH)
- {
- ERROR("too deep atoms nesting!");
- }
- stack[depth].bytes = payload_bytes;
- stack[depth].format = g_envelope_box[i].type;
- break;
- }
- }
- // if box is not envelope, just skip it
- if (i == NELEM(g_envelope_box))
- {
- if (payload_bytes > file_size)
- {
- eof_flag = 1;
- } else
- {
- SKIP(payload_bytes);
- }
- }
- // remove empty boxes from stack
- // don't touch box with index 0 (which indicates whole file)
- while (depth > 0 && !stack[depth].bytes)
- {
- depth--;
- }
- } while(!eof_flag);
- if (!mp4->track_count)
- {
- RETURN_ERROR("no tracks found");
- }
- return 1;
- }
- /**
- * Find chunk, containing given sample.
- * Returns chunk number, and first sample in this chunk.
- */
- static int sample_to_chunk(MP4D_track_t *tr, unsigned nsample, unsigned *nfirst_sample_in_chunk)
- {
- unsigned chunk_group = 0, nc;
- unsigned sum = 0;
- *nfirst_sample_in_chunk = 0;
- if (tr->chunk_count <= 1)
- {
- return 0;
- }
- for (nc = 0; nc < tr->chunk_count; nc++)
- {
- if (chunk_group + 1 < tr->sample_to_chunk_count // stuck at last entry till EOF
- && nc + 1 == // Chunks counted starting with '1'
- tr->sample_to_chunk[chunk_group + 1].first_chunk) // next group?
- {
- chunk_group++;
- }
- sum += tr->sample_to_chunk[chunk_group].samples_per_chunk;
- if (nsample < sum)
- return nc;
- // TODO: this can be calculated once per file
- *nfirst_sample_in_chunk = sum;
- }
- return -1;
- }
- // Exported API function
- MP4D_file_offset_t MP4D_frame_offset(const MP4D_demux_t *mp4, unsigned ntrack, unsigned nsample, unsigned *frame_bytes, unsigned *timestamp, unsigned *duration)
- {
- MP4D_track_t *tr = mp4->track + ntrack;
- unsigned ns;
- int nchunk = sample_to_chunk(tr, nsample, &ns);
- MP4D_file_offset_t offset;
- if (nchunk < 0)
- {
- *frame_bytes = 0;
- return 0;
- }
- offset = tr->chunk_offset[nchunk];
- for (; ns < nsample; ns++)
- {
- offset += tr->entry_size[ns];
- }
- *frame_bytes = tr->entry_size[ns];
- if (timestamp)
- {
- #if MP4D_TIMESTAMPS_SUPPORTED
- *timestamp = tr->timestamp[ns];
- #else
- *timestamp = 0;
- #endif
- }
- if (duration)
- {
- #if MP4D_TIMESTAMPS_SUPPORTED
- *duration = tr->duration[ns];
- #else
- *duration = 0;
- #endif
- }
- return offset;
- }
- #define FREE(x) if (x) {free(x); x = NULL;}
- // Exported API function
- void MP4D_close(MP4D_demux_t *mp4)
- {
- while (mp4->track_count)
- {
- MP4D_track_t *tr = mp4->track + --mp4->track_count;
- FREE(tr->entry_size);
- #if MP4D_TIMESTAMPS_SUPPORTED
- FREE(tr->timestamp);
- FREE(tr->duration);
- #endif
- FREE(tr->sample_to_chunk);
- FREE(tr->chunk_offset);
- FREE(tr->dsi);
- }
- FREE(mp4->track);
- #if MP4D_INFO_SUPPORTED
- FREE(mp4->tag.title);
- FREE(mp4->tag.artist);
- FREE(mp4->tag.album);
- FREE(mp4->tag.year);
- FREE(mp4->tag.comment);
- FREE(mp4->tag.genre);
- #endif
- }
- static int skip_spspps(const unsigned char *p, int nbytes, int nskip)
- {
- int i, k = 0;
- for (i = 0; i < nskip; i++)
- {
- unsigned segmbytes;
- if (k > nbytes - 2)
- return -1;
- segmbytes = p[k]*256 + p[k+1];
- k += 2 + segmbytes;
- }
- return k;
- }
- static const void *MP4D_read_spspps(const MP4D_demux_t *mp4, unsigned int ntrack, int pps_flag, int nsps, int *sps_bytes)
- {
- int sps_count, skip_bytes;
- int bytepos = 0;
- unsigned char *p = mp4->track[ntrack].dsi;
- if (ntrack >= mp4->track_count)
- return NULL;
- if (mp4->track[ntrack].object_type_indication != MP4_OBJECT_TYPE_AVC)
- return NULL; // SPS/PPS are specific for AVC format only
- if (pps_flag)
- {
- // Skip all SPS
- sps_count = p[bytepos++];
- skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, sps_count);
- if (skip_bytes < 0)
- return NULL;
- bytepos += skip_bytes;
- }
- // Skip sps/pps before the given target
- sps_count = p[bytepos++];
- if (nsps >= sps_count)
- return NULL;
- skip_bytes = skip_spspps(p+bytepos, mp4->track[ntrack].dsi_bytes - bytepos, nsps);
- if (skip_bytes < 0)
- return NULL;
- bytepos += skip_bytes;
- *sps_bytes = p[bytepos]*256 + p[bytepos+1];
- return p + bytepos + 2;
- }
- const void *MP4D_read_sps(const MP4D_demux_t *mp4, unsigned int ntrack, int nsps, int *sps_bytes)
- {
- return MP4D_read_spspps(mp4, ntrack, 0, nsps, sps_bytes);
- }
- const void *MP4D_read_pps(const MP4D_demux_t *mp4, unsigned int ntrack, int npps, int *pps_bytes)
- {
- return MP4D_read_spspps(mp4, ntrack, 1, npps, pps_bytes);
- }
- #if MP4D_PRINT_INFO_SUPPORTED
- /************************************************************************/
- /* Purely informational part, may be removed for embedded applications */
- /************************************************************************/
- //
- // Decodes ISO/IEC 14496 MP4 stream type to ASCII string
- //
- static const char *GetMP4StreamTypeName(int streamType)
- {
- switch (streamType)
- {
- case 0x00: return "Forbidden";
- case 0x01: return "ObjectDescriptorStream";
- case 0x02: return "ClockReferenceStream";
- case 0x03: return "SceneDescriptionStream";
- case 0x04: return "VisualStream";
- case 0x05: return "AudioStream";
- case 0x06: return "MPEG7Stream";
- case 0x07: return "IPMPStream";
- case 0x08: return "ObjectContentInfoStream";
- case 0x09: return "MPEGJStream";
- default:
- if (streamType >= 0x20 && streamType <= 0x3F)
- {
- return "User private";
- } else
- {
- return "Reserved for ISO use";
- }
- }
- }
- //
- // Decodes ISO/IEC 14496 MP4 object type to ASCII string
- //
- static const char *GetMP4ObjectTypeName(int objectTypeIndication)
- {
- switch (objectTypeIndication)
- {
- case 0x00: return "Forbidden";
- case 0x01: return "Systems ISO/IEC 14496-1";
- case 0x02: return "Systems ISO/IEC 14496-1";
- case 0x20: return "Visual ISO/IEC 14496-2";
- case 0x40: return "Audio ISO/IEC 14496-3";
- case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile";
- case 0x61: return "Visual ISO/IEC 13818-2 Main Profile";
- case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile";
- case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile";
- case 0x64: return "Visual ISO/IEC 13818-2 High Profile";
- case 0x65: return "Visual ISO/IEC 13818-2 422 Profile";
- case 0x66: return "Audio ISO/IEC 13818-7 Main Profile";
- case 0x67: return "Audio ISO/IEC 13818-7 LC Profile";
- case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile";
- case 0x69: return "Audio ISO/IEC 13818-3";
- case 0x6A: return "Visual ISO/IEC 11172-2";
- case 0x6B: return "Audio ISO/IEC 11172-3";
- case 0x6C: return "Visual ISO/IEC 10918-1";
- case 0xFF: return "no object type specified";
- default:
- if (objectTypeIndication >= 0xC0 && objectTypeIndication <= 0xFE)
- return "User private";
- else
- return "Reserved for ISO use";
- }
- }
- /**
- * Print MP4 information to stdout.
- * Subject for customization to particular application
- Output Example #1: movie file
- MP4 FILE: 7 tracks found. Movie time 104.12 sec
- No|type|lng| duration | bitrate| Stream type | Object type
- 0|odsm|fre| 0.00 s 1 frm| 0| Forbidden | Forbidden
- 1|sdsm|fre| 0.00 s 1 frm| 0| Forbidden | Forbidden
- 2|vide|```| 104.12 s 2603 frm| 1960559| VisualStream | Visual ISO/IEC 14496-2 - 720x304
- 3|soun|ger| 104.06 s 2439 frm| 191242| AudioStream | Audio ISO/IEC 14496-3 - 6 ch 24000 hz
- 4|soun|eng| 104.06 s 2439 frm| 194171| AudioStream | Audio ISO/IEC 14496-3 - 6 ch 24000 hz
- 5|subp|ger| 71.08 s 25 frm| 0| Forbidden | Forbidden
- 6|subp|eng| 71.08 s 25 frm| 0| Forbidden | Forbidden
- Output Example #2: audio file with tags
- MP4 FILE: 1 tracks found. Movie time 92.42 sec
- title = 86-Second Blowout
- artist = Yo La Tengo
- album = May I Sing With Me
- year = 1992
- No|type|lng| duration | bitrate| Stream type | Object type
- 0|mdir|und| 92.42 s 3980 frm| 128000| AudioStream | Audio ISO/IEC 14496-3MP4 FILE: 1 tracks found. Movie time 92.42 sec
- */
- void MP4D_printf_info(const MP4D_demux_t *mp4)
- {
- unsigned i;
- printf("\nMP4 FILE: %d tracks found. Movie time %.2f sec\n", mp4->track_count, (4294967296.0*mp4->duration_hi + mp4->duration_lo) / mp4->timescale);
- #define STR_TAG(name) if (mp4->tag.name) printf("%10s = %s\n", #name, mp4->tag.name)
- STR_TAG(title);
- STR_TAG(artist);
- STR_TAG(album);
- STR_TAG(year);
- STR_TAG(comment);
- STR_TAG(genre);
- printf("\nNo|type|lng| duration | bitrate| %-23s| Object type", "Stream type");
- for (i = 0; i < mp4->track_count; i++)
- {
- MP4D_track_t *tr = mp4->track + i;
- printf("\n%2d|%c%c%c%c|%c%c%c|%7.2f s %6d frm| %7d|", i,
- (tr->handler_type >> 24), (tr->handler_type >> 16), (tr->handler_type >> 8), (tr->handler_type >> 0),
- tr->language[0], tr->language[1], tr->language[2],
- (65536.0*65536.0*tr->duration_hi + tr->duration_lo) / tr->timescale,
- tr->sample_count,
- tr->avg_bitrate_bps);
- printf(" %-23s|", GetMP4StreamTypeName(tr->stream_type));
- printf(" %-23s", GetMP4ObjectTypeName(tr->object_type_indication));
- if (tr->handler_type == MP4D_HANDLER_TYPE_SOUN)
- {
- printf(" - %d ch %d hz", tr->SampleDescription.audio.channelcount, tr->SampleDescription.audio.samplerate_hz);
- } else if (tr->handler_type == MP4D_HANDLER_TYPE_VIDE)
- {
- printf(" - %dx%d", tr->SampleDescription.video.width, tr->SampleDescription.video.height);
- }
- }
- printf("\n");
- }
- #endif // MP4D_PRINT_INFO_SUPPORTED
- #endif
|