├── .travis.yml ├── LICENSE ├── README.md ├── scripts ├── build_arm.sh ├── build_x86.sh └── test.sh ├── src ├── mp4defs.h ├── mp4demux.c ├── mp4demux.h ├── mp4mux.c └── mp4mux.h ├── test └── mp4transcode_test.c └── vectors └── ref ├── mp4mux_file.mp4 ├── mp4mux_stream.mp4 ├── track0.audio ├── track1.264 ├── track2.data └── transcoded.mp4 /.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | addons: 3 | apt: 4 | packages: 5 | - libc6-dev-i386 6 | - linux-libc-dev:i386 7 | - gcc-arm-none-eabi 8 | - gcc-arm-linux-gnueabihf 9 | - libnewlib-arm-none-eabi 10 | - gcc-4.8-multilib 11 | - gcc-4.8-aarch64-linux-gnu 12 | - gcc-4.8-powerpc-linux-gnu 13 | - gcc-aarch64-linux-gnu 14 | - gcc-powerpc-linux-gnu 15 | - libc6-armhf-cross 16 | - libc6-arm64-cross 17 | - libc6-powerpc-cross 18 | - libc6-dev-armhf-cross 19 | - libc6-dev-arm64-cross 20 | - libc6-dev-powerpc-cross 21 | - qemu 22 | 23 | os: 24 | - linux 25 | 26 | compiler: 27 | - gcc 28 | 29 | script: 30 | - scripts/build_x86.sh 31 | - scripts/build_arm.sh 32 | - scripts/test.sh 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | 117 | 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Minimalistic MP4 muxer & demuxer 2 | ================================ 3 | 4 | [![Build Status](https://travis-ci.org/aspt/mp4.svg)](https://travis-ci.org/aspt/mp4) 5 | 6 | 7 | MP4 muxer features: 8 | - Support audio, H.264 video and private data tracks 9 | - Option for MP4 streaming (no fseek()) 10 | 11 | MP4 demuxer features: 12 | - Parse MP4 headers, and provide sample sizes & offsets to the application 13 | -------------------------------------------------------------------------------- /scripts/build_arm.sh: -------------------------------------------------------------------------------- 1 | _FILENAME=${0##*/} 2 | CUR_DIR=${0/${_FILENAME}} 3 | CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/ 4 | FLAGS=-"static -mcpu=cortex-a8 -mfpu=neon -mfloat-abi=hard -flto -O3 -std=gnu11 -ffast-math -fomit-frame-pointer -ftree-vectorize -lm" 5 | DEFS="-DNDEBUG" 6 | 7 | pushd $CUR_DIR/.. 8 | 9 | arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4mux_stream_arm_gcc src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=0 10 | arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4mux_file_arm_gcc src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1 11 | arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4demux_arm_gcc src/mp4demux.c -Dmp4demux_test 12 | arm-linux-gnueabihf-gcc ${FLAGS} ${DEFS} -o mp4transcode_arm_gcc test/mp4transcode_test.c src/mp4mux.c src/mp4demux.c -Isrc 13 | -------------------------------------------------------------------------------- /scripts/build_x86.sh: -------------------------------------------------------------------------------- 1 | _FILENAME=${0##*/} 2 | CUR_DIR=${0/${_FILENAME}} 3 | CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/ 4 | FLAGS="-flto -O3 -std=gnu11 -fno-stack-protector -ffunction-sections -fdata-sections -Wl,--gc-sections -lm" 5 | DEFS="-DNDEBUG" 6 | 7 | pushd $CUR_DIR/.. 8 | 9 | gcc ${FLAGS} ${DEFS} -o mp4mux_stream_x86 src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=0 10 | gcc ${FLAGS} ${DEFS} -o mp4mux_file_x86 src/mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1 11 | gcc ${FLAGS} ${DEFS} -o mp4demux_x86 src/mp4demux.c -Dmp4demux_test 12 | gcc ${FLAGS} ${DEFS} -o mp4transcode_x86 test/mp4transcode_test.c src/mp4mux.c src/mp4demux.c -Isrc 13 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | _FILENAME=${0##*/} 2 | CUR_DIR=${0/${_FILENAME}} 3 | CUR_DIR=$(cd $(dirname ${CUR_DIR}); pwd)/$(basename ${CUR_DIR})/ 4 | 5 | pushd $CUR_DIR/.. 6 | 7 | ./mp4mux_stream_x86 mp4mux_stream.mp4 8 | if ! cmp ./mp4mux_stream.mp4 vectors/ref/mp4mux_stream.mp4 >/dev/null 2>&1 9 | then 10 | echo test failed 11 | exit 1 12 | fi 13 | 14 | ./mp4mux_file_x86 mp4mux_file.mp4 15 | if ! cmp ./mp4mux_file.mp4 vectors/ref/mp4mux_file.mp4 >/dev/null 2>&1 16 | then 17 | echo test failed 18 | exit 1 19 | fi 20 | 21 | ./mp4demux_x86 mp4mux_stream.mp4 22 | if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1 23 | then 24 | echo test failed 25 | exit 1 26 | fi 27 | if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1 28 | then 29 | echo test failed 30 | exit 1 31 | fi 32 | if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1 33 | then 34 | echo test failed 35 | exit 1 36 | fi 37 | rm track0.audio 38 | rm track1.264 39 | rm track2.data 40 | 41 | ./mp4demux_x86 mp4mux_file.mp4 42 | if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1 43 | then 44 | echo test failed 45 | exit 1 46 | fi 47 | if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1 48 | then 49 | echo test failed 50 | exit 1 51 | fi 52 | if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1 53 | then 54 | echo test failed 55 | exit 1 56 | fi 57 | rm track0.audio 58 | rm track1.264 59 | rm track2.data 60 | 61 | ./mp4transcode_x86 mp4mux_file.mp4 mp4mux_stream.mp4 62 | if ! cmp ./transcoded.mp4 vectors/ref/transcoded.mp4 >/dev/null 2>&1 63 | then 64 | echo test failed 65 | exit 1 66 | fi 67 | rm mp4mux_stream.mp4 68 | rm mp4mux_file.mp4 69 | rm transcoded.mp4 70 | 71 | 72 | qemu-arm ./mp4mux_stream_arm_gcc mp4mux_stream.mp4 73 | if ! cmp ./mp4mux_stream.mp4 vectors/ref/mp4mux_stream.mp4 >/dev/null 2>&1 74 | then 75 | echo test failed 76 | exit 1 77 | fi 78 | 79 | qemu-arm ./mp4mux_file_arm_gcc mp4mux_file.mp4 80 | if ! cmp ./mp4mux_file.mp4 vectors/ref/mp4mux_file.mp4 >/dev/null 2>&1 81 | then 82 | echo test failed 83 | exit 1 84 | fi 85 | 86 | qemu-arm ./mp4demux_arm_gcc mp4mux_stream.mp4 87 | if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1 88 | then 89 | echo test failed 90 | exit 1 91 | fi 92 | if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1 93 | then 94 | echo test failed 95 | exit 1 96 | fi 97 | if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1 98 | then 99 | echo test failed 100 | exit 1 101 | fi 102 | rm track0.audio 103 | rm track1.264 104 | rm track2.data 105 | 106 | qemu-arm ./mp4demux_arm_gcc mp4mux_file.mp4 107 | if ! cmp ./track0.audio vectors/ref/track0.audio >/dev/null 2>&1 108 | then 109 | echo test failed 110 | exit 1 111 | fi 112 | if ! cmp ./track1.264 vectors/ref/track1.264 >/dev/null 2>&1 113 | then 114 | echo test failed 115 | exit 1 116 | fi 117 | if ! cmp ./track2.data vectors/ref/track2.data >/dev/null 2>&1 118 | then 119 | echo test failed 120 | exit 1 121 | fi 122 | rm track0.audio 123 | rm track1.264 124 | rm track2.data 125 | 126 | qemu-arm ./mp4transcode_arm_gcc mp4mux_file.mp4 mp4mux_stream.mp4 127 | if ! cmp ./transcoded.mp4 vectors/ref/transcoded.mp4 >/dev/null 2>&1 128 | then 129 | echo test failed 130 | exit 1 131 | fi 132 | 133 | rm mp4mux_stream.mp4 134 | rm mp4mux_file.mp4 135 | rm transcoded.mp4 136 | 137 | echo test passed 138 | -------------------------------------------------------------------------------- /src/mp4defs.h: -------------------------------------------------------------------------------- 1 | /** 25.09.2018 @file 2 | * 3 | * Common MP4 definitions 4 | * 5 | * Acronyms: 6 | * AVC = Advanced Video Coding (AKA H.264) 7 | * AAC = Advanced Audio Coding 8 | * OD = Object descriptor 9 | * DSI = Decoder Specific Info (AAC element) 10 | * LC = Low Complexity (AAC profile) 11 | * SPS = Sequence Parameter Set (H.264 element) 12 | * PPS = Picture Parameter Set (H.264 element) 13 | * 14 | * The MP4 file has several tracks. Each track contains a number of 'samples' 15 | * (audio or video frames). Position and size of each sample in the track 16 | * encoded in the track index. 17 | * 18 | */ 19 | 20 | #ifndef mp4defs_H_INCLUDED 21 | #define mp4defs_H_INCLUDED 22 | 23 | /************************************************************************/ 24 | /* Some values of MP4X_track_t::object_type_indication */ 25 | /************************************************************************/ 26 | enum 27 | { 28 | // MPEG-4 AAC (all profiles) 29 | MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3 = 0x40, 30 | 31 | // MPEG-2 AAC, Main profile 32 | MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_MAIN_PROFILE = 0x66, 33 | 34 | // MPEG-2 AAC, LC profile 35 | MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE = 0x67, 36 | 37 | // MPEG-2 AAC, SSR profile 38 | MP4_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_SSR_PROFILE = 0x68, 39 | 40 | // H.264 (AVC) video 41 | MP4_OBJECT_TYPE_AVC = 0x21, 42 | 43 | // http://www.mp4ra.org/object.html 0xC0-E0 && 0xE2 - 0xFE are specified as "user private" 44 | MP4_OBJECT_TYPE_USER_PRIVATE = 0xC0 45 | }; 46 | 47 | 48 | /************************************************************************/ 49 | /* Some values of MP4X_track_t::handler_type */ 50 | /************************************************************************/ 51 | enum 52 | { 53 | // Video track : 'vide' 54 | MP4_HANDLER_TYPE_VIDE = 0x76696465, 55 | // Audio track : 'soun' 56 | MP4_HANDLER_TYPE_SOUN = 0x736F756E, 57 | // General MPEG-4 systems streams (without specific handler). 58 | // Used for private stream, as suggested in http://www.mp4ra.org/handler.html 59 | MP4_HANDLER_TYPE_GESM = 0x6765736D, 60 | // Text comment track 61 | MP4_HANDLER_TYPE_MDIR = 0x6d646972 62 | }; 63 | 64 | /************************************************************************/ 65 | /* Complete box list (most of them not used here) */ 66 | /************************************************************************/ 67 | #define FOUR_CHAR_INT( a, b, c, d ) (((a)<<24)|((b)<<16)|((c)<<8)|(d)) 68 | enum 69 | { 70 | BOX_co64 = FOUR_CHAR_INT( 'c', 'o', '6', '4' ),//ChunkLargeOffsetAtomType 71 | BOX_stco = FOUR_CHAR_INT( 's', 't', 'c', 'o' ),//ChunkOffsetAtomType 72 | BOX_crhd = FOUR_CHAR_INT( 'c', 'r', 'h', 'd' ),//ClockReferenceMediaHeaderAtomType 73 | BOX_ctts = FOUR_CHAR_INT( 'c', 't', 't', 's' ),//CompositionOffsetAtomType 74 | BOX_cprt = FOUR_CHAR_INT( 'c', 'p', 'r', 't' ),//CopyrightAtomType 75 | BOX_url_ = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ),//DataEntryURLAtomType 76 | BOX_urn_ = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ),//DataEntryURNAtomType 77 | BOX_dinf = FOUR_CHAR_INT( 'd', 'i', 'n', 'f' ),//DataInformationAtomType 78 | BOX_dref = FOUR_CHAR_INT( 'd', 'r', 'e', 'f' ),//DataReferenceAtomType 79 | BOX_stdp = FOUR_CHAR_INT( 's', 't', 'd', 'p' ),//DegradationPriorityAtomType 80 | BOX_edts = FOUR_CHAR_INT( 'e', 'd', 't', 's' ),//EditAtomType 81 | BOX_elst = FOUR_CHAR_INT( 'e', 'l', 's', 't' ),//EditListAtomType 82 | BOX_uuid = FOUR_CHAR_INT( 'u', 'u', 'i', 'd' ),//ExtendedAtomType 83 | BOX_free = FOUR_CHAR_INT( 'f', 'r', 'e', 'e' ),//FreeSpaceAtomType 84 | BOX_hdlr = FOUR_CHAR_INT( 'h', 'd', 'l', 'r' ),//HandlerAtomType 85 | BOX_hmhd = FOUR_CHAR_INT( 'h', 'm', 'h', 'd' ),//HintMediaHeaderAtomType 86 | BOX_hint = FOUR_CHAR_INT( 'h', 'i', 'n', 't' ),//HintTrackReferenceAtomType 87 | BOX_mdia = FOUR_CHAR_INT( 'm', 'd', 'i', 'a' ),//MediaAtomType 88 | BOX_mdat = FOUR_CHAR_INT( 'm', 'd', 'a', 't' ),//MediaDataAtomType 89 | BOX_mdhd = FOUR_CHAR_INT( 'm', 'd', 'h', 'd' ),//MediaHeaderAtomType 90 | BOX_minf = FOUR_CHAR_INT( 'm', 'i', 'n', 'f' ),//MediaInformationAtomType 91 | BOX_moov = FOUR_CHAR_INT( 'm', 'o', 'o', 'v' ),//MovieAtomType 92 | BOX_mvhd = FOUR_CHAR_INT( 'm', 'v', 'h', 'd' ),//MovieHeaderAtomType 93 | BOX_stsd = FOUR_CHAR_INT( 's', 't', 's', 'd' ),//SampleDescriptionAtomType 94 | BOX_stsz = FOUR_CHAR_INT( 's', 't', 's', 'z' ),//SampleSizeAtomType 95 | BOX_stz2 = FOUR_CHAR_INT( 's', 't', 'z', '2' ),//CompactSampleSizeAtomType 96 | BOX_stbl = FOUR_CHAR_INT( 's', 't', 'b', 'l' ),//SampleTableAtomType 97 | BOX_stsc = FOUR_CHAR_INT( 's', 't', 's', 'c' ),//SampleToChunkAtomType 98 | BOX_stsh = FOUR_CHAR_INT( 's', 't', 's', 'h' ),//ShadowSyncAtomType 99 | BOX_skip = FOUR_CHAR_INT( 's', 'k', 'i', 'p' ),//SkipAtomType 100 | BOX_smhd = FOUR_CHAR_INT( 's', 'm', 'h', 'd' ),//SoundMediaHeaderAtomType 101 | BOX_stss = FOUR_CHAR_INT( 's', 't', 's', 's' ),//SyncSampleAtomType 102 | BOX_stts = FOUR_CHAR_INT( 's', 't', 't', 's' ),//TimeToSampleAtomType 103 | BOX_trak = FOUR_CHAR_INT( 't', 'r', 'a', 'k' ),//TrackAtomType 104 | BOX_tkhd = FOUR_CHAR_INT( 't', 'k', 'h', 'd' ),//TrackHeaderAtomType 105 | BOX_tref = FOUR_CHAR_INT( 't', 'r', 'e', 'f' ),//TrackReferenceAtomType 106 | BOX_udta = FOUR_CHAR_INT( 'u', 'd', 't', 'a' ),//UserDataAtomType 107 | BOX_vmhd = FOUR_CHAR_INT( 'v', 'm', 'h', 'd' ),//VideoMediaHeaderAtomType 108 | BOX_url = FOUR_CHAR_INT( 'u', 'r', 'l', ' ' ), 109 | BOX_urn = FOUR_CHAR_INT( 'u', 'r', 'n', ' ' ), 110 | 111 | BOX_gnrv = FOUR_CHAR_INT( 'g', 'n', 'r', 'v' ),//GenericVisualSampleEntryAtomType 112 | BOX_gnra = FOUR_CHAR_INT( 'g', 'n', 'r', 'a' ),//GenericAudioSampleEntryAtomType 113 | 114 | //V2 atoms 115 | BOX_ftyp = FOUR_CHAR_INT( 'f', 't', 'y', 'p' ),//FileTypeAtomType 116 | BOX_padb = FOUR_CHAR_INT( 'p', 'a', 'd', 'b' ),//PaddingBitsAtomType 117 | 118 | //MP4 Atoms 119 | BOX_sdhd = FOUR_CHAR_INT( 's', 'd', 'h', 'd' ),//SceneDescriptionMediaHeaderAtomType 120 | BOX_dpnd = FOUR_CHAR_INT( 'd', 'p', 'n', 'd' ),//StreamDependenceAtomType 121 | BOX_iods = FOUR_CHAR_INT( 'i', 'o', 'd', 's' ),//ObjectDescriptorAtomType 122 | BOX_odhd = FOUR_CHAR_INT( 'o', 'd', 'h', 'd' ),//ObjectDescriptorMediaHeaderAtomType 123 | BOX_mpod = FOUR_CHAR_INT( 'm', 'p', 'o', 'd' ),//ODTrackReferenceAtomType 124 | BOX_nmhd = FOUR_CHAR_INT( 'n', 'm', 'h', 'd' ),//MPEGMediaHeaderAtomType 125 | BOX_esds = FOUR_CHAR_INT( 'e', 's', 'd', 's' ),//ESDAtomType 126 | BOX_sync = FOUR_CHAR_INT( 's', 'y', 'n', 'c' ),//OCRReferenceAtomType 127 | BOX_ipir = FOUR_CHAR_INT( 'i', 'p', 'i', 'r' ),//IPIReferenceAtomType 128 | BOX_mp4s = FOUR_CHAR_INT( 'm', 'p', '4', 's' ),//MPEGSampleEntryAtomType 129 | BOX_mp4a = FOUR_CHAR_INT( 'm', 'p', '4', 'a' ),//MPEGAudioSampleEntryAtomType 130 | BOX_mp4v = FOUR_CHAR_INT( 'm', 'p', '4', 'v' ),//MPEGVisualSampleEntryAtomType 131 | 132 | // http://www.itscj.ipsj.or.jp/sc29/open/29view/29n7644t.doc 133 | BOX_avc1 = FOUR_CHAR_INT( 'a', 'v', 'c', '1' ), 134 | BOX_avc2 = FOUR_CHAR_INT( 'a', 'v', 'c', '2' ), 135 | BOX_svc1 = FOUR_CHAR_INT( 's', 'v', 'c', '1' ), 136 | BOX_avcC = FOUR_CHAR_INT( 'a', 'v', 'c', 'C' ), 137 | BOX_svcC = FOUR_CHAR_INT( 's', 'v', 'c', 'C' ), 138 | BOX_btrt = FOUR_CHAR_INT( 'b', 't', 'r', 't' ), 139 | BOX_m4ds = FOUR_CHAR_INT( 'm', '4', 'd', 's' ), 140 | BOX_seib = FOUR_CHAR_INT( 's', 'e', 'i', 'b' ), 141 | 142 | //3GPP atoms 143 | BOX_samr = FOUR_CHAR_INT( 's', 'a', 'm', 'r' ),//AMRSampleEntryAtomType 144 | BOX_sawb = FOUR_CHAR_INT( 's', 'a', 'w', 'b' ),//WB_AMRSampleEntryAtomType 145 | BOX_damr = FOUR_CHAR_INT( 'd', 'a', 'm', 'r' ),//AMRConfigAtomType 146 | BOX_s263 = FOUR_CHAR_INT( 's', '2', '6', '3' ),//H263SampleEntryAtomType 147 | BOX_d263 = FOUR_CHAR_INT( 'd', '2', '6', '3' ),//H263ConfigAtomType 148 | 149 | //V2 atoms - Movie Fragments 150 | BOX_mvex = FOUR_CHAR_INT( 'm', 'v', 'e', 'x' ),//MovieExtendsAtomType 151 | BOX_trex = FOUR_CHAR_INT( 't', 'r', 'e', 'x' ),//TrackExtendsAtomType 152 | BOX_moof = FOUR_CHAR_INT( 'm', 'o', 'o', 'f' ),//MovieFragmentAtomType 153 | BOX_mfhd = FOUR_CHAR_INT( 'm', 'f', 'h', 'd' ),//MovieFragmentHeaderAtomType 154 | BOX_traf = FOUR_CHAR_INT( 't', 'r', 'a', 'f' ),//TrackFragmentAtomType 155 | BOX_tfhd = FOUR_CHAR_INT( 't', 'f', 'h', 'd' ),//TrackFragmentHeaderAtomType 156 | BOX_trun = FOUR_CHAR_INT( 't', 'r', 'u', 'n' ),//TrackFragmentRunAtomType 157 | BOX_mehd = FOUR_CHAR_INT( 'm', 'e', 'h', 'd' ),//MovieExtendsHeaderBox 158 | 159 | // Object Descriptors (OD) data coding 160 | // These takes only 1 byte; this implementation translate to 161 | // + OD_BASE to keep API uniform and safe for string functions 162 | OD_BASE = FOUR_CHAR_INT( '$', '$', '$', '0' ),// 163 | OD_ESD = FOUR_CHAR_INT( '$', '$', '$', '3' ),//SDescriptor_Tag 164 | OD_DCD = FOUR_CHAR_INT( '$', '$', '$', '4' ),//DecoderConfigDescriptor_Tag 165 | OD_DSI = FOUR_CHAR_INT( '$', '$', '$', '5' ),//DecoderSpecificInfo_Tag 166 | OD_SLC = FOUR_CHAR_INT( '$', '$', '$', '6' ),//SLConfigDescriptor_Tag 167 | 168 | BOX_meta = FOUR_CHAR_INT( 'm', 'e', 't', 'a' ), 169 | BOX_ilst = FOUR_CHAR_INT( 'i', 'l', 's', 't' ), 170 | 171 | // Metagata tags, see http://atomicparsley.sourceforge.net/mpeg-4files.html 172 | BOX_calb = FOUR_CHAR_INT( '\xa9', 'a', 'l', 'b'), // album 173 | BOX_cart = FOUR_CHAR_INT( '\xa9', 'a', 'r', 't'), // artist 174 | BOX_aART = FOUR_CHAR_INT( 'a', 'A', 'R', 'T' ), // album artist 175 | BOX_ccmt = FOUR_CHAR_INT( '\xa9', 'c', 'm', 't'), // comment 176 | BOX_cday = FOUR_CHAR_INT( '\xa9', 'd', 'a', 'y'), // year (as string) 177 | BOX_cnam = FOUR_CHAR_INT( '\xa9', 'n', 'a', 'm'), // title 178 | BOX_cgen = FOUR_CHAR_INT( '\xa9', 'g', 'e', 'n'), // custom genre (as string or as byte!) 179 | BOX_trkn = FOUR_CHAR_INT( 't', 'r', 'k', 'n'), // track number (byte) 180 | BOX_disk = FOUR_CHAR_INT( 'd', 'i', 's', 'k'), // disk number (byte) 181 | BOX_cwrt = FOUR_CHAR_INT( '\xa9', 'w', 'r', 't'), // composer 182 | BOX_ctoo = FOUR_CHAR_INT( '\xa9', 't', 'o', 'o'), // encoder 183 | BOX_tmpo = FOUR_CHAR_INT( 't', 'm', 'p', 'o'), // bpm (byte) 184 | BOX_cpil = FOUR_CHAR_INT( 'c', 'p', 'i', 'l'), // compilation (byte) 185 | BOX_covr = FOUR_CHAR_INT( 'c', 'o', 'v', 'r'), // cover art (JPEG/PNG) 186 | BOX_rtng = FOUR_CHAR_INT( 'r', 't', 'n', 'g'), // rating/advisory (byte) 187 | BOX_cgrp = FOUR_CHAR_INT( '\xa9', 'g', 'r', 'p'), // grouping 188 | 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 189 | BOX_pcst = FOUR_CHAR_INT( 'p', 'c', 's', 't'), // podcast (byte) 190 | BOX_catg = FOUR_CHAR_INT( 'c', 'a', 't', 'g'), // category 191 | BOX_keyw = FOUR_CHAR_INT( 'k', 'e', 'y', 'w'), // keyword 192 | BOX_purl = FOUR_CHAR_INT( 'p', 'u', 'r', 'l'), // podcast URL (byte) 193 | BOX_egid = FOUR_CHAR_INT( 'e', 'g', 'i', 'd'), // episode global unique ID (byte) 194 | BOX_desc = FOUR_CHAR_INT( 'd', 'e', 's', 'c'), // description 195 | BOX_clyr = FOUR_CHAR_INT( '\xa9', 'l', 'y', 'r'), // lyrics (may be > 255 bytes) 196 | BOX_tven = FOUR_CHAR_INT( 't', 'v', 'e', 'n'), // tv episode number 197 | BOX_tves = FOUR_CHAR_INT( 't', 'v', 'e', 's'), // tv episode (byte) 198 | BOX_tvnn = FOUR_CHAR_INT( 't', 'v', 'n', 'n'), // tv network name 199 | BOX_tvsh = FOUR_CHAR_INT( 't', 'v', 's', 'h'), // tv show name 200 | BOX_tvsn = FOUR_CHAR_INT( 't', 'v', 's', 'n'), // tv season (byte) 201 | BOX_purd = FOUR_CHAR_INT( 'p', 'u', 'r', 'd'), // purchase date 202 | BOX_pgap = FOUR_CHAR_INT( 'p', 'g', 'a', 'p'), // Gapless Playback (byte) 203 | 204 | //BOX_aart = FOUR_CHAR_INT( 'a', 'a', 'r', 't' ), // Album artist 205 | BOX_cART = FOUR_CHAR_INT( '\xa9', 'A', 'R', 'T'), // artist 206 | BOX_gnre = FOUR_CHAR_INT( 'g', 'n', 'r', 'e'), 207 | 208 | // 3GPP metatags (http://cpansearch.perl.org/src/JHAR/MP4-Info-1.12/Info.pm) 209 | BOX_auth = FOUR_CHAR_INT( 'a', 'u', 't', 'h'), // author 210 | BOX_titl = FOUR_CHAR_INT( 't', 'i', 't', 'l'), // title 211 | BOX_dscp = FOUR_CHAR_INT( 'd', 's', 'c', 'p'), // description 212 | BOX_perf = FOUR_CHAR_INT( 'p', 'e', 'r', 'f'), // performer 213 | BOX_mean = FOUR_CHAR_INT( 'm', 'e', 'a', 'n'), // 214 | BOX_name = FOUR_CHAR_INT( 'n', 'a', 'm', 'e'), // 215 | BOX_data = FOUR_CHAR_INT( 'd', 'a', 't', 'a'), // 216 | 217 | // these from http://lists.mplayerhq.hu/pipermail/ffmpeg-devel/2008-September/053151.html 218 | BOX_albm = FOUR_CHAR_INT( 'a', 'l', 'b', 'm'), // album 219 | BOX_yrrc = FOUR_CHAR_INT( 'y', 'r', 'r', 'c') // album 220 | 221 | }; 222 | 223 | #endif //mp4defs_H_INCLUDED 224 | -------------------------------------------------------------------------------- /src/mp4demux.c: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/src/mp4demux.c -------------------------------------------------------------------------------- /src/mp4demux.h: -------------------------------------------------------------------------------- 1 | /** 20.09.2009 @file 2 | * ISO MP4 file parsing 3 | * 4 | * Portability note: this module uses: 5 | * - Dynamic memory allocation (malloc(), realloc() and free() 6 | * - Direct file access (fgetc(), fread() & fseek()) 7 | * - File size (fstat()) 8 | * 9 | * This module provide functions to decode mp4 indexes, and retrieve 10 | * file position and size for each sample in given track. 11 | * 12 | * Integration scenario example: 13 | * 14 | * // 1. Parse MP4 structure 15 | * if (MP4D__open(mp4, file_handle)) 16 | * { 17 | * // 2. Find tracks, supported by the application 18 | * for (i = 0; i < mp4->track_count; i++) 19 | * { 20 | * if (supported(mp4->track[i].object_type_indication)) 21 | * { 22 | * // 3. Initialize decoder from transparent 'Decoder Specific Info' data 23 | * init_decoder(mp4->track[i].dsi); 24 | * 25 | * // 4. Read track data for each frame 26 | * for (k = 0; k < mp4->track[i].sample_count; k++) 27 | * { 28 | * fseek(file_handle, MP4D__frame_offset(mp4, i, k, &frame_size, NULL, NULL), SEEK_SET); 29 | * fread(buf, frame_size, 1, file_handle); 30 | * decode(buf); 31 | * } 32 | * } 33 | * } 34 | * } 35 | * 36 | */ 37 | 38 | #ifndef mp4demux_H_INCLUDED 39 | #define mp4demux_H_INCLUDED 40 | 41 | #include 42 | #include "mp4defs.h" 43 | 44 | #ifdef __cplusplus 45 | extern "C" { 46 | #endif //__cplusplus 47 | 48 | 49 | /************************************************************************/ 50 | /* Portable 64-bit type definition */ 51 | /************************************************************************/ 52 | 53 | #if (defined(__GNUC__) && __GNUC__ >= 4) || (defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901) 54 | # include // hope that all GCC compilers support this C99 extension 55 | typedef uint64_t mp4d_size_t; 56 | #else 57 | # if defined (_MSC_VER) 58 | typedef unsigned __int64 mp4d_size_t; 59 | # else 60 | typedef unsigned long long mp4d_size_t; 61 | # endif 62 | typedef unsigned int uint32_t; 63 | #endif 64 | 65 | 66 | 67 | typedef struct 68 | { 69 | unsigned first_chunk; 70 | unsigned samples_per_chunk; 71 | } MP4D_sample_to_chunk_t; 72 | 73 | 74 | typedef struct 75 | { 76 | /************************************************************************/ 77 | /* mandatory public data */ 78 | /************************************************************************/ 79 | // How many 'samples' in the track 80 | // The 'sample' is MP4 term, denoting audio or video frame 81 | unsigned sample_count; 82 | 83 | // Decoder-specific info (DSI) data 84 | unsigned char * dsi; 85 | 86 | // DSI data size 87 | unsigned dsi_bytes; 88 | 89 | // MP4 object type code 90 | // case 0x00: return "Forbidden"; 91 | // case 0x01: return "Systems ISO/IEC 14496-1"; 92 | // case 0x02: return "Systems ISO/IEC 14496-1"; 93 | // case 0x20: return "Visual ISO/IEC 14496-2"; 94 | // case 0x40: return "Audio ISO/IEC 14496-3"; 95 | // case 0x60: return "Visual ISO/IEC 13818-2 Simple Profile"; 96 | // case 0x61: return "Visual ISO/IEC 13818-2 Main Profile"; 97 | // case 0x62: return "Visual ISO/IEC 13818-2 SNR Profile"; 98 | // case 0x63: return "Visual ISO/IEC 13818-2 Spatial Profile"; 99 | // case 0x64: return "Visual ISO/IEC 13818-2 High Profile"; 100 | // case 0x65: return "Visual ISO/IEC 13818-2 422 Profile"; 101 | // case 0x66: return "Audio ISO/IEC 13818-7 Main Profile"; 102 | // case 0x67: return "Audio ISO/IEC 13818-7 LC Profile"; 103 | // case 0x68: return "Audio ISO/IEC 13818-7 SSR Profile"; 104 | // case 0x69: return "Audio ISO/IEC 13818-3"; 105 | // case 0x6A: return "Visual ISO/IEC 11172-2"; 106 | // case 0x6B: return "Audio ISO/IEC 11172-3"; 107 | // case 0x6C: return "Visual ISO/IEC 10918-1"; 108 | unsigned object_type_indication; 109 | 110 | 111 | /************************************************************************/ 112 | /* informational public data, not needed for decoding */ 113 | /************************************************************************/ 114 | // handler_type when present in a media box, is an integer containing one of 115 | // the following values, or a value from a derived specification: 116 | // 'vide' Video track 117 | // 'soun' Audio track 118 | // 'hint' Hint track 119 | unsigned handler_type; 120 | 121 | // Track duration: 64-bit value split into 2 variables 122 | unsigned duration_hi; 123 | unsigned duration_lo; 124 | 125 | // duration scale: duration = timescale*seconds 126 | unsigned timescale; 127 | 128 | // Average bitrate, bits per second 129 | unsigned avg_bitrate_bps; 130 | 131 | // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... 132 | unsigned char language[4]; 133 | 134 | // MP4 stream type 135 | // case 0x00: return "Forbidden"; 136 | // case 0x01: return "ObjectDescriptorStream"; 137 | // case 0x02: return "ClockReferenceStream"; 138 | // case 0x03: return "SceneDescriptionStream"; 139 | // case 0x04: return "VisualStream"; 140 | // case 0x05: return "AudioStream"; 141 | // case 0x06: return "MPEG7Stream"; 142 | // case 0x07: return "IPMPStream"; 143 | // case 0x08: return "ObjectContentInfoStream"; 144 | // case 0x09: return "MPEGJStream"; 145 | unsigned stream_type; 146 | 147 | union 148 | { 149 | // for handler_type == 'soun' tracks 150 | struct 151 | { 152 | unsigned channelcount; 153 | unsigned samplerate_hz; 154 | } audio; 155 | 156 | // for handler_type == 'vide' tracks 157 | struct 158 | { 159 | unsigned width; 160 | unsigned height; 161 | } video; 162 | } SampleDescription; 163 | 164 | /************************************************************************/ 165 | /* private data: MP4 indexes */ 166 | /************************************************************************/ 167 | unsigned *entry_size; // [sample_count] 168 | unsigned *timestamp; // [sample_count] 169 | unsigned *duration; // [sample_count] 170 | 171 | unsigned sample_to_chunk_count; 172 | MP4D_sample_to_chunk_t * sample_to_chunk; // [sample_to_chunk_count] 173 | 174 | unsigned chunk_count; 175 | mp4d_size_t * chunk_offset; // [chunk_count] 176 | 177 | } MP4D_track_t; 178 | 179 | 180 | typedef struct MP4D_demux_tag 181 | { 182 | /************************************************************************/ 183 | /* mandatory public data */ 184 | /************************************************************************/ 185 | // number of tracks in the movie 186 | unsigned track_count; 187 | 188 | // track data (public/private) 189 | MP4D_track_t * track; 190 | 191 | /************************************************************************/ 192 | /* informational public data, not needed for decoding */ 193 | /************************************************************************/ 194 | // Movie duration: 64-bit value split into 2 variables 195 | unsigned duration_hi; 196 | unsigned duration_lo; 197 | 198 | // duration scale: duration = timescale*seconds 199 | unsigned timescale; 200 | 201 | // Metadata tag (optional) 202 | // Tags provided 'as-is', without any re-encoding 203 | struct 204 | { 205 | unsigned char *title; 206 | unsigned char *artist; 207 | unsigned char *album; 208 | unsigned char *year; 209 | unsigned char *comment; 210 | unsigned char *genre; 211 | } tag; 212 | 213 | } MP4D_demux_t; 214 | 215 | 216 | /** 217 | * Parse given file as MP4 file. Allocate and store data indexes. 218 | * return 1 on success, 0 on failure 219 | * Given file rewind()'ed on return. 220 | * The MP4 indexes may be stored at the end of file, so this 221 | * function may parse all file, using fseek(). 222 | * It is guaranteed that function will read/seek the file sequentially, 223 | * and will never jump back. 224 | */ 225 | int MP4D__open(MP4D_demux_t * mp4, FILE * f); 226 | 227 | 228 | /** 229 | * Return position and size for given sample from given track. The 'sample' is a 230 | * MP4 term for 'frame' 231 | * 232 | * frame_bytes [OUT] - return coded frame size in bytes 233 | * timestamp [OUT] - return frame timestamp (in mp4->timescale units) 234 | * duration [OUT] - return frame duration (in mp4->timescale units) 235 | * 236 | * function return file offset for the frame 237 | */ 238 | mp4d_size_t MP4D__frame_offset(const MP4D_demux_t * mp4, unsigned int ntrack, unsigned int nsample, unsigned int * frame_bytes, unsigned * timestamp, unsigned * duration); 239 | 240 | 241 | /** 242 | * De-allocated memory 243 | */ 244 | void MP4D__close(MP4D_demux_t * mp4); 245 | 246 | 247 | /** 248 | * Helper functions to parse mp4.track[ntrack].dsi for H.264 SPS/PPS 249 | * Return pointer to internal mp4 memory, it must not be free()-ed 250 | * 251 | * Example: process all SPS in MP4 file: 252 | * while (sps = MP4D__read_sps(mp4, num_of_avc_track, sps_count, &sps_bytes)) 253 | * { 254 | * process(sps, sps_bytes); 255 | * sps_count++; 256 | * } 257 | */ 258 | const unsigned char * MP4D__read_sps(const MP4D_demux_t * mp4, unsigned int ntrack, int nsps, int * sps_bytes); 259 | const unsigned char * MP4D__read_pps(const MP4D_demux_t * mp4, unsigned int ntrack, int npps, int * pps_bytes); 260 | 261 | 262 | 263 | /** 264 | * Decode MP4D_track_t::stream_type to ASCII string 265 | */ 266 | const char * MP4D__stream_type_to_ascii(int stream_type); 267 | 268 | 269 | /** 270 | * Decode MP4D_track_t::object_type_indication to ASCII string 271 | */ 272 | const char * MP4D__object_type_to_ascii(int object_type_indication); 273 | 274 | 275 | #ifdef __cplusplus 276 | } 277 | #endif //__cplusplus 278 | 279 | #endif //mp4demux_H_INCLUDED 280 | -------------------------------------------------------------------------------- /src/mp4mux.c: -------------------------------------------------------------------------------- 1 | /** 24.04.2010 ASP @file 2 | * 3 | * Ref: [1] ISO/IEC 14496-12:2005 4 | * 5 | Option 1: simple mp4 layout, when fseek() available: 6 | 7 | [MDAT] [MOOV] 8 | 9 | A/V data written in single huge MDAT box. fseek() needed to update size of the MDAT box. 10 | 11 | 12 | Option 2: simple mp4 layout, when fseek() NOT available: 13 | 14 | [MDAT] [MDAT] .... [MDAT] [MOOV] 15 | 16 | Each A/V frame written in it's MDAT box 17 | 18 | Options 3&4: fragmented mp4 layout: 19 | 20 | [MOOV] [MOOF][MDAT][MOOF][MDAT] .... [MOOF][MDAT] 21 | 22 | Each A/V frame written in it's MDAT box. Frame side info written in MOOF box before. There is no global index in this file 23 | If fseek() available, media duration in the stream description is updated when closing the file 24 | 25 | **/ 26 | #include "mp4mux.h" 27 | #include 28 | #include 29 | #include 30 | 31 | /************************************************************************/ 32 | /* Build config */ 33 | /************************************************************************/ 34 | // if fseek() avaialable, use single MDAT atom per file, and update data size on close 35 | #ifndef MP4E_CAN_USE_RANDOM_FILE_ACCESS 36 | #define MP4E_CAN_USE_RANDOM_FILE_ACCESS 1 37 | #endif 38 | 39 | // How much memory needed for indexes 40 | // Experimental data: 41 | // file with 1 track = 560 bytes 42 | // file with 2 tracks = 972 bytes 43 | // track size = 412 bytes; 44 | // file header size = 148 bytes 45 | #define FILE_HEADER_BYTES 256 // file header 46 | #define TRACK_HEADER_BYTES 512 // track header 47 | 48 | // File timescale 49 | #define MOOV_TIMESCALE 1000 50 | 51 | typedef unsigned int mp4e_size_t; 52 | 53 | /* 54 | * Sample descriptor 55 | * 1 sample = 1 video frame (incl all slices) 56 | * = 1 audio frame 57 | */ 58 | typedef struct 59 | { 60 | mp4e_size_t size; // sample data size 61 | mp4e_size_t offset; // sample data offset in the mp4 file 62 | unsigned duration; // sample duration, x(1./MP4E_track_t::time_scale) seconds 63 | unsigned flag_random_access; // 1 if sample intra-coded 64 | } sample_t; 65 | 66 | /* 67 | * Dynamically sized memory block ('vector') 68 | */ 69 | typedef struct 70 | { 71 | unsigned char * data; // malloc()-ed memory 72 | size_t bytes; // used size 73 | size_t capacity; // allocated size 74 | } asp_vector_t; 75 | 76 | /* 77 | * Track descriptor 78 | * Track is a sequence of samples. There are 1 or several tracks in the mp4 file 79 | */ 80 | typedef struct 81 | { 82 | MP4E_track_t info; // Application-supplied track description 83 | asp_vector_t smpl; // samples descriptor 84 | asp_vector_t vsps; // SPS for video or DSI for audio 85 | asp_vector_t vpps; // PPS for video, not used for audio 86 | } track_t; 87 | 88 | /* 89 | * MP4 file descriptor 90 | */ 91 | typedef struct MP4E_mux_tag 92 | { 93 | asp_vector_t tracks; // mp4 file tracks 94 | FILE * mp4file; // output file handle 95 | mp4e_size_t write_pos; // ## of bytes written ~ current file position (until 1st fseek) 96 | char * text_comment; // application-supplied file comment 97 | int enable_fragmentation; // flag, indicating streaming-friendly 'fragmentation' mode 98 | int fragments_count; // # of fragments in 'fragmentation' mode 99 | } MP4E_mux_t; 100 | 101 | 102 | 103 | /* 104 | * Endian-independent byte-write macros 105 | */ 106 | #define MP4_WR(x, n) *write_ptr++ = (unsigned char)((x) >> 8*n) 107 | #define WR1(x) MP4_WR(x,0); 108 | #define WR2(x) MP4_WR(x,1); MP4_WR(x,0); 109 | #define WR3(x) MP4_WR(x,2); MP4_WR(x,1); MP4_WR(x,0); 110 | #define WR4(x) MP4_WR(x,3); MP4_WR(x,2); MP4_WR(x,1); MP4_WR(x,0); 111 | // esc-coded OD length 112 | #define MP4_WRITE_OD_LEN(size) if (size > 0x7F) do { size -= 0x7F; WR1(0x00ff);} while (size > 0x7F); WR1(size) 113 | 114 | #define MP4_WRITE_STRING(s) {size_t len = strlen(s) + 1; memcpy(write_ptr, s, len); write_ptr += len;} 115 | 116 | #define MP4_WR4_PTR(p,x) (p)[0]=(char)((x) >> 8*3); (p)[1]=(char)((x) >> 8*2); (p)[2]=(char)((x) >> 8*1); (p)[3]=(char)((x)); 117 | 118 | // Finish atom: update atom size field 119 | #define MP4_END_ATOM --stack; MP4_WR4_PTR((unsigned char*)*stack, write_ptr - *stack);} 120 | 121 | // Initiate atom: save position of size field on stack 122 | #define MP4_ATOM(x) {*stack++ = write_ptr; write_ptr += 4; WR4(x); 123 | 124 | // Atom with 'FullAtomVersionFlags' field 125 | #define MP4_FULL_ATOM(x, flag) MP4_ATOM(x); WR4(flag); 126 | 127 | 128 | static int mp4e_write_index(MP4E_mux_t * mux); 129 | 130 | /************************************************************************/ 131 | /* File output (non-portable) stuff */ 132 | /************************************************************************/ 133 | 134 | /** 135 | * Data stream output function 136 | */ 137 | static size_t mp4e_fwrite(MP4E_mux_t * mux, const void *buffer, size_t size) 138 | { 139 | mux->write_pos += size; 140 | return fwrite(buffer, size, 1, mux->mp4file); 141 | } 142 | 143 | /************************************************************************/ 144 | /* Abstract vector data structure */ 145 | /************************************************************************/ 146 | /** 147 | * Allocate vector with given size, return 1 on success, 0 on fail 148 | */ 149 | static int asp_vector_init(asp_vector_t * h, int capacity) 150 | { 151 | h->bytes = 0; 152 | h->capacity = capacity; 153 | h->data = capacity ? (unsigned char *)malloc(capacity) : NULL; 154 | return !capacity || !!h->data; 155 | } 156 | 157 | /** 158 | * Deallocates vector memory 159 | */ 160 | static void asp_vector_reset(asp_vector_t * h) 161 | { 162 | free(h->data); 163 | memset(h, 0, sizeof(asp_vector_t)); 164 | } 165 | 166 | /** 167 | * Allocates given number of bytes at the end of vector data, increasing 168 | * vector memory if necessary. 169 | * Return allocated memory. 170 | */ 171 | static unsigned char * asp_vector_alloc_tail(asp_vector_t * h, size_t bytes) 172 | { 173 | unsigned char * p; 174 | if (h->bytes + bytes > h->capacity) 175 | { 176 | size_t grow_by = (bytes > h->capacity/2 ? bytes : h->capacity/2); 177 | size_t new_size = (h->capacity + grow_by + 1024) & -1024; // less-realloc's variant 178 | p = (unsigned char *)realloc(h->data, new_size); 179 | if (!p) 180 | { 181 | return NULL; 182 | } 183 | h->data = p; 184 | h->capacity = new_size; 185 | } 186 | assert(h->capacity - h->bytes >= bytes); 187 | p = h->data + h->bytes; 188 | h->bytes += bytes; 189 | return p; 190 | } 191 | 192 | /** 193 | * Append data to the end of the vector (accumulate or enqueue) 194 | */ 195 | static unsigned char * asp_vector_put(asp_vector_t * h, const void * buf, int bytes) 196 | { 197 | unsigned char * tail = asp_vector_alloc_tail(h, bytes); 198 | if (tail) 199 | { 200 | memcpy(tail, buf, bytes); 201 | } 202 | return tail; 203 | } 204 | 205 | /************************************************************************/ 206 | /* Index data structure managment functions */ 207 | /************************************************************************/ 208 | 209 | /** 210 | * Destroy multiplexer object: release memory, close output file handle 211 | */ 212 | static void mp4e_free(MP4E_mux_t * mux) 213 | { 214 | if (mux) 215 | { 216 | unsigned long ntr, ntracks = mux->tracks.bytes / sizeof(track_t); 217 | for (ntr = 0; ntr < ntracks; ntr++) 218 | { 219 | track_t* tr = ((track_t*)mux->tracks.data) + ntr; 220 | asp_vector_reset(&tr->vsps); 221 | asp_vector_reset(&tr->vpps); 222 | asp_vector_reset(&tr->smpl); 223 | } 224 | asp_vector_reset(&mux->tracks); 225 | fclose(mux->mp4file); 226 | free(mux->text_comment); 227 | free(mux); 228 | } 229 | } 230 | 231 | /** 232 | * Append new SPS/PPS to the list, keeping them in MP4 format (16-bit data_size + data) 233 | */ 234 | static int mp4e_sps_pps_append_mem(asp_vector_t * v, const void * mem, int bytes) 235 | { 236 | size_t i; 237 | unsigned char size[2]; 238 | const unsigned char * p = v->data; 239 | for (i = 0; i + 2 < v->bytes;) 240 | { 241 | int cb = p[i]*256 + p[i+1]; 242 | if (cb == bytes && !memcmp(p+i+2, mem, cb)) 243 | { 244 | return 1; 245 | } 246 | i += 2 + cb; 247 | } 248 | size[0] = bytes >> 8; 249 | size[1] = bytes; 250 | return asp_vector_put(v, size, 2) && asp_vector_put(v, mem, bytes); 251 | } 252 | 253 | /** 254 | * Count number of SPS/PPS in the list 255 | */ 256 | static int mp4e_sps_pps_items_count(asp_vector_t * v) 257 | { 258 | int count= 0; 259 | size_t i; 260 | const unsigned char * p = v->data; 261 | for (i = 0; i + 2 < v->bytes;) 262 | { 263 | int cb = p[i]*256 + p[i+1]; 264 | count++; 265 | i += 2 + cb; 266 | } 267 | return count; 268 | } 269 | 270 | /** 271 | * Return sum of track samples duration. 272 | */ 273 | static unsigned mp4e_get_track_duration(const track_t * tr) 274 | { 275 | unsigned i, sum_duration = 0; 276 | const sample_t * s = (const sample_t *)tr->smpl.data; 277 | for (i = 0; i < tr->smpl.bytes/sizeof(sample_t); i++) 278 | { 279 | sum_duration += s[i].duration; 280 | } 281 | return sum_duration; 282 | } 283 | 284 | /** 285 | * calculate size of length field of OD box 286 | */ 287 | static int mp4e_od_size_of_size(int size) 288 | { 289 | int i, size_of_size = 1; 290 | for (i = size; i > 0x7F; i -= 0x7F) size_of_size++; 291 | return size_of_size; 292 | } 293 | 294 | /** 295 | * Append sample descriptor to the samples list 296 | */ 297 | static int mp4e_add_sample_descriptor(MP4E_mux_t * mux, track_t * tr, int data_bytes, int duration, int kind) 298 | { 299 | sample_t smp; 300 | smp.size = data_bytes; 301 | smp.offset = (mp4e_size_t)mux->write_pos; 302 | smp.duration = (duration ? duration : tr->info.default_duration); 303 | smp.flag_random_access = (kind == MP4E_SAMPLE_RANDOM_ACCESS); 304 | return NULL != asp_vector_put(&tr->smpl, &smp, sizeof(sample_t)); 305 | } 306 | 307 | /************************************************************************/ 308 | /* Data write functions */ 309 | /************************************************************************/ 310 | 311 | /** 312 | * Write fixed file header: 'ftyp' box 313 | */ 314 | static int mp4e_write_file_header(MP4E_mux_t * mux) 315 | { 316 | // hardcoded part of the file header 317 | static const unsigned char mp4e_box_ftyp[] = 318 | { 319 | #if 1 320 | 0,0,0,0x18,'f','t','y','p', 321 | 'm','p','4','2',0,0,0,0, 322 | 'm','p','4','2','i','s','o','m', 323 | #else 324 | // as in ffmpeg 325 | 0,0,0,0x20,'f','t','y','p', 326 | 'i','s','o','m',0,0,2,0, 327 | 'm','p','4','1','i','s','o','m', 328 | 'i','s','o','2','a','v','c','1', 329 | #endif 330 | }; 331 | return mp4e_fwrite(mux, mp4e_box_ftyp, sizeof(mp4e_box_ftyp)) ? sizeof(mp4e_box_ftyp) : 0; 332 | } 333 | 334 | /** 335 | * Write data header: 'mdat' box 336 | */ 337 | static int mp4e_write_mdat_box(MP4E_mux_t * mux, unsigned data_bytes) 338 | { 339 | unsigned char write_base[8], *write_ptr = write_base; // for WR4 macro 340 | WR4(data_bytes); 341 | WR4(BOX_mdat); 342 | return mp4e_fwrite(mux, write_base, write_ptr - write_base); 343 | } 344 | 345 | /** 346 | * Write Movie Fragment: 'moof' box 347 | */ 348 | static int mp4e_write_fragment_header(MP4E_mux_t * mux, int track_num, int data_bytes, int duration, int kind) 349 | { 350 | unsigned char write_base[888], *write_ptr = write_base; // for WRITE_4 macro 351 | // atoms nesting stack 352 | unsigned char * stack_base[20]; 353 | unsigned char ** stack = stack_base; 354 | unsigned flags; 355 | unsigned char * pdata_offset; 356 | enum 357 | { 358 | default_sample_duration_present = 0x000008, 359 | default_sample_flags_present = 0x000020, 360 | } e; 361 | 362 | track_t * tr = ((track_t*)mux->tracks.data) + track_num; 363 | 364 | MP4_ATOM(BOX_moof) 365 | MP4_FULL_ATOM(BOX_mfhd, 0) 366 | WR4(mux->fragments_count); // start from 1 367 | MP4_END_ATOM 368 | MP4_ATOM(BOX_traf) 369 | flags = 0; 370 | if (tr->info.track_media_kind == e_video) 371 | { 372 | flags |= 0x20; // default-sample-flags-present 373 | } 374 | else 375 | { 376 | flags |= 0x08; // default-sample-duration-present 377 | } 378 | flags = (tr->info.track_media_kind == e_video) ? 0x20020 : 0x20008; 379 | 380 | MP4_FULL_ATOM(BOX_tfhd, flags) 381 | WR4(track_num+1); // track_ID 382 | if (tr->info.track_media_kind == e_video) 383 | { 384 | flags = 0x001; // data-offset-present 385 | flags |= 0x100; // sample-duration-present 386 | WR4(0x1010000); // default_sample_flags 387 | } 388 | else 389 | { 390 | WR4(duration); 391 | } 392 | MP4_END_ATOM 393 | if (tr->info.track_media_kind == e_audio) 394 | { 395 | flags = 0; 396 | flags |= 0x001; // data-offset-present 397 | flags |= 0x200; // sample-size-present 398 | MP4_FULL_ATOM(BOX_trun, flags) 399 | WR4(1); // sample_count 400 | pdata_offset = write_ptr; write_ptr += 4; // save ptr to data_offset 401 | WR4(duration); // sample_duration 402 | MP4_END_ATOM 403 | } 404 | else if (kind == MP4E_SAMPLE_RANDOM_ACCESS) 405 | { 406 | flags = 0; 407 | flags |= 0x001; // data-offset-present 408 | flags |= 0x004; // first-sample-flags-present 409 | flags |= 0x100; // sample-duration-present 410 | flags |= 0x200; // sample-size-present 411 | MP4_FULL_ATOM(BOX_trun, flags) 412 | WR4(1); // sample_count 413 | pdata_offset = write_ptr; write_ptr += 4; // save ptr to data_offset 414 | WR4(0x2000000); // first_sample_flags 415 | WR4(duration); // sample_duration 416 | WR4(data_bytes); // sample_size 417 | MP4_END_ATOM 418 | } 419 | else 420 | { 421 | flags = 0; 422 | flags |= 0x001; // data-offset-present 423 | flags |= 0x100; // sample-duration-present 424 | flags |= 0x200; // sample-size-present 425 | MP4_FULL_ATOM(BOX_trun, flags) 426 | WR4(1); // sample_count 427 | pdata_offset = write_ptr; write_ptr += 4; // save ptr to data_offset 428 | WR4(duration); // sample_duration 429 | WR4(data_bytes); // sample_size 430 | MP4_END_ATOM 431 | } 432 | MP4_END_ATOM 433 | MP4_END_ATOM 434 | MP4_WR4_PTR(pdata_offset, (write_ptr - write_base) + 8); 435 | 436 | return mp4e_fwrite(mux, write_base, write_ptr - write_base); 437 | } 438 | 439 | /** 440 | * Write file index 'moov' box with all its boxes and indexes 441 | */ 442 | static int mp4e_write_index(MP4E_mux_t * mux) 443 | { 444 | // atoms nesting stack 445 | unsigned char * stack_base[20]; 446 | unsigned char ** stack = stack_base; 447 | 448 | // in-memory indexes 449 | unsigned char * write_base, * write_ptr; 450 | 451 | unsigned int ntr, index_bytes, ntracks; 452 | int i, error_code; 453 | 454 | mp4e_size_t mdat_end = mux->write_pos; 455 | 456 | ntracks = (unsigned int)(mux->tracks.bytes / sizeof(track_t)); 457 | index_bytes = FILE_HEADER_BYTES; 458 | if (mux->text_comment) 459 | { 460 | index_bytes += 128 + strlen(mux->text_comment); 461 | } 462 | for (ntr = 0; ntr < ntracks; ntr++) 463 | { 464 | track_t * tr = ((track_t*)mux->tracks.data) + ntr; 465 | index_bytes += TRACK_HEADER_BYTES; // fixed amount (implementation-dependent) 466 | // may need extra 4 bytes for duration field + 4 bytes for worst-case random access box 467 | index_bytes += tr->smpl.bytes * (sizeof(sample_t) + 4 + 4) / sizeof(sample_t); 468 | index_bytes += tr->vsps.bytes; 469 | index_bytes += tr->vpps.bytes; 470 | } 471 | 472 | // Allocate index memory 473 | write_base = (unsigned char*)malloc(index_bytes); 474 | if (!write_base) 475 | { 476 | return MP4E_STATUS_NO_MEMORY; 477 | } 478 | write_ptr = write_base; 479 | 480 | // 481 | // Write index atoms; order taken from Table 1 of [1] 482 | // 483 | MP4_ATOM(BOX_moov); 484 | MP4_FULL_ATOM(BOX_mvhd, 0); 485 | WR4(0); // creation_time 486 | WR4(0); // modification_time 487 | 488 | if (ntracks) 489 | { 490 | track_t * tr = ((track_t*)mux->tracks.data) + 0; // take 1st track 491 | unsigned duration = mp4e_get_track_duration(tr); 492 | duration = (unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale); 493 | WR4(MOOV_TIMESCALE); // duration 494 | WR4(duration); // duration 495 | } 496 | 497 | WR4(0x00010000); // rate 498 | WR2(0x0100); // volume 499 | WR2(0); // reserved 500 | WR4(0); // reserved 501 | WR4(0); // reserved 502 | 503 | // matrix[9] 504 | WR4(0x00010000);WR4(0);WR4(0); 505 | WR4(0);WR4(0x00010000);WR4(0); 506 | WR4(0);WR4(0);WR4(0x40000000); 507 | 508 | // pre_defined[6] 509 | WR4(0); WR4(0); WR4(0); 510 | WR4(0); WR4(0); WR4(0); 511 | 512 | //next_track_ID is a non-zero integer that indicates a value to use for the track ID of the next track to be 513 | //added to this presentation. Zero is not a valid track ID value. The value of next_track_ID shall be 514 | //larger than the largest track-ID in use. 515 | WR4(ntracks+1); 516 | MP4_END_ATOM; 517 | 518 | for (ntr = 0; ntr < ntracks; ntr++) 519 | { 520 | track_t * tr = ((track_t*)mux->tracks.data) + ntr; 521 | unsigned duration = mp4e_get_track_duration(tr); 522 | int samples_count = (int)(tr->smpl.bytes / sizeof(sample_t)); 523 | const sample_t * sample = (const sample_t *)tr->smpl.data; 524 | unsigned handler_type; 525 | const char * handler_ascii = NULL; 526 | 527 | if (mux->enable_fragmentation) 528 | { 529 | samples_count = 0; 530 | } 531 | else if (samples_count <= 0) 532 | { 533 | continue; // skip empty track 534 | } 535 | 536 | switch (tr->info.track_media_kind) 537 | { 538 | case e_audio: 539 | handler_type = MP4_HANDLER_TYPE_SOUN; 540 | handler_ascii = "SoundHandler"; 541 | break; 542 | case e_video: 543 | handler_type = MP4_HANDLER_TYPE_VIDE; 544 | handler_ascii = "VideoHandler"; 545 | break; 546 | case e_private: 547 | handler_type = MP4_HANDLER_TYPE_GESM; 548 | break; 549 | default: 550 | return MP4E_STATUS_BAD_ARGUMENTS; 551 | } 552 | 553 | MP4_ATOM(BOX_trak); 554 | MP4_FULL_ATOM(BOX_tkhd, 7); // flag: 1=trak enabled; 2=track in movie; 4=track in preview 555 | WR4(0); // creation_time 556 | WR4(0); // modification_time 557 | WR4(ntr+1); // track_ID 558 | WR4(0); // reserved 559 | WR4((unsigned)(duration * 1LL * MOOV_TIMESCALE / tr->info.time_scale)); 560 | WR4(0); WR4(0); // reserved[2] 561 | WR2(0); // layer 562 | WR2(0); // alternate_group 563 | WR2(0x0100); // volume {if track_is_audio 0x0100 else 0}; 564 | WR2(0); // reserved 565 | 566 | // matrix[9] 567 | WR4(0x00010000);WR4(0);WR4(0); 568 | WR4(0);WR4(0x00010000);WR4(0); 569 | WR4(0);WR4(0);WR4(0x40000000); 570 | 571 | if (tr->info.track_media_kind == e_audio || tr->info.track_media_kind == e_private) 572 | { 573 | WR4(0); // width 574 | WR4(0); // height 575 | } 576 | else 577 | { 578 | WR4(tr->info.u.v.width*0x10000); // width 579 | WR4(tr->info.u.v.height*0x10000); // height 580 | } 581 | MP4_END_ATOM; 582 | 583 | MP4_ATOM(BOX_mdia); 584 | MP4_FULL_ATOM(BOX_mdhd, 0); 585 | WR4(0); // creation_time 586 | WR4(0); // modification_time 587 | WR4(tr->info.time_scale); 588 | WR4(duration); // duration 589 | { 590 | int lang_code = ((tr->info.language[0]&31) << 10) | ((tr->info.language[1]&31) << 5) | (tr->info.language[2]&31); 591 | WR2(lang_code); // language 592 | } 593 | WR2(0); // pre_defined 594 | MP4_END_ATOM; 595 | 596 | MP4_FULL_ATOM(BOX_hdlr, 0); 597 | WR4(0); // pre_defined 598 | WR4(handler_type); // handler_type 599 | WR4(0); WR4(0); WR4(0); // reserved[3] 600 | // 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). 601 | // set mdia hdlr name field to what quicktime uses. 602 | // Sony smartphone may fail to decode short files w/o handler name 603 | if (handler_ascii) 604 | { 605 | MP4_WRITE_STRING(handler_ascii); 606 | } 607 | else 608 | { 609 | WR4(0); 610 | } 611 | MP4_END_ATOM; 612 | 613 | MP4_ATOM(BOX_minf); 614 | 615 | if (tr->info.track_media_kind == e_audio) 616 | { 617 | // Sound Media Header Box 618 | MP4_FULL_ATOM(BOX_smhd, 0); 619 | WR2(0); // balance 620 | WR2(0); // reserved 621 | MP4_END_ATOM; 622 | } 623 | if (tr->info.track_media_kind == e_video) 624 | { 625 | // mandatory Video Media Header Box 626 | MP4_FULL_ATOM(BOX_vmhd, 1); 627 | WR2(0); // graphicsmode 628 | WR2(0); WR2(0); WR2(0); // opcolor[3] 629 | MP4_END_ATOM; 630 | } 631 | 632 | MP4_ATOM(BOX_dinf); 633 | MP4_FULL_ATOM(BOX_dref, 0); 634 | WR4(1); // entry_count 635 | // 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) 636 | // shall be supplied in the entry field. 637 | 638 | // ASP the correct way to avoid supply the string, is to use flag 1 639 | // otherwise ISO reference demux crashes 640 | MP4_FULL_ATOM(BOX_url, 1); 641 | MP4_END_ATOM; 642 | MP4_END_ATOM; 643 | MP4_END_ATOM; 644 | 645 | MP4_ATOM(BOX_stbl); 646 | MP4_FULL_ATOM(BOX_stsd, 0); 647 | WR4(1); // entry_count; 648 | 649 | if (tr->info.track_media_kind == e_audio || 650 | tr->info.track_media_kind == e_private 651 | ) 652 | { 653 | // AudioSampleEntry() assume MP4E_HANDLER_TYPE_SOUN 654 | unsigned box_name = (tr->info.track_media_kind == e_audio) ? BOX_mp4a : BOX_mp4s; 655 | MP4_ATOM(box_name); 656 | 657 | // SampleEntry 658 | WR4(0); WR2(0); // reserved[6] 659 | WR2(1); // data_reference_index; - this is a tag for descriptor below 660 | 661 | if (tr->info.track_media_kind == e_audio) 662 | { 663 | // AudioSampleEntry 664 | WR4(0); WR4(0); // reserved[2] 665 | WR2(tr->info.u.a.channelcount); // channelcount 666 | WR2(16); // samplesize 667 | WR4(0); // pre_defined+reserved 668 | WR4((tr->info.time_scale << 16)); // samplerate == = {timescale of media}<<16; 669 | } 670 | 671 | MP4_FULL_ATOM(BOX_esds, 0); 672 | if (tr->vsps.bytes > 0) 673 | { 674 | int dsi_bytes = (int)(tr->vsps.bytes) - 2; // - two bytes size field 675 | int dsi_size_size = mp4e_od_size_of_size(dsi_bytes); 676 | int dcd_bytes = dsi_bytes + dsi_size_size + 1 + (1+1+3+4+4); 677 | int dcd_size_size = mp4e_od_size_of_size(dcd_bytes); 678 | int esd_bytes = dcd_bytes + dcd_size_size + 1 + 3; 679 | 680 | WR1(3); // OD_ESD 681 | MP4_WRITE_OD_LEN(esd_bytes); 682 | WR2(0); // ES_ID(2) // TODO - what is this? 683 | WR1(0); // flags(1) 684 | 685 | WR1(4); // OD_DCD 686 | MP4_WRITE_OD_LEN(dcd_bytes); 687 | if (tr->info.track_media_kind == e_audio) 688 | { 689 | WR1(tr->info.object_type_indication); // OD_DCD 690 | //WR1(MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3); // OD_DCD 691 | WR1(5<<2); // stream_type == AudioStream 692 | } 693 | else 694 | { 695 | // http://xhelmboyx.tripod.com/formats/mp4-layout.txt 696 | WR1(208); // 208 = private video 697 | WR1(32<<2); // stream_type == user private 698 | } 699 | WR3(tr->info.u.a.channelcount * 6144/8); // bufferSizeDB in bytes, constant as in reference decoder 700 | WR4(0); // maxBitrate TODO 701 | WR4(0); // avg_bitrate_bps TODO 702 | 703 | WR1(5); // OD_DSI 704 | MP4_WRITE_OD_LEN(dsi_bytes); 705 | for (i = 0; i < dsi_bytes; i++) 706 | { 707 | WR1(tr->vsps.data[2+i]); 708 | } 709 | } 710 | MP4_END_ATOM; 711 | MP4_END_ATOM; 712 | } 713 | 714 | if (tr->info.track_media_kind == e_video) 715 | { 716 | unsigned int sps_count = mp4e_sps_pps_items_count(&tr->vsps); 717 | unsigned int pps_count = mp4e_sps_pps_items_count(&tr->vpps); 718 | 719 | MP4_ATOM(BOX_avc1); 720 | // VisualSampleEntry 8.16.2 721 | // extends SampleEntry 722 | WR2(0); // reserved 723 | WR2(0); // reserved 724 | WR2(0); // reserved 725 | WR2(1); // data_reference_index 726 | WR2(0); // pre_defined 727 | WR2(0); // reserved 728 | WR4(0); // pre_defined 729 | WR4(0); // pre_defined 730 | WR4(0); // pre_defined 731 | WR2(tr->info.u.v.width); 732 | WR2(tr->info.u.v.height); 733 | WR4(0x00480000); // horizresolution = 72 dpi 734 | WR4(0x00480000); // vertresolution = 72 dpi 735 | WR4(0); // reserved 736 | WR2(1); // frame_count 737 | for (i = 0; i < 32; i++) 738 | { 739 | WR1(0); // compressorname 740 | } 741 | WR2(24); // depth 742 | WR2(0xffff); // pre_defined 743 | 744 | MP4_ATOM(BOX_avcC); 745 | // AVCDecoderConfigurationRecord 5.2.4.1.1 746 | WR1(1); // configurationVersion 747 | WR1(tr->vsps.data[2+1]); // 748 | WR1(tr->vsps.data[2+2]); // 749 | WR1(tr->vsps.data[2+3]); // 750 | WR1(255); // 0xfc + NALU_len-1 751 | WR1(0xe0 | sps_count); 752 | for (i = 0; i < (int)tr->vsps.bytes; i++) 753 | { 754 | WR1(tr->vsps.data[i]); 755 | } 756 | 757 | WR1(pps_count); 758 | for (i = 0; i < (int)tr->vpps.bytes; i++) 759 | { 760 | WR1(tr->vpps.data[i]); 761 | } 762 | 763 | MP4_END_ATOM; 764 | MP4_END_ATOM; 765 | } 766 | MP4_END_ATOM; 767 | 768 | /************************************************************************/ 769 | /* indexes */ 770 | /************************************************************************/ 771 | 772 | // Time to Sample Box 773 | MP4_FULL_ATOM(BOX_stts, 0); 774 | { 775 | unsigned char * pentry_count = write_ptr; 776 | int cnt = 1, entry_count = 0; 777 | WR4(0); 778 | for (i = 0; i < samples_count; i++, cnt++) 779 | { 780 | if (i == samples_count-1 || sample[i].duration != sample[i+1].duration) 781 | { 782 | WR4(cnt); 783 | WR4(sample[i].duration); 784 | cnt = 0; 785 | entry_count++; 786 | } 787 | } 788 | MP4_WR4_PTR(pentry_count, entry_count); 789 | } 790 | MP4_END_ATOM; 791 | 792 | // Sample To Chunk Box 793 | MP4_FULL_ATOM(BOX_stsc, 0); 794 | if (mux->enable_fragmentation) 795 | { 796 | WR4(0); // entry_count 797 | } 798 | else 799 | { 800 | WR4(1); // entry_count 801 | WR4(1); // first_chunk; 802 | WR4(1); // samples_per_chunk; 803 | WR4(1); // sample_description_index; 804 | } 805 | MP4_END_ATOM; 806 | 807 | // Sample Size Box 808 | MP4_FULL_ATOM(BOX_stsz, 0); 809 | WR4(0); // sample_size If this field is set to 0, then the samples have different sizes, and those sizes 810 | // are stored in the sample size table. 811 | WR4(samples_count); // sample_count; 812 | for (i = 0; i < samples_count; i++) 813 | { 814 | WR4(sample[i].size); 815 | } 816 | MP4_END_ATOM; 817 | 818 | // Chunk Offset Box 819 | MP4_FULL_ATOM(BOX_stco, 0); 820 | WR4(samples_count); // entry_count 821 | for (i = 0; i < samples_count; i++) 822 | { 823 | WR4(sample[i].offset); 824 | } 825 | MP4_END_ATOM; 826 | 827 | // Sync Sample Box 828 | { 829 | int ra_count = 0; 830 | for (i = 0; i < samples_count; i++) 831 | { 832 | ra_count += !!sample[i].flag_random_access; 833 | } 834 | if (ra_count != samples_count) 835 | { 836 | // If the sync sample box is not present, every sample is a random access point. 837 | MP4_FULL_ATOM(BOX_stss, 0); 838 | WR4(ra_count); 839 | for (i = 0; i < samples_count; i++) 840 | { 841 | if (sample[i].flag_random_access) 842 | { 843 | WR4(i+1); 844 | } 845 | } 846 | MP4_END_ATOM; 847 | } 848 | } 849 | MP4_END_ATOM; 850 | MP4_END_ATOM; 851 | MP4_END_ATOM; 852 | MP4_END_ATOM; 853 | } // tracks loop 854 | 855 | if (mux->text_comment) 856 | { 857 | MP4_ATOM(BOX_udta); 858 | MP4_FULL_ATOM(BOX_meta, 0); 859 | MP4_FULL_ATOM(BOX_hdlr, 0); 860 | WR4(0); // pre_defined 861 | WR4(MP4_HANDLER_TYPE_MDIR); // handler_type 862 | WR4(0); WR4(0); WR4(0); // reserved[3] 863 | WR4(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). 864 | MP4_END_ATOM; 865 | MP4_ATOM(BOX_ilst); 866 | MP4_ATOM((unsigned)BOX_ccmt); 867 | MP4_ATOM(BOX_data); 868 | WR4(1); // type 869 | WR4(0); //lang 870 | MP4_WRITE_STRING(mux->text_comment); 871 | MP4_END_ATOM; 872 | MP4_END_ATOM; 873 | MP4_END_ATOM; 874 | MP4_END_ATOM; 875 | MP4_END_ATOM; 876 | } 877 | 878 | if (mux->enable_fragmentation) 879 | { 880 | track_t * tr = ((track_t*)mux->tracks.data) + 0; 881 | unsigned movie_duration = mp4e_get_track_duration(tr); 882 | 883 | MP4_ATOM(BOX_mvex); 884 | MP4_FULL_ATOM(BOX_mehd, 0); 885 | WR4(movie_duration); // duration 886 | MP4_END_ATOM; 887 | for (ntr = 0; ntr < ntracks; ntr++) 888 | { 889 | MP4_FULL_ATOM(BOX_trex, 0); 890 | WR4(ntr+1); // track_ID 891 | WR4(1); // default_sample_description_index 892 | WR4(0); // default_sample_duration 893 | WR4(0); // default_sample_size 894 | WR4(0); // default_sample_flags 895 | MP4_END_ATOM; 896 | } 897 | MP4_END_ATOM; 898 | } 899 | 900 | MP4_END_ATOM; // moov atom 901 | 902 | assert((unsigned)(write_ptr - write_base) <= index_bytes); 903 | 904 | if (!mp4e_fwrite(mux, write_base, write_ptr - write_base)) 905 | { 906 | error_code = MP4E_STATUS_FILE_WRITE_ERROR; 907 | } 908 | else 909 | { 910 | error_code = MP4E_STATUS_OK; 911 | } 912 | free(write_base); 913 | 914 | 915 | #if MP4E_CAN_USE_RANDOM_FILE_ACCESS 916 | if (!mux->enable_fragmentation) 917 | { 918 | // update size of mdat box. 919 | fseek(mux->mp4file, 0, SEEK_SET); 920 | mp4e_write_mdat_box(mux, mdat_end - mp4e_write_file_header(mux)); 921 | } 922 | #endif 923 | 924 | return error_code; 925 | } 926 | 927 | /************************************************************************/ 928 | /* Exported API functions */ 929 | /************************************************************************/ 930 | 931 | /** 932 | * Allocates and initialize mp4 multiplexer 933 | * return multiplexer handle on success; NULL on failure 934 | */ 935 | MP4E_mux_t * MP4E__open(FILE * mp4file, int enable_fragmentation) 936 | { 937 | MP4E_mux_t * mux; 938 | if (!mp4file) 939 | { 940 | return NULL; 941 | } 942 | 943 | mux = (MP4E_mux_t *)calloc(sizeof(MP4E_mux_t), 1); 944 | if (mux) 945 | { 946 | int success; 947 | mux->mp4file = mp4file; 948 | mux->enable_fragmentation = enable_fragmentation; 949 | asp_vector_init(&mux->tracks, 2*sizeof(track_t)); 950 | 951 | success = !!mp4e_write_file_header(mux); 952 | #if MP4E_CAN_USE_RANDOM_FILE_ACCESS 953 | if (!mux->enable_fragmentation) 954 | { 955 | success &= mp4e_write_mdat_box(mux, 0); // Write stub, which would be updated later 956 | } 957 | #endif 958 | if (!success) 959 | { 960 | mp4e_free(mux); 961 | mux = NULL; 962 | } 963 | } 964 | 965 | return mux; 966 | } 967 | 968 | /** 969 | * Closes MP4 multiplexer 970 | */ 971 | int MP4E__close(MP4E_mux_t * mux) 972 | { 973 | int error_code = MP4E_STATUS_OK; 974 | if (!mux) 975 | { 976 | return MP4E_STATUS_BAD_ARGUMENTS; 977 | } 978 | 979 | if (mux->enable_fragmentation) 980 | { 981 | #if MP4E_CAN_USE_RANDOM_FILE_ACCESS 982 | rewind(mux->mp4file); 983 | mp4e_write_file_header(mux); 984 | error_code = mp4e_write_index(mux); 985 | #endif 986 | } 987 | else 988 | { 989 | error_code = mp4e_write_index(mux); 990 | } 991 | mp4e_free(mux); 992 | 993 | return error_code; 994 | } 995 | 996 | /** 997 | * Add new track, return track ID 998 | */ 999 | int MP4E__add_track(MP4E_mux_t * mux, const MP4E_track_t * track_data) 1000 | { 1001 | track_t *tr; 1002 | 1003 | if (!mux || !track_data) 1004 | { 1005 | return MP4E_STATUS_BAD_ARGUMENTS; 1006 | } 1007 | if (mux->fragments_count) 1008 | { 1009 | return MP4E_STATUS_ENCODE_IN_PROGRESS; 1010 | } 1011 | 1012 | tr = (track_t*)asp_vector_alloc_tail(&mux->tracks, sizeof(track_t)); 1013 | if (!tr) 1014 | { 1015 | return MP4E_STATUS_NO_MEMORY; 1016 | } 1017 | memset(tr, 0, sizeof(track_t)); 1018 | memcpy(&tr->info, track_data, sizeof(*track_data)); 1019 | if (!asp_vector_init(&tr->smpl, 256)) 1020 | { 1021 | return MP4E_STATUS_NO_MEMORY; 1022 | } 1023 | asp_vector_init(&tr->vsps, 0); 1024 | asp_vector_init(&tr->vpps, 0); 1025 | return (int)(mux->tracks.bytes / sizeof(track_t)) - 1; 1026 | } 1027 | 1028 | /** 1029 | * Set track DSI. Used for audio tracks. 1030 | */ 1031 | int MP4E__set_dsi(MP4E_mux_t * mux, int track_id, const void * dsi, int bytes) 1032 | { 1033 | track_t* tr = ((track_t*)mux->tracks.data) + track_id; 1034 | assert(tr->info.track_media_kind == e_audio || 1035 | tr->info.track_media_kind == e_private); 1036 | if (tr->vsps.bytes) 1037 | { 1038 | return MP4E_STATUS_ONLY_ONE_DSI_ALLOWED; // only one DSI allowed 1039 | } 1040 | if (mux->fragments_count) 1041 | { 1042 | return MP4E_STATUS_ENCODE_IN_PROGRESS; 1043 | } 1044 | return mp4e_sps_pps_append_mem(&tr->vsps, dsi, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; 1045 | } 1046 | 1047 | /** 1048 | * Set track SPS. Used for AVC video tracks. 1049 | */ 1050 | int MP4E__set_sps(MP4E_mux_t * mux, int track_id, const void * sps, int bytes) 1051 | { 1052 | track_t* tr = ((track_t*)mux->tracks.data) + track_id; 1053 | assert(tr->info.track_media_kind == e_video); 1054 | if (mux->fragments_count) 1055 | { 1056 | return MP4E_STATUS_ENCODE_IN_PROGRESS; 1057 | } 1058 | return mp4e_sps_pps_append_mem(&tr->vsps, sps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; 1059 | } 1060 | 1061 | /** 1062 | * Set track PPS. Used for AVC video tracks. 1063 | */ 1064 | int MP4E__set_pps(MP4E_mux_t * mux, int track_id, const void * pps, int bytes) 1065 | { 1066 | track_t* tr = ((track_t*)mux->tracks.data) + track_id; 1067 | assert(tr->info.track_media_kind == e_video); 1068 | if (mux->fragments_count) 1069 | { 1070 | return MP4E_STATUS_ENCODE_IN_PROGRESS; 1071 | } 1072 | return mp4e_sps_pps_append_mem(&tr->vpps, pps, bytes) ? MP4E_STATUS_OK : MP4E_STATUS_NO_MEMORY; 1073 | } 1074 | 1075 | /** 1076 | * Add or remove MP4 file text comment according to Apple specs: 1077 | * https://developer.apple.com/library/mac/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW1 1078 | * http://atomicparsley.sourceforge.net/mpeg-4files.html 1079 | * note that ISO did not specify comment format. 1080 | */ 1081 | int MP4E__set_text_comment(MP4E_mux_t * mux, const char * comment) 1082 | { 1083 | if (!mux) 1084 | { 1085 | return MP4E_STATUS_BAD_ARGUMENTS; 1086 | } 1087 | if (mux->fragments_count) 1088 | { 1089 | return MP4E_STATUS_ENCODE_IN_PROGRESS; 1090 | } 1091 | 1092 | // replace comment 1093 | free(mux->text_comment); 1094 | mux->text_comment = NULL; 1095 | if (comment) 1096 | { 1097 | mux->text_comment = (char*)malloc(strlen(comment) + 1); 1098 | if (mux->text_comment) 1099 | { 1100 | strcpy(mux->text_comment, comment); 1101 | } 1102 | } 1103 | return MP4E_STATUS_OK; 1104 | } 1105 | 1106 | /** 1107 | * Add new sample to specified track 1108 | */ 1109 | int MP4E__put_sample(MP4E_mux_t * mux, int track_num, const void * data, int data_bytes, int duration, int kind) 1110 | { 1111 | if (!mux || !data || track_num*sizeof(track_t) >= mux->tracks.bytes) 1112 | { 1113 | return MP4E_STATUS_BAD_ARGUMENTS; 1114 | } 1115 | 1116 | if (mux->enable_fragmentation) 1117 | { 1118 | if (!mux->fragments_count++) 1119 | { 1120 | // write file headers before 1st sample 1121 | int error_code = mp4e_write_index(mux); 1122 | if (error_code) 1123 | { 1124 | return error_code; 1125 | } 1126 | } 1127 | 1128 | // write MOOF + MDAT + sample data 1129 | if (!mp4e_write_fragment_header(mux, track_num, data_bytes, duration, kind)) 1130 | { 1131 | return MP4E_STATUS_FILE_WRITE_ERROR; 1132 | } 1133 | 1134 | // write MDAT box for each sample 1135 | if (!mp4e_write_mdat_box(mux, data_bytes + 8)) 1136 | { 1137 | return MP4E_STATUS_FILE_WRITE_ERROR; 1138 | } 1139 | } 1140 | else 1141 | { 1142 | #if !MP4E_CAN_USE_RANDOM_FILE_ACCESS 1143 | // write MDAT box for each sample 1144 | if (!mp4e_write_mdat_box(mux, data_bytes + 8)) 1145 | { 1146 | return MP4E_STATUS_FILE_WRITE_ERROR; 1147 | } 1148 | #endif 1149 | } 1150 | 1151 | // update file index (after optional MDAT) 1152 | // fragmented mode also may use optional index at the end of file (not yet implemented) 1153 | if (!mp4e_add_sample_descriptor(mux, ((track_t*)mux->tracks.data) + track_num, data_bytes, duration, kind)) 1154 | { 1155 | return MP4E_STATUS_NO_MEMORY; 1156 | } 1157 | 1158 | // write sample data 1159 | if (!mp4e_fwrite(mux, data, data_bytes)) 1160 | { 1161 | return MP4E_STATUS_FILE_WRITE_ERROR; 1162 | } 1163 | 1164 | return MP4E_STATUS_OK; 1165 | } 1166 | 1167 | 1168 | 1169 | #ifdef mp4mux_test 1170 | /****************************************************************************** 1171 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1172 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1173 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1174 | !!!! !!!! 1175 | !!!! !!!!!!!! !!!!!!!! !!!!!!! !!!!!!!! !!!! 1176 | !!!! !! !! !! !! !!!! 1177 | !!!! !! !! !! !! !!!! 1178 | !!!! !! !!!!!! !!!!!!! !! !!!! 1179 | !!!! !! !! !! !! !!!! 1180 | !!!! !! !! !! !! !!!! 1181 | !!!! !! !!!!!!!! !!!!!!! !! !!!! 1182 | !!!! !!!! 1183 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1184 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1185 | !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 1186 | ******************************************************************************/ 1187 | 1188 | 1189 | #include 1190 | #include 1191 | #include 1192 | #include 1193 | #include 1194 | #include 1195 | #include "mp4mux.h" 1196 | 1197 | const unsigned char avc[] = {0, 0, 1, 0xB3, 0, 0x10, 7, 0, 0, 1, 0xB6, 16, 0x60, 0x51, 0x82, 0x3D, 0xB7, 0xEF}; 1198 | const unsigned char sps[] = {0x67, 0x42, 0xE0, 0x0A, 0xDA, 0x79}; 1199 | const unsigned char pps[] = {0x68, 0xCE, 0x04, 0x72}; 1200 | const unsigned char idr[] = {0,0,0,12,0x65, 0xB8, 0x23, 0xFF, 0xFF, 0xF0, 0xF4, 0x50, 0x00, 0x10, 0x11, 0xF8}; 1201 | const unsigned char frm[] = {0,0,0,4,0x61, 0xE2, 0x3D, 0x40 }; 1202 | 1203 | const unsigned char aac_dsi[] = {0x12, 0x10}; 1204 | const unsigned char aac[] = { 1205 | 0x21, 0x10, 0x05, 0x20, 0xA4, 0x1B, 0xFF, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1206 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1207 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1208 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1209 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1210 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1211 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1212 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1213 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1214 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1215 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1216 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1217 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1218 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1219 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1220 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1221 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1222 | 0x00, 0x00, 0x00, 0x00, 0x37, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1223 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1224 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1225 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1226 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1227 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 1228 | 0x00, 0x00, 0x70 1229 | }; 1230 | 1231 | void test(FILE * output, int fragmentation_mode) 1232 | { 1233 | int i, id_video, id_audio, id_private; 1234 | static unsigned char dummy[100]; 1235 | // == Open file 1236 | MP4E_mux_t * mp4 = MP4E__open(output, fragmentation_mode); 1237 | 1238 | // == Add audio track 1239 | MP4E_track_t track; 1240 | track.object_type_indication = MP4_OBJECT_TYPE_AUDIO_ISO_IEC_14496_3; 1241 | strcpy((char*)track.language, "und"); 1242 | track.u.a.channelcount = 2; 1243 | track.time_scale = 44100; 1244 | track.default_duration = 1024; 1245 | track.track_media_kind = e_audio; 1246 | id_audio = MP4E__add_track(mp4, &track); 1247 | 1248 | // == Add video track 1249 | track.track_media_kind = e_video; 1250 | track.object_type_indication = MP4_OBJECT_TYPE_AVC; 1251 | track.time_scale = 90000; 1252 | track.default_duration = 90000 / 30; 1253 | track.u.v.width = 16; 1254 | track.u.v.height = 16; 1255 | id_video = MP4E__add_track(mp4, &track); 1256 | 1257 | // == Add private data track 1258 | track.track_media_kind = e_private; 1259 | track.time_scale = 44100; 1260 | track.default_duration = 1024; 1261 | track.object_type_indication = MP4_OBJECT_TYPE_USER_PRIVATE; 1262 | id_private = MP4E__add_track(mp4, &track); 1263 | 1264 | 1265 | // == Supply SPS/PPS/DSI descriptors 1266 | MP4E__set_sps(mp4, id_video, sps, sizeof(sps)); 1267 | MP4E__set_pps(mp4, id_video, pps, sizeof(pps)); 1268 | MP4E__set_dsi(mp4, id_audio, aac_dsi, sizeof(aac_dsi)); 1269 | for (i = 0; i < 10; i++) 1270 | { 1271 | dummy[i] = 16+i; 1272 | } 1273 | MP4E__set_dsi(mp4, id_private, dummy, 10); 1274 | 1275 | 1276 | // == Append audio data 1277 | for (i = 0; i < 2 * 44100 / 1024; i++) 1278 | { 1279 | MP4E__put_sample(mp4, id_audio, aac, sizeof(aac), 0, MP4E_SAMPLE_RANDOM_ACCESS); 1280 | } 1281 | // == Append video data 1282 | for (i = 0; i < 30; i++) 1283 | { 1284 | MP4E__put_sample(mp4, id_video, idr, sizeof(idr), 0, MP4E_SAMPLE_RANDOM_ACCESS); 1285 | MP4E__put_sample(mp4, id_video, frm, sizeof(frm), 0, MP4E_SAMPLE_DEFAULT); 1286 | } 1287 | // == Append private data 1288 | for (i = 0; i < 2 * 44100 / 1024; i++) 1289 | { 1290 | int bytes = 100; 1291 | int duration = 1024; 1292 | dummy[0] = i; 1293 | MP4E__put_sample(mp4, id_private, dummy, bytes, duration, MP4E_SAMPLE_DEFAULT); 1294 | } 1295 | 1296 | // == Set file comment 1297 | MP4E__set_text_comment(mp4, "test comment"); 1298 | 1299 | // == Close session 1300 | MP4E__close(mp4); 1301 | } 1302 | 1303 | int main(int argc, char* argv[]) 1304 | { 1305 | FILE * file; 1306 | char * output_file_name = (argc > 1)?argv[1]:"mp4mux_test.mp4"; 1307 | int fragmentation_mode = (argc > 2)?argv[2][0] == 'f':0; 1308 | 1309 | if (!output_file_name) 1310 | { 1311 | printf("ERROR: no file name given!\n"); 1312 | return 1; 1313 | } 1314 | file = fopen(output_file_name, "wb"); 1315 | if (!file) 1316 | { 1317 | printf("ERROR: can't open file %s!\n", output_file_name); 1318 | return 1; 1319 | } 1320 | test(file, fragmentation_mode); 1321 | 1322 | return 0; 1323 | } 1324 | 1325 | // dmc mp4mux.c -Dmp4mux_test -DMP4E_CAN_USE_RANDOM_FILE_ACCESS=1 && mp4mux.exe && del *.obj *.map mp4mux.exe 1326 | #endif // mp4mux_test 1327 | -------------------------------------------------------------------------------- /src/mp4mux.h: -------------------------------------------------------------------------------- 1 | /** 24.04.2010 ASP @file 2 | * 3 | * ISO/IEC 14496-12:2005 4 | * 5 | **/ 6 | 7 | #ifndef MP4MUX_H_INCLUDED 8 | #define MP4MUX_H_INCLUDED 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif //__cplusplus 13 | 14 | #include 15 | #include "mp4defs.h" 16 | 17 | 18 | /************************************************************************/ 19 | /* API error codes */ 20 | /************************************************************************/ 21 | #define MP4E_STATUS_OK 0 22 | #define MP4E_STATUS_BAD_ARGUMENTS -1 23 | #define MP4E_STATUS_NO_MEMORY -2 24 | #define MP4E_STATUS_FILE_WRITE_ERROR -3 25 | #define MP4E_STATUS_ONLY_ONE_DSI_ALLOWED -4 26 | #define MP4E_STATUS_ENCODE_IN_PROGRESS -5 27 | 28 | 29 | /************************************************************************/ 30 | /* Sample kind for MP4E__put_sample() */ 31 | /************************************************************************/ 32 | #define MP4E_SAMPLE_DEFAULT 0 // (beginning of) audio or video frame 33 | #define MP4E_SAMPLE_RANDOM_ACCESS 1 // mark sample as random access point (key frame) 34 | 35 | /************************************************************************/ 36 | /* Data structures */ 37 | /************************************************************************/ 38 | 39 | typedef struct MP4E_mux_tag MP4E_mux_t; 40 | 41 | typedef struct 42 | { 43 | // MP4 object type code, which defined codec class for the track. 44 | // See MP4E_OBJECT_TYPE_* values for some codecs 45 | unsigned object_type_indication; 46 | 47 | // Track language: 3-char ISO 639-2T code: "und", "eng", "rus", "jpn" etc... 48 | unsigned char language[4]; 49 | 50 | enum 51 | { 52 | e_audio, 53 | e_video, 54 | e_private 55 | } track_media_kind; 56 | 57 | // 90000 for video, sample rate for audio 58 | unsigned time_scale; 59 | 60 | // default sample duration: 61 | // for ex: time_scale / FPS - for fixed FPS video, of frame size for audio 62 | // default duration can be overriden by MP4E__put_sample() parameter 63 | unsigned default_duration; 64 | 65 | union 66 | { 67 | struct 68 | { 69 | // number of channels in the audio track. 70 | unsigned channelcount; 71 | } a; 72 | 73 | struct 74 | { 75 | int width; 76 | int height; 77 | } v; 78 | } u; 79 | } MP4E_track_t; 80 | 81 | 82 | /************************************************************************/ 83 | /* API */ 84 | /************************************************************************/ 85 | 86 | /** 87 | * Allocates and initialize mp4 multiplexer. 88 | * Passed file handle owned by the multiplexor, and closed with MP4E__close() 89 | * 90 | * return multiplexor handle on success; NULL on failure 91 | * 92 | * Example: 93 | * 94 | * int enable_fragmentation = 0; 95 | * MP4E_mux_t * mux = MP4E__open(fopen(input_file_name, "wb"), enable_fragmentation); 96 | */ 97 | MP4E_mux_t * MP4E__open(FILE * mp4file, int enable_fragmentation); 98 | 99 | 100 | /** 101 | * Add new track 102 | * The track_data parameter does not referred by the multiplexer after function 103 | * return, and may be allocated in short-time memory. The dsi member of 104 | * track_data parameter is mandatory. 105 | * 106 | * return ID of added track, or error code MP4E_STATUS_* 107 | * 108 | * Example 1: add AVC video track: 109 | * 110 | * MP4E_track_t track = {0,}; 111 | * track.object_type_indication = MP4E_OBJECT_TYPE_AVC; 112 | * track.track_media_kind = e_video; 113 | * track.time_scale = 90000; 114 | * track.default_duration = track.time_scale / 30; // 30 FPS 115 | * track.u.v.width = 640; 116 | * track.u.v.height = 640; 117 | * video_track_id = MP4E__add_track(mux, &track); 118 | * 119 | * 120 | * Example 2: add AAC audio track: 121 | * 122 | * MP4E_track_t track = {0,}; 123 | * track.object_type_indication = MP4E_OBJECT_TYPE_AUDIO_ISO_IEC_13818_7_LC_PROFILE; 124 | * track.track_media_kind = e_audio; 125 | * track.time_scale = 48000; // sample rate, hz 126 | * track.default_duration = 1024; // AAC frame size 127 | * track.u.a.channelcount = 2; // stereo 128 | * audio_track_id = MP4E__add_track(mux, &track); 129 | */ 130 | int MP4E__add_track(MP4E_mux_t * mux, const MP4E_track_t * track_data); 131 | 132 | 133 | /** 134 | * Add new sample to specified track 135 | * The tracks numbered starting with 0, according to order of MP4E__add_track() calls 136 | * 'kind' is one of MP4E_SAMPLE_... defines 137 | * non-zero 'duration' overrides MP4E_track_t::default_duration settings 138 | * 139 | * return error code MP4E_STATUS_* 140 | * 141 | * Example 1: put 'P' video frame: 142 | * 143 | * MP4E__put_sample(mux, video_track_id, data, data_bytes, frame_duration, MP4E_SAMPLE_DEFAULT); 144 | * 145 | * Example 2: put AAC frame: 146 | * 147 | * MP4E__put_sample(mux, audio_track_id, data, data_bytes, 0, MP4E_SAMPLE_RANDOM_ACCESS); 148 | */ 149 | int MP4E__put_sample(MP4E_mux_t * mux, int track_id, const void * data, int data_bytes, int duration, int kind); 150 | 151 | 152 | /** 153 | * Finalize MP4 file, de-allocated memory, and closes MP4 multiplexer. 154 | * The close operation takes a time and disk space, since it writes MP4 file 155 | * indexes. Please note that this function does not closes file handle, 156 | * which was passed to open function. 157 | * 158 | * return error code MP4E_STATUS_* 159 | */ 160 | int MP4E__close(MP4E_mux_t * mux); 161 | 162 | 163 | /** 164 | * Set Decoder Specific Info (DSI) 165 | * Can be used for audio and private tracks. 166 | * MUST be used for AAC track. 167 | * Only one DSI can be set. It is an error to set DSI again 168 | * 169 | * return error code MP4E_STATUS_* 170 | * 171 | * Example: 172 | * 173 | * MP4E__set_dsi(mux, aac_track_id, dsi, dsi_bytes); 174 | * 175 | */ 176 | int MP4E__set_dsi(MP4E_mux_t * mux, int track_id, const void * dsi, int bytes); 177 | 178 | 179 | /** 180 | * Set SPS/PPS data. MUST be used for AVC (H.264) track. 181 | * Up to 32 different SPS can be used in one track. 182 | * Up to 256 different PPS can be used in one track. 183 | * 184 | * return error code MP4E_STATUS_* 185 | * 186 | */ 187 | int MP4E__set_sps(MP4E_mux_t * mux, int track_id, const void * sps, int bytes); 188 | int MP4E__set_pps(MP4E_mux_t * mux, int track_id, const void * pps, int bytes); 189 | 190 | 191 | /** 192 | * Set or replace ASCII test comment for the file. Set comment to NULL to remove comment. 193 | * 194 | * return error code MP4E_STATUS_* 195 | * 196 | * Example: 197 | * 198 | * MP4E__set_text_comment(mux, "file comment"); 199 | * 200 | */ 201 | int MP4E__set_text_comment(MP4E_mux_t * mux, const char * comment); 202 | 203 | 204 | #ifdef __cplusplus 205 | } 206 | #endif //__cplusplus 207 | 208 | #endif //MP4MUX_H_INCLUDED 209 | -------------------------------------------------------------------------------- /test/mp4transcode_test.c: -------------------------------------------------------------------------------- 1 | /** ASP 21.12.2016 @file 2 | * 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "mp4demux.h" 12 | #include "mp4mux.h" 13 | // #include "mp4mux.c" 14 | // #include "mp4demux.c" 15 | 16 | 17 | int transcode(int argc, char* argv[]) 18 | { 19 | unsigned i, ntrack = 0; 20 | int ninput; 21 | int fragmentation_mode = 0; 22 | MP4E_mux_t * mux = MP4E__open(fopen("transcoded.mp4", "wb"), fragmentation_mode); 23 | 24 | for (ninput = 0; ninput < 2; ninput++) 25 | { 26 | MP4D_demux_t mp4 = {0,}; 27 | char * file_name = (argc>1+ninput)?argv[1+ninput]:"input.mp4"; 28 | FILE * input_file = fopen(file_name, "rb"); 29 | if (!input_file) 30 | { 31 | printf("\ncant open %s\n", file_name); 32 | break; 33 | } 34 | MP4D__open(&mp4, input_file); 35 | 36 | for (ntrack = 0; ntrack < mp4.track_count; ntrack++) 37 | // for (ntrack = mp4.track_count - 1; ntrack >= 0; ntrack--) 38 | { 39 | MP4D_track_t *tr = mp4.track + ntrack; 40 | unsigned sum_duration = 0; 41 | MP4E_track_t tre; 42 | int trid; 43 | 44 | tre.object_type_indication = tr->object_type_indication; 45 | 46 | // Take video track from 1st file and audio from 2nd 47 | if (!( 48 | tr->handler_type == MP4_HANDLER_TYPE_VIDE && ninput == 0 || 49 | tr->handler_type == MP4_HANDLER_TYPE_SOUN && ninput == 1 50 | ) 51 | ) 52 | { 53 | continue; 54 | } 55 | 56 | memcpy(tre.language, tr->language, 4); 57 | if (tr->handler_type == MP4_HANDLER_TYPE_VIDE) 58 | { 59 | tre.track_media_kind = e_video; 60 | tre.u.v.width = tr->SampleDescription.video.width; 61 | tre.u.v.height = tr->SampleDescription.video.height; 62 | } 63 | if (tr->handler_type == MP4_HANDLER_TYPE_SOUN) 64 | { 65 | tre.track_media_kind = e_audio; 66 | tre.u.a.channelcount = tr->SampleDescription.audio.channelcount; 67 | } 68 | if (tr->handler_type == MP4_HANDLER_TYPE_GESM) 69 | { 70 | tre.track_media_kind = e_private; 71 | } 72 | tre.time_scale = tr->timescale; 73 | tre.default_duration = tr->duration_lo; 74 | 75 | trid = MP4E__add_track(mux, &tre); 76 | 77 | if (mp4.track[ntrack].object_type_indication == MP4_OBJECT_TYPE_AVC) 78 | { 79 | int sps_bytes, nsps; 80 | const void * sps; 81 | for (nsps = 0; NULL != (sps = MP4D__read_sps(&mp4, ntrack, nsps, &sps_bytes)); nsps++) 82 | { 83 | MP4E__set_sps(mux, trid, sps, sps_bytes); 84 | } 85 | for (nsps = 0; NULL != (sps = MP4D__read_pps(&mp4, ntrack, nsps, &sps_bytes)); nsps++) 86 | { 87 | MP4E__set_pps(mux, trid, sps, sps_bytes); 88 | } 89 | } 90 | else 91 | { 92 | MP4E__set_dsi(mux, trid, tr->dsi, tr->dsi_bytes); 93 | } 94 | #define MAX_FRAMES ~0u 95 | for (i = 0; i < mp4.track[ntrack].sample_count && (tr->handler_type != MP4_HANDLER_TYPE_VIDE || i < MAX_FRAMES); i++) 96 | { 97 | int sample_kind = MP4E_SAMPLE_DEFAULT; 98 | unsigned frame_bytes, timestamp, duration; 99 | mp4d_size_t ofs = MP4D__frame_offset(&mp4, ntrack, i, &frame_bytes, ×tamp, &duration); 100 | unsigned char *mem = malloc(frame_bytes); 101 | sum_duration += duration; 102 | fseek(input_file, (long)ofs, SEEK_SET); 103 | fread(mem, 1, frame_bytes, input_file); 104 | 105 | if (!i || (tr->handler_type == MP4_HANDLER_TYPE_SOUN)) 106 | { 107 | sample_kind = MP4E_SAMPLE_RANDOM_ACCESS; 108 | } 109 | 110 | // Ensure video duration is > 1 sec, extending last video frame duration 111 | if ((i == mp4.track[ntrack].sample_count-1 || 112 | i == (MAX_FRAMES-1) 113 | ) 114 | && (tr->handler_type == MP4_HANDLER_TYPE_VIDE)) 115 | { 116 | if (sum_duration < tr->timescale) 117 | { 118 | duration += 100 + tr->timescale - sum_duration; 119 | } 120 | } 121 | 122 | MP4E__put_sample(mux, trid, mem, frame_bytes, duration, sample_kind); 123 | free(mem); 124 | } 125 | } 126 | fclose(input_file); 127 | MP4D__close(&mp4); 128 | } 129 | 130 | MP4E__set_text_comment(mux, "transcoded"); 131 | MP4E__close(mux); 132 | return 0; 133 | } 134 | 135 | 136 | int main(int argc, char* argv[]) 137 | { 138 | return transcode(argc, argv); 139 | } 140 | -------------------------------------------------------------------------------- /vectors/ref/mp4mux_file.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/mp4mux_file.mp4 -------------------------------------------------------------------------------- /vectors/ref/mp4mux_stream.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/mp4mux_stream.mp4 -------------------------------------------------------------------------------- /vectors/ref/track0.audio: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/track0.audio -------------------------------------------------------------------------------- /vectors/ref/track1.264: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/track1.264 -------------------------------------------------------------------------------- /vectors/ref/track2.data: -------------------------------------------------------------------------------- 1 |   2 |     !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTU -------------------------------------------------------------------------------- /vectors/ref/transcoded.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/transcoded.mp4 --------------------------------------------------------------------------------