├── .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 | [](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 |
! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U
--------------------------------------------------------------------------------
/vectors/ref/transcoded.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aspt/mp4/1daf871cbbf641b04ea9ed5209c6fab6a20cdf52/vectors/ref/transcoded.mp4
--------------------------------------------------------------------------------