├── .MODULE_LICENSE_BSD ├── .MODULE_NAME_libmp4 ├── .clang-format ├── AUTHORS ├── COPYING ├── README.md ├── atom.mk ├── include └── libmp4.h ├── src ├── mp4.c ├── mp4_box_reader.c ├── mp4_box_to_json.c ├── mp4_box_writer.c ├── mp4_demux.c ├── mp4_mux.c ├── mp4_priv.h ├── mp4_recovery.c ├── mp4_recovery_reader.c ├── mp4_recovery_writer.c └── mp4_track.c ├── tests ├── mp4_test.c ├── mp4_test.h ├── mp4_test_demux.c └── mp4_test_utilities.c └── tools ├── larry_covery.c ├── mp4_demux.c └── mp4_mux.c /.MODULE_LICENSE_BSD: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Parrot Drones SAS 2 | Copyright (c) 2016 Aurelien Barre -------------------------------------------------------------------------------- /.MODULE_NAME_libmp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Parrot-Developers/libmp4/ada1714e433e953d2aab00f93e4f1540e1ef40b6/.MODULE_NAME_libmp4 -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | AccessModifierOffset: -8 4 | AlignAfterOpenBracket: true 5 | AlignEscapedNewlinesLeft: false 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortFunctionsOnASingleLine: Empty 10 | AllowShortIfStatementsOnASingleLine: false 11 | AllowShortLoopsOnASingleLine: false 12 | AlwaysBreakBeforeMultilineStrings: true 13 | BinPackArguments: false 14 | BinPackParameters: false 15 | BreakBeforeBinaryOperators: None 16 | BreakBeforeBraces: WebKit 17 | BreakConstructorInitializers: AfterColon 18 | BreakStringLiterals: false 19 | ContinuationIndentWidth: 8 20 | ConstructorInitializerIndentWidth: 16 21 | IndentCaseLabels: false 22 | IndentPPDirectives: AfterHash 23 | IndentWidth: 8 24 | Language: Cpp 25 | MaxEmptyLinesToKeep: 2 26 | SortIncludes: true 27 | SpaceAfterCStyleCast: false 28 | UseTab: Always 29 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Aurelien Barre 2 | Original author 3 | Parrot Drones SAS 4 | Maintainer 5 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Parrot Drones SAS 2 | Copyright (c) 2016 Aurelien Barre 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the copyright holders nor the names of its 12 | contributors may be used to endorse or promote products derived from 13 | this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libmp4 - MP4 file library 2 | 3 | libmp4 is a C library to handle MP4 files (ISO base media file format, 4 | see ISO/IEC 14496-12). 5 | It is mainly targeted to be used with videos produced by Parrot Drones 6 | (Bebop, Bebop2, Disco, ANAFI, ANAFI Ai, ANAFI UKR, etc.). 7 | -------------------------------------------------------------------------------- /atom.mk: -------------------------------------------------------------------------------- 1 | 2 | LOCAL_PATH := $(call my-dir) 3 | 4 | include $(CLEAR_VARS) 5 | LOCAL_MODULE := libmp4 6 | LOCAL_CATEGORY_PATH := libs 7 | LOCAL_DESCRIPTION := MP4 file library 8 | LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include 9 | # Public API headers - top level headers first 10 | # This header list is currently used to generate a python binding 11 | LOCAL_EXPORT_CUSTOM_VARIABLES := LIBMP4_HEADERS=$\ 12 | $(LOCAL_PATH)/include/libmp4.h; 13 | LOCAL_CFLAGS := -DMP4_API_EXPORTS -fvisibility=hidden -std=gnu99 -D_GNU_SOURCE 14 | 15 | LOCAL_SRC_FILES := \ 16 | src/mp4.c \ 17 | src/mp4_box_reader.c \ 18 | src/mp4_box_to_json.c \ 19 | src/mp4_box_writer.c \ 20 | src/mp4_demux.c \ 21 | src/mp4_mux.c \ 22 | src/mp4_recovery.c \ 23 | src/mp4_recovery_reader.c \ 24 | src/mp4_recovery_writer.c \ 25 | src/mp4_track.c 26 | LOCAL_LIBRARIES := \ 27 | json \ 28 | libfutils \ 29 | libulog 30 | 31 | LOCAL_CONDITIONAL_LIBRARIES := \ 32 | OPTIONAL:util-linux-ng 33 | 34 | ifeq ("$(TARGET_OS)","windows") 35 | LOCAL_LDLIBS += -lws2_32 36 | endif 37 | 38 | include $(BUILD_LIBRARY) 39 | 40 | 41 | include $(CLEAR_VARS) 42 | 43 | LOCAL_MODULE := mp4-demux 44 | LOCAL_DESCRIPTION := MP4 file library demuxer program 45 | LOCAL_CATEGORY_PATH := multimedia 46 | LOCAL_SRC_FILES := tools/mp4_demux.c 47 | LOCAL_LIBRARIES := \ 48 | json \ 49 | libfutils \ 50 | libmp4 \ 51 | libulog 52 | 53 | include $(BUILD_EXECUTABLE) 54 | 55 | 56 | include $(CLEAR_VARS) 57 | 58 | LOCAL_MODULE := mp4-mux 59 | LOCAL_DESCRIPTION := MP4 file library muxer program 60 | LOCAL_CATEGORY_PATH := multimedia 61 | LOCAL_SRC_FILES := tools/mp4_mux.c 62 | LOCAL_LIBRARIES := \ 63 | libmp4 \ 64 | libulog 65 | 66 | include $(BUILD_EXECUTABLE) 67 | 68 | 69 | include $(CLEAR_VARS) 70 | 71 | LOCAL_MODULE := larry-covery 72 | LOCAL_DESCRIPTION := Larry Covery: MP4 file recovery CLI 73 | LOCAL_CATEGORY_PATH := multimedia 74 | LOCAL_SRC_FILES := tools/larry_covery.c 75 | LOCAL_LIBRARIES := \ 76 | libmp4 \ 77 | libulog \ 78 | libfutils 79 | 80 | include $(BUILD_EXECUTABLE) 81 | 82 | 83 | ifdef TARGET_TEST 84 | 85 | include $(CLEAR_VARS) 86 | 87 | LOCAL_MODULE := tst-libmp4 88 | LOCAL_SRC_FILES := \ 89 | tests/mp4_test.c \ 90 | tests/mp4_test_demux.c \ 91 | tests/mp4_test_utilities.c 92 | LOCAL_LIBRARIES := \ 93 | json \ 94 | libcunit \ 95 | libfutils \ 96 | libmp4 97 | 98 | include $(BUILD_EXECUTABLE) 99 | 100 | endif 101 | -------------------------------------------------------------------------------- /include/libmp4.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _LIBMP4_H_ 29 | #define _LIBMP4_H_ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #ifdef __cplusplus 38 | extern "C" { 39 | #endif /* __cplusplus */ 40 | 41 | /* To be used for all public API */ 42 | #ifdef MP4_API_EXPORTS 43 | # ifdef _WIN32 44 | # define MP4_API __declspec(dllexport) 45 | # else /* !_WIN32 */ 46 | # define MP4_API __attribute__((visibility("default"))) 47 | # endif /* !_WIN32 */ 48 | #else /* !MP4_API_EXPORTS */ 49 | # define MP4_API 50 | #endif /* !MP4_API_EXPORTS */ 51 | 52 | 53 | /** 54 | * MP4 Metadata keys for muxer. 55 | * Setting the META key also sets the associated UDTA key to the same value, 56 | * unless previously set. 57 | * Setting the UDTA key also sets the associated META key to the same value, 58 | * unless previously set. 59 | */ 60 | #define MP4_META_KEY_FRIENDLY_NAME "com.apple.quicktime.artist" 61 | #define MP4_UDTA_KEY_FRIENDLY_NAME "\251ART" 62 | #define MP4_META_KEY_TITLE "com.apple.quicktime.title" 63 | #define MP4_UDTA_KEY_TITLE "\251nam" 64 | #define MP4_META_KEY_COMMENT "com.apple.quicktime.comment" 65 | #define MP4_UDTA_KEY_COMMENT "\251cmt" 66 | #define MP4_META_KEY_COPYRIGHT "com.apple.quicktime.copyright" 67 | #define MP4_UDTA_KEY_COPYRIGHT "\251cpy" 68 | #define MP4_META_KEY_MEDIA_DATE "com.apple.quicktime.creationdate" 69 | #define MP4_UDTA_KEY_MEDIA_DATE "\251day" 70 | #define MP4_META_KEY_LOCATION "com.apple.quicktime.location.ISO6709" 71 | #define MP4_UDTA_KEY_LOCATION "\251xyz" 72 | #define MP4_META_KEY_MAKER "com.apple.quicktime.make" 73 | #define MP4_UDTA_KEY_MAKER "\251mak" 74 | #define MP4_META_KEY_MODEL "com.apple.quicktime.model" 75 | #define MP4_UDTA_KEY_MODEL "\251mod" 76 | #define MP4_META_KEY_SOFTWARE_VERSION "com.apple.quicktime.software" 77 | #define MP4_UDTA_KEY_SOFTWARE_VERSION "\251swr" 78 | 79 | #define MP4_MUX_DEFAULT_TABLE_SIZE_MB 2 80 | 81 | enum mp4_track_type { 82 | MP4_TRACK_TYPE_UNKNOWN = 0, 83 | MP4_TRACK_TYPE_VIDEO, 84 | MP4_TRACK_TYPE_AUDIO, 85 | MP4_TRACK_TYPE_HINT, 86 | MP4_TRACK_TYPE_METADATA, 87 | MP4_TRACK_TYPE_TEXT, 88 | MP4_TRACK_TYPE_CHAPTERS, 89 | }; 90 | 91 | 92 | enum mp4_video_codec { 93 | MP4_VIDEO_CODEC_UNKNOWN = 0, 94 | MP4_VIDEO_CODEC_AVC, 95 | MP4_VIDEO_CODEC_HEVC, 96 | }; 97 | 98 | 99 | enum mp4_audio_codec { 100 | MP4_AUDIO_CODEC_UNKNOWN = 0, 101 | MP4_AUDIO_CODEC_AAC_LC, 102 | }; 103 | 104 | 105 | enum mp4_metadata_cover_type { 106 | MP4_METADATA_COVER_TYPE_UNKNOWN = 0, 107 | MP4_METADATA_COVER_TYPE_JPEG, 108 | MP4_METADATA_COVER_TYPE_PNG, 109 | MP4_METADATA_COVER_TYPE_BMP, 110 | }; 111 | 112 | 113 | enum mp4_seek_method { 114 | MP4_SEEK_METHOD_PREVIOUS = 0, 115 | MP4_SEEK_METHOD_PREVIOUS_SYNC, 116 | MP4_SEEK_METHOD_NEXT_SYNC, 117 | MP4_SEEK_METHOD_NEAREST_SYNC, 118 | }; 119 | 120 | 121 | struct mp4_media_info { 122 | uint64_t duration; 123 | uint64_t creation_time; 124 | uint64_t modification_time; 125 | uint32_t track_count; 126 | }; 127 | 128 | 129 | struct mp4_track_info { 130 | uint32_t id; 131 | const char *name; 132 | int enabled; 133 | int in_movie; 134 | int in_preview; 135 | enum mp4_track_type type; 136 | uint32_t timescale; 137 | uint64_t duration; 138 | uint64_t creation_time; 139 | uint64_t modification_time; 140 | uint32_t sample_count; 141 | uint32_t sample_max_size; 142 | const uint64_t *sample_offsets; 143 | const uint32_t *sample_sizes; 144 | enum mp4_video_codec video_codec; 145 | uint32_t video_width; 146 | uint32_t video_height; 147 | enum mp4_audio_codec audio_codec; 148 | uint32_t audio_channel_count; 149 | uint32_t audio_sample_size; 150 | float audio_sample_rate; 151 | const char *content_encoding; 152 | const char *mime_format; 153 | int has_metadata; 154 | const char *metadata_content_encoding; 155 | const char *metadata_mime_format; 156 | }; 157 | 158 | 159 | /* hvcC box structure */ 160 | struct mp4_hvcc_info { 161 | uint8_t general_profile_space; 162 | uint8_t general_tier_flag; 163 | uint8_t general_profile_idc; 164 | uint32_t general_profile_compatibility_flags; 165 | uint64_t general_constraints_indicator_flags; 166 | uint8_t general_level_idc; 167 | uint16_t min_spatial_segmentation_idc; 168 | uint8_t parallelism_type; 169 | uint8_t chroma_format; 170 | uint8_t bit_depth_luma; 171 | uint8_t bit_depth_chroma; 172 | uint16_t avg_framerate; 173 | uint8_t constant_framerate; 174 | uint8_t num_temporal_layers; 175 | uint8_t temporal_id_nested; 176 | uint8_t length_size; 177 | }; 178 | 179 | 180 | struct mp4_video_decoder_config { 181 | enum mp4_video_codec codec; 182 | union { 183 | struct { 184 | union { 185 | uint8_t *sps; 186 | const uint8_t *c_sps; 187 | }; 188 | size_t sps_size; 189 | union { 190 | uint8_t *pps; 191 | const uint8_t *c_pps; 192 | }; 193 | size_t pps_size; 194 | } avc; 195 | struct { 196 | struct mp4_hvcc_info hvcc_info; 197 | union { 198 | uint8_t *vps; 199 | const uint8_t *c_vps; 200 | }; 201 | size_t vps_size; 202 | union { 203 | uint8_t *sps; 204 | const uint8_t *c_sps; 205 | }; 206 | size_t sps_size; 207 | union { 208 | uint8_t *pps; 209 | const uint8_t *c_pps; 210 | }; 211 | size_t pps_size; 212 | } hevc; 213 | }; 214 | uint32_t width; 215 | uint32_t height; 216 | }; 217 | 218 | 219 | struct mp4_track_sample { 220 | uint32_t size; 221 | uint64_t offset; 222 | uint32_t metadata_size; 223 | int silent; 224 | int sync; 225 | uint64_t dts; 226 | uint64_t next_dts; 227 | uint64_t prev_sync_dts; 228 | uint64_t next_sync_dts; 229 | }; 230 | 231 | 232 | struct mp4_mux_track_params { 233 | /* Track type */ 234 | enum mp4_track_type type; 235 | /* Track name, if NULL, an empty string will be used */ 236 | const char *name; 237 | /* Track flags (bool-like) */ 238 | int enabled; 239 | int in_movie; 240 | int in_preview; 241 | /* Track timescale, mandatory */ 242 | uint32_t timescale; 243 | /* Creation time */ 244 | uint64_t creation_time; 245 | /* Modification time. If zero, creation time will be used */ 246 | uint64_t modification_time; 247 | }; 248 | 249 | 250 | struct mp4_mux_sample { 251 | const uint8_t *buffer; 252 | size_t len; 253 | int sync; 254 | int64_t dts; 255 | }; 256 | 257 | 258 | struct mp4_mux_scattered_sample { 259 | const uint8_t *const *buffers; 260 | const size_t *len; 261 | int nbuffers; 262 | int sync; 263 | int64_t dts; 264 | }; 265 | 266 | 267 | /* Demuxer API */ 268 | 269 | struct mp4_demux; 270 | 271 | 272 | /** 273 | * Create an MP4 demuxer. 274 | * The instance handle is returned through the mp4_demux parameter. 275 | * When no longer needed, the instance must be freed using the 276 | * mp4_demux_close() function. 277 | * @param filename: file path to use 278 | * @param ret_obj: demuxer instance handle (output) 279 | * @return 0 on success, negative errno value in case of error 280 | */ 281 | MP4_API int mp4_demux_open(const char *filename, struct mp4_demux **ret_obj); 282 | 283 | 284 | /** 285 | * Free an MP4 demuxer. 286 | * This function frees all resources associated with a demuxer instance. 287 | * @param demux: demuxer instance handle 288 | * @return 0 on success, negative errno value in case of error 289 | */ 290 | MP4_API int mp4_demux_close(struct mp4_demux *demux); 291 | 292 | 293 | /** 294 | * Get the media level information. 295 | * @param demux: demuxer instance handle 296 | * @param media_info: pointer to the media_info structure (output) 297 | * @return 0 on success, negative errno value in case of error 298 | */ 299 | MP4_API int mp4_demux_get_media_info(struct mp4_demux *demux, 300 | struct mp4_media_info *media_info); 301 | 302 | 303 | /** 304 | * Get the number of tracks of an MP4 demuxer. 305 | * @param demux: demuxer instance handle 306 | * @return the track count on success, negative errno value in case of error 307 | */ 308 | MP4_API int mp4_demux_get_track_count(struct mp4_demux *demux); 309 | 310 | 311 | /** 312 | * Get the info of a specific track. 313 | * @param demux: demuxer instance handle 314 | * @param track_idx: track index 315 | * @param track_info: pointer to the track_info structure to fill (output) 316 | * @return 0 on success, negative errno value in case of error 317 | */ 318 | MP4_API int mp4_demux_get_track_info(struct mp4_demux *demux, 319 | unsigned int track_idx, 320 | struct mp4_track_info *track_info); 321 | 322 | 323 | /** 324 | * Get the video decoder config of a specific track. 325 | * @param demux: demuxer instance handle 326 | * @param track_id: track ID 327 | * @param vdc: pointer to the video_decoder_config structure to fill 328 | * (output) 329 | * @return 0 on success, negative errno value in case of error 330 | */ 331 | MP4_API int 332 | mp4_demux_get_track_video_decoder_config(struct mp4_demux *demux, 333 | unsigned int track_id, 334 | struct mp4_video_decoder_config *vdc); 335 | 336 | 337 | /** 338 | * Get the audio config of a specific track. 339 | * @param demux: demuxer instance handle 340 | * @param track_id: track ID 341 | * @param audio_specific_config: pointer to the audio specific config buffer 342 | * (output) 343 | * @param asc_size: pointer to the size of the audio specific config (output) 344 | * @return 0 on success, negative errno value in case of error 345 | */ 346 | MP4_API int 347 | mp4_demux_get_track_audio_specific_config(struct mp4_demux *demux, 348 | unsigned int track_id, 349 | uint8_t **audio_specific_config, 350 | unsigned int *asc_size); 351 | 352 | 353 | /** 354 | * Get a track sample. 355 | * @param demux: demuxer instance handle 356 | * @param track_id: track ID 357 | * @param advance: if true, advance to the next sample of the track 358 | * @param sample_buffer: sample buffer (optional, can be null) 359 | * @param sample_buffer_size: sample buffer size 360 | * @param metadata_buffer: metadata buffer (optional, can be null) 361 | * @param metadata_buffer_size: size of the metadata buffer 362 | * @param track_sample: pointer to the track_sample structure to fill (output) 363 | * @return 0 on success, negative errno value in case of error 364 | */ 365 | MP4_API int mp4_demux_get_track_sample(struct mp4_demux *demux, 366 | unsigned int track_id, 367 | int advance, 368 | uint8_t *sample_buffer, 369 | unsigned int sample_buffer_size, 370 | uint8_t *metadata_buffer, 371 | unsigned int metadata_buffer_size, 372 | struct mp4_track_sample *track_sample); 373 | 374 | 375 | /** 376 | * Get the previous sample time of a track. 377 | * @param demux: demuxer instance handle 378 | * @param track_id: track ID 379 | * @param sample_time: pointer to the sample time to fill (output) 380 | * @return 0 on success, negative errno value in case of error 381 | */ 382 | MP4_API int mp4_demux_get_track_prev_sample_time(struct mp4_demux *demux, 383 | unsigned int track_id, 384 | uint64_t *sample_time); 385 | 386 | 387 | /** 388 | * Get the next sample time of a track. 389 | * @param demux: demuxer instance handle 390 | * @param track_id: track ID 391 | * @param sample_time: pointer to the sample time to fill (output) 392 | * @return 0 on success, negative errno value in case of error 393 | */ 394 | MP4_API int mp4_demux_get_track_next_sample_time(struct mp4_demux *demux, 395 | unsigned int track_id, 396 | uint64_t *sample_time); 397 | 398 | 399 | /** 400 | * Get the previous sample time of a track before a timestamp. 401 | * @param demux: demuxer instance handle 402 | * @param track_id: track ID 403 | * @param time: timestamp 404 | * @param sync: if true, search for a sync sample 405 | * @param sample_time: pointer to the sample time to fill (output) 406 | * @return 0 on success, negative errno value in case of error 407 | */ 408 | MP4_API int mp4_demux_get_track_prev_sample_time_before(struct mp4_demux *demux, 409 | unsigned int track_id, 410 | uint64_t time, 411 | int sync, 412 | uint64_t *sample_time); 413 | 414 | 415 | /** 416 | * Get the next sample time of a track before a timestamp. 417 | * @param demux: demuxer instance handle 418 | * @param track_id: track ID 419 | * @param time: timestamp 420 | * @param sync: if true, search for a sync sample 421 | * @param sample_time (output): sample time 422 | * @return 0 on success, negative errno value in case of error 423 | */ 424 | MP4_API int mp4_demux_get_track_next_sample_time_after(struct mp4_demux *demux, 425 | unsigned int track_id, 426 | uint64_t time, 427 | int sync, 428 | uint64_t *sample_time); 429 | 430 | 431 | /** 432 | * Seek to a time offset. 433 | * @param demux: demuxer instance handle 434 | * @param time_offset: timestamp 435 | * @param method: seek method 436 | * @return 0 on success, negative errno value in case of error 437 | */ 438 | MP4_API int mp4_demux_seek(struct mp4_demux *demux, 439 | uint64_t time_offset, 440 | enum mp4_seek_method method); 441 | 442 | 443 | /** 444 | * Seek to the previous sample of a track. 445 | * @param demux: demuxer instance handle 446 | * @param track_id: track ID 447 | * @return 0 on success, negative errno value in case of error 448 | */ 449 | MP4_API int mp4_demux_seek_to_track_prev_sample(struct mp4_demux *demux, 450 | unsigned int track_id); 451 | 452 | 453 | /** 454 | * Seek to the next sample of a track. 455 | * @param demux: demuxer instance handle 456 | * @param track_id: track ID 457 | * @return 0 on success, negative errno value in case of error 458 | */ 459 | MP4_API int mp4_demux_seek_to_track_next_sample(struct mp4_demux *demux, 460 | unsigned int track_id); 461 | 462 | 463 | /** 464 | * Get the chapters of an MP4 file. 465 | * @param demux: demuxer instance handle 466 | * @param chapters_count: pointer to the chapters count to fill (output) 467 | * @param chapters_time: pointer to chapters times array to fill (output) 468 | * @param chapters_name: pointer to a chapters names array to fill (output) 469 | * @return 0 on success, negative errno value in case of error 470 | */ 471 | MP4_API int mp4_demux_get_chapters(struct mp4_demux *demux, 472 | unsigned int *chapters_count, 473 | uint64_t **chapters_time, 474 | char ***chapters_name); 475 | 476 | 477 | /** 478 | * Get the metadata strings of an MP4 file. 479 | * @param demux: demuxer instance handle 480 | * @param count: pointer to the metadata count to fill (output) 481 | * @param keys: pointer to the metadata keys array to fill (output) 482 | * @param values: pointer to the metadata values array to fill (output) 483 | * @return 0 on success, negative errno value in case of error 484 | */ 485 | MP4_API int mp4_demux_get_metadata_strings(struct mp4_demux *demux, 486 | unsigned int *count, 487 | char ***keys, 488 | char ***values); 489 | 490 | 491 | /** 492 | * Get the metadata strings of a track. 493 | * @param demux: demuxer instance handle 494 | * @param track_id: track ID 495 | * @param count: pointer to the metadata count to fill (output) 496 | * @param keys: pointer to the metadata keys array to fill (output) 497 | * @param values: pointer to the metadata values array to fill (output) 498 | * @return 0 on success, negative errno value in case of error 499 | */ 500 | MP4_API int mp4_demux_get_track_metadata_strings(struct mp4_demux *demux, 501 | unsigned int track_id, 502 | unsigned int *count, 503 | char ***keys, 504 | char ***values); 505 | 506 | 507 | /** 508 | * Get the metadata cover of an MP4 file. 509 | * @param demux: demuxer instance handle 510 | * @param cover_buffer: pointer to the cover data to fill (output) 511 | * @param cover_buffer_size: cover buffer size 512 | * @param cover_size: pointer to the cover data size to fill (output) 513 | * @param cover_type: pointer to the cover type to fill (output) 514 | * @return 0 on success, negative errno value in case of error 515 | */ 516 | MP4_API int 517 | mp4_demux_get_metadata_cover(struct mp4_demux *demux, 518 | uint8_t *cover_buffer, 519 | unsigned int cover_buffer_size, 520 | unsigned int *cover_size, 521 | enum mp4_metadata_cover_type *cover_type); 522 | 523 | 524 | /* Muxer API */ 525 | 526 | struct mp4_mux; 527 | 528 | struct mp4_mux_config { 529 | const char *filename; 530 | mode_t filemode; 531 | uint32_t timescale; 532 | uint64_t creation_time; 533 | uint64_t modification_time; 534 | size_t tables_size_mbytes; 535 | struct { 536 | /* will be created by mp4_mux_open, must be deleted by caller 537 | * after calling mp4_mux_close */ 538 | const char *link_file; 539 | /* will be created by mp4_mux_open, must be deleted by caller 540 | * after calling mp4_mux_close */ 541 | const char *tables_file; 542 | bool check_storage_uuid; 543 | } recovery; 544 | }; 545 | 546 | 547 | /** 548 | * Create an MP4 muxer. 549 | * The instance handle is returned through the mp4_mux parameter. 550 | * When no longer needed, the instance must be freed using the 551 | * mp4_mux_close() function. 552 | * @note mp4_recovery_finalize must be called after mp4_mux_close if recovery is 553 | * enabled 554 | * @param config: mp4_mux_config to use 555 | * @param ret_obj: muxer instance handle (output) 556 | * @return 0 on success, negative errno value in case of error 557 | */ 558 | MP4_API int mp4_mux_open(struct mp4_mux_config *config, 559 | struct mp4_mux **ret_obj); 560 | 561 | 562 | /** 563 | * Sync an MP4 muxer. 564 | * If recovery is enabled, this function must be called periodically to write 565 | * the tables in the recovery files. 566 | * If write_tables parameter is set to true, the tables are written in the final 567 | * file (if recovery is enabled, tables will also be written in the recovery 568 | * files). 569 | * @note sync with write_tables allows the MP4 file to be read at any time but 570 | * the operation requires to fully write the tables; sync without write_tables 571 | * only writes the tables after the last call to mp4_mux_sync but requires a 572 | * recovery with mp4_recovery_recover_file to read the MP4 file. 573 | * @param mux: muxer instance handle 574 | * @param write_tables: if true, tables are written in the final file. 575 | * @return 0 on success, negative errno value in case of error 576 | */ 577 | MP4_API int mp4_mux_sync(struct mp4_mux *mux, bool write_tables); 578 | 579 | 580 | /** 581 | * Free an MP4 muxer. 582 | * This function frees all resources associated with a muxer instance. 583 | * @param mux: muxer instance handle 584 | * @return 0 on success, negative errno value in case of error 585 | */ 586 | MP4_API int mp4_mux_close(struct mp4_mux *mux); 587 | 588 | 589 | /** 590 | * Add a track in an MP4 muxer. 591 | * @param mux: muxer instance handle 592 | * @param params: mp4_mux_track_params of the track to add 593 | * @return 0 on success, negative errno value in case of error 594 | */ 595 | MP4_API int mp4_mux_add_track(struct mp4_mux *mux, 596 | const struct mp4_mux_track_params *params); 597 | 598 | 599 | /** 600 | * Add a reference to a track. 601 | * @param mux: muxer instance handle 602 | * @param track_handle: track handle 603 | * @param ref_track_handle: reference track handle 604 | * @return 0 on success, negative errno value in case of error 605 | */ 606 | MP4_API int mp4_mux_add_ref_to_track(struct mp4_mux *mux, 607 | uint32_t track_handle, 608 | uint32_t ref_track_handle); 609 | 610 | 611 | /** 612 | * Set the video decoder config of a track. 613 | * @param mux: muxer instance handle 614 | * @param track_handle: track handle 615 | * @param vdc: mp4_video_decoder_config to set 616 | * @return 0 on success, negative errno value in case of error 617 | */ 618 | MP4_API int 619 | mp4_mux_track_set_video_decoder_config(struct mp4_mux *mux, 620 | int track_handle, 621 | struct mp4_video_decoder_config *vdc); 622 | 623 | 624 | /** 625 | * Set the audio specific config of a track. 626 | * @param mux: muxer instance handle 627 | * @param track_handle: track handle 628 | * @param asc: buffer containing the audio specific config to set 629 | * @param asc_size: size of the buffer containing the audio specific config 630 | * @param channel_count: channel count 631 | * @param sample_size: sample size 632 | * @param sample_rate: sample rate 633 | * @return 0 on success, negative errno value in case of error 634 | */ 635 | MP4_API int mp4_mux_track_set_audio_specific_config(struct mp4_mux *mux, 636 | int track_handle, 637 | const uint8_t *asc, 638 | size_t asc_size, 639 | uint32_t channel_count, 640 | uint32_t sample_size, 641 | float sample_rate); 642 | 643 | 644 | /** 645 | * Set the metadata mime type of a track. 646 | * @param mux: muxer instance handle 647 | * @param track_handle: track handle 648 | * @param content_encoding: content encoding 649 | * @param mime_type: mime type 650 | * @return 0 on success, negative errno value in case of error 651 | */ 652 | MP4_API int mp4_mux_track_set_metadata_mime_type(struct mp4_mux *mux, 653 | int track_handle, 654 | const char *content_encoding, 655 | const char *mime_type); 656 | 657 | 658 | /** 659 | * Add a file level metadata. 660 | * @param mux: muxer instance handle 661 | * @param key: metadata key 662 | * @param value: metadata value 663 | * @return 0 on success, negative errno value in case of error 664 | */ 665 | MP4_API int mp4_mux_add_file_metadata(struct mp4_mux *mux, 666 | const char *key, 667 | const char *value); 668 | 669 | 670 | /** 671 | * Add a track level metadata. 672 | * @param mux: muxer instance handle 673 | * @param track_handle: track handle 674 | * @param key: metadata key 675 | * @param value: metadata value 676 | * @return 0 on success, negative errno value in case of error 677 | */ 678 | MP4_API int mp4_mux_add_track_metadata(struct mp4_mux *mux, 679 | uint32_t track_handle, 680 | const char *key, 681 | const char *value); 682 | 683 | 684 | /** 685 | * Set the cover of an MP4 file. 686 | * @param mux: muxer instance handle 687 | * @param cover_type: type of the cover 688 | * @param cover: cover data 689 | * @param cover_size: size of cover 690 | * @return 0 on success, negative errno value in case of error 691 | */ 692 | MP4_API int mp4_mux_set_file_cover(struct mp4_mux *mux, 693 | enum mp4_metadata_cover_type cover_type, 694 | const uint8_t *cover, 695 | size_t cover_size); 696 | 697 | 698 | /** 699 | * Add a sample to a track. 700 | * @param mux: muxer instance handle 701 | * @param track_handle: track handle 702 | * @param sample: sample to add 703 | * @return 0 on success, negative errno value in case of error 704 | */ 705 | MP4_API int mp4_mux_track_add_sample(struct mp4_mux *mux, 706 | int track_handle, 707 | const struct mp4_mux_sample *sample); 708 | 709 | 710 | /** 711 | * Add a scattered sample to a track. 712 | * @param mux: muxer instance handle 713 | * @param track_handle: track handle 714 | * @param sample: sample to add 715 | * @return 0 on success, negative errno value in case of error 716 | */ 717 | MP4_API int mp4_mux_track_add_scattered_sample( 718 | struct mp4_mux *mux, 719 | int track_handle, 720 | const struct mp4_mux_scattered_sample *sample); 721 | 722 | 723 | /** 724 | * Print the muxer data. 725 | * @param mux: muxer instance handle 726 | * @return 0 on success, negative errno value in case of error 727 | */ 728 | MP4_API void mp4_mux_dump(struct mp4_mux *mux); 729 | 730 | 731 | /* Utilities */ 732 | 733 | /** 734 | * Create an avc decoder config. 735 | * @param sps: buffer containing the sps 736 | * @param sps_size: size of the buffer containing the sps 737 | * @param pps: buffer containing the pps 738 | * @param pps_size: size of the buffer containing the pps 739 | * @param avcc: pointer to the avc decoder config buffer (output) 740 | * @param avcc_size: pointer to the avc decoder config buffer size (output) 741 | * @return 0 on success, negative errno value in case of error 742 | */ 743 | MP4_API int mp4_generate_avc_decoder_config(const uint8_t *sps, 744 | unsigned int sps_size, 745 | const uint8_t *pps, 746 | unsigned int pps_size, 747 | uint8_t *avcc, 748 | unsigned int *avcc_size); 749 | 750 | 751 | /** 752 | * Create a chapter sample. 753 | * @param chapter_str: name of the chapter 754 | * @param buffer: pointer to the chapter sample buffer (output) 755 | * @param buffer_size: pointer to the chapter sample buffer size (output) 756 | * @return 0 on success, negative errno value in case of error 757 | */ 758 | MP4_API int mp4_generate_chapter_sample(const char *chapter_str, 759 | uint8_t **buffer, 760 | unsigned int *buffer_size); 761 | 762 | 763 | /** 764 | * Get a string from an enum mp4_track_type value. 765 | * @param type: track type value to convert 766 | * @return a string description of the track type 767 | */ 768 | MP4_API const char *mp4_track_type_str(enum mp4_track_type type); 769 | 770 | 771 | /** 772 | * Get a string from an enum mp4_video_codec value. 773 | * @param codec: video codec value to convert 774 | * @return a string description of the video codec 775 | */ 776 | MP4_API const char *mp4_video_codec_str(enum mp4_video_codec codec); 777 | 778 | 779 | /** 780 | * Get a string from an enum mp4_audio_codec value. 781 | * @param codec: audio codec value to convert 782 | * @return a string description of the audio codec 783 | */ 784 | MP4_API const char *mp4_audio_codec_str(enum mp4_audio_codec codec); 785 | 786 | 787 | /** 788 | * Get a string from an enum mp4_metadata_cover_type value. 789 | * @param type: metadata cover type value to convert 790 | * @return a string description of the cover type 791 | */ 792 | MP4_API const char * 793 | mp4_metadata_cover_type_str(enum mp4_metadata_cover_type type); 794 | 795 | 796 | static inline uint64_t mp4_usec_to_sample_time(uint64_t time, 797 | uint32_t timescale) 798 | { 799 | return (time * timescale + 500000) / 1000000; 800 | } 801 | 802 | 803 | static inline uint64_t mp4_sample_time_to_usec(uint64_t time, 804 | uint32_t timescale) 805 | { 806 | if (timescale == 0) 807 | return 0; 808 | return (time * 1000000 + timescale / 2) / timescale; 809 | } 810 | 811 | 812 | static inline uint64_t mp4_convert_timescale(uint64_t time, 813 | uint32_t src_timescale, 814 | uint32_t dest_timescale) 815 | { 816 | if (src_timescale == dest_timescale) 817 | return time; 818 | return (time * dest_timescale + src_timescale / 2) / src_timescale; 819 | } 820 | 821 | 822 | /** 823 | * Convert an MP4 file to a json object 824 | * @param filename: file path 825 | * @param verbose: if true, print all the MP4 boxes 826 | * @param json_obj: pointer to the json_object to fill (output) 827 | * @return 0 on success, negative errno value in case of error 828 | */ 829 | MP4_API int mp4_file_to_json(const char *filename, 830 | bool verbose, 831 | struct json_object **json_obj); 832 | 833 | 834 | /* Recovery API */ 835 | 836 | struct link_file_info { 837 | char *tables_file; 838 | char *data_file; 839 | char *uuid; 840 | size_t tables_size_b; 841 | uint32_t recovery_version; 842 | }; 843 | 844 | /** 845 | * Fills a link_file_info structure from a link_file. 846 | * @param link_file: link file path 847 | * @param info: link_file_info structure to fill 848 | * @return 0 on success, negative errno value in case of error 849 | */ 850 | MP4_API int mp4_recovery_parse_link_file(const char *link_file, 851 | struct link_file_info *info); 852 | 853 | /** 854 | * Recovery function. 855 | * Must be called after mp4_recovery_recover_file or after 856 | * a call to mp4_mux_close. 857 | * Remove the recovery files (link_file, tables_file) 858 | * @param link_file: link file path used for recovery. 859 | * @param truncate_file: if true, will truncate the media file to 0 byte. 860 | * @return 0 on success, negative errno value in case of error 861 | */ 862 | MP4_API int mp4_recovery_finalize(const char *link_file, bool truncate_file); 863 | 864 | 865 | /** 866 | * Clean up a link_file_info structure. 867 | * @param info: the link_file_info structure to clean up 868 | * @return 0 on success, negative errno value in case of error 869 | */ 870 | MP4_API int mp4_recovery_link_file_info_destroy(struct link_file_info *info); 871 | 872 | 873 | /** 874 | * Recovery function. 875 | * Use the link_file to find a data file (where mdat is written) and a 876 | * tables file (where moov is written) and merge the two into a valid 877 | * MP4 file. 878 | * @param link_file: link file path to use for recovery. 879 | * @param error_msg (output): required, unset on success, a string 880 | * description of the failure, the string should be freed after usage 881 | * @param recovered_file (output): path of the recovered file (optional), must 882 | * be released by caller after use. 883 | 884 | * @return 0 on success, negative errno value in case of error 885 | */ 886 | MP4_API 887 | int mp4_recovery_recover_file(const char *link_file, 888 | char **error_msg, 889 | char **recovered_file); 890 | 891 | 892 | /** 893 | * Recovery function. 894 | * Rewrite the link_file with the custom tables_file (where tables are written) 895 | * and data_file (where mdat is written) parameters, 896 | * and merge the two into a valid MP4 file. 897 | * @param link_file: link file path to use for recovery. 898 | * @param tables_file: custom table file path that will written in the link file 899 | * before starting the recovery. 900 | * @param data_file: custom table data path that will written in the link file 901 | * before starting the recovery. 902 | * @param error_msg (output): required, unset on success, a string 903 | * description of the failure, the string should be freed after usage 904 | * @param recovered_file (output): path of the recovered file (optional), must 905 | * be released by caller after use. 906 | 907 | * @return 0 on success, negative errno value in case of error 908 | */ 909 | MP4_API 910 | int mp4_recovery_recover_file_from_paths(const char *link_file, 911 | const char *tables_file, 912 | const char *data_file, 913 | char **error_msg, 914 | char **recovered_file); 915 | 916 | 917 | #ifdef __cplusplus 918 | } 919 | #endif /* __cplusplus */ 920 | 921 | #endif /* !_LIBMP4_H_ */ 922 | -------------------------------------------------------------------------------- /src/mp4.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_priv.h" 29 | 30 | ULOG_DECLARE_TAG(libmp4); 31 | 32 | 33 | const char *mp4_track_type_str(enum mp4_track_type type) 34 | { 35 | switch (type) { 36 | case MP4_TRACK_TYPE_VIDEO: 37 | return "VIDEO"; 38 | case MP4_TRACK_TYPE_AUDIO: 39 | return "AUDIO"; 40 | case MP4_TRACK_TYPE_HINT: 41 | return "HINT"; 42 | case MP4_TRACK_TYPE_METADATA: 43 | return "METADATA"; 44 | case MP4_TRACK_TYPE_TEXT: 45 | return "TEXT"; 46 | case MP4_TRACK_TYPE_CHAPTERS: 47 | return "CHAPTERS"; 48 | case MP4_TRACK_TYPE_UNKNOWN: 49 | default: 50 | return "UNKNOWN"; 51 | } 52 | } 53 | 54 | 55 | const char *mp4_video_codec_str(enum mp4_video_codec codec) 56 | { 57 | switch (codec) { 58 | case MP4_VIDEO_CODEC_AVC: 59 | return "AVC"; 60 | case MP4_VIDEO_CODEC_HEVC: 61 | return "HEVC"; 62 | case MP4_VIDEO_CODEC_UNKNOWN: 63 | default: 64 | return "UNKNOWN"; 65 | } 66 | } 67 | 68 | 69 | const char *mp4_audio_codec_str(enum mp4_audio_codec codec) 70 | { 71 | switch (codec) { 72 | case MP4_AUDIO_CODEC_AAC_LC: 73 | return "AAC_LC"; 74 | case MP4_AUDIO_CODEC_UNKNOWN: 75 | default: 76 | return "UNKNOWN"; 77 | } 78 | } 79 | 80 | 81 | const char *mp4_metadata_cover_type_str(enum mp4_metadata_cover_type type) 82 | { 83 | switch (type) { 84 | case MP4_METADATA_COVER_TYPE_JPEG: 85 | return "JPEG"; 86 | case MP4_METADATA_COVER_TYPE_PNG: 87 | return "PNG"; 88 | case MP4_METADATA_COVER_TYPE_BMP: 89 | return "BMP"; 90 | default: 91 | return "UNKNOWN"; 92 | } 93 | } 94 | 95 | 96 | void mp4_video_decoder_config_destroy(struct mp4_video_decoder_config *vdc) 97 | { 98 | if (vdc == NULL) 99 | return; 100 | 101 | switch (vdc->codec) { 102 | case MP4_VIDEO_CODEC_AVC: 103 | free(vdc->avc.sps); 104 | free(vdc->avc.pps); 105 | vdc->avc.sps = NULL; 106 | vdc->avc.pps = NULL; 107 | break; 108 | case MP4_VIDEO_CODEC_HEVC: 109 | free(vdc->hevc.vps); 110 | free(vdc->hevc.sps); 111 | free(vdc->hevc.pps); 112 | vdc->hevc.vps = NULL; 113 | vdc->hevc.sps = NULL; 114 | vdc->hevc.pps = NULL; 115 | break; 116 | default: 117 | return; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/mp4_demux.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_priv.h" 29 | 30 | #include 31 | #include 32 | #include 33 | 34 | 35 | static int mp4_metadata_build(struct mp4_file *mp4) 36 | { 37 | unsigned int i, k = 0, metaCount = 0, udtaCount = 0, xyzCount = 0; 38 | 39 | for (i = 0; i < mp4->metaMetadataCount; i++) { 40 | if ((mp4->metaMetadataValue[i]) && 41 | (strlen(mp4->metaMetadataValue[i]) > 0) && 42 | (mp4->metaMetadataKey[i]) && 43 | (strlen(mp4->metaMetadataKey[i]) > 0)) 44 | metaCount++; 45 | } 46 | 47 | for (i = 0; i < mp4->udtaMetadataCount; i++) { 48 | if ((mp4->udtaMetadataValue[i]) && 49 | (strlen(mp4->udtaMetadataValue[i]) > 0) && 50 | (mp4->udtaMetadataKey[i]) && 51 | (strlen(mp4->udtaMetadataKey[i]) > 0)) 52 | udtaCount++; 53 | } 54 | 55 | if ((mp4->udtaLocationValue) && (strlen(mp4->udtaLocationValue) > 0) && 56 | (mp4->udtaLocationKey) && (strlen(mp4->udtaLocationKey) > 0)) 57 | xyzCount++; 58 | 59 | mp4->finalMetadataCount = metaCount + udtaCount + xyzCount; 60 | 61 | mp4->finalMetadataKey = calloc(mp4->finalMetadataCount, sizeof(char *)); 62 | if (mp4->finalMetadataKey == NULL) { 63 | ULOG_ERRNO("calloc", ENOMEM); 64 | return -ENOMEM; 65 | } 66 | 67 | mp4->finalMetadataValue = 68 | calloc(mp4->finalMetadataCount, sizeof(char *)); 69 | if (mp4->finalMetadataValue == NULL) { 70 | ULOG_ERRNO("calloc", ENOMEM); 71 | return -ENOMEM; 72 | } 73 | 74 | for (i = 0; i < mp4->metaMetadataCount; i++) { 75 | if ((mp4->metaMetadataValue[i]) && 76 | (strlen(mp4->metaMetadataValue[i]) > 0) && 77 | (mp4->metaMetadataKey[i]) && 78 | (strlen(mp4->metaMetadataKey[i]) > 0)) { 79 | mp4->finalMetadataKey[k] = mp4->metaMetadataKey[i]; 80 | mp4->finalMetadataValue[k] = mp4->metaMetadataValue[i]; 81 | k++; 82 | } 83 | } 84 | 85 | for (i = 0; i < mp4->udtaMetadataCount; i++) { 86 | if ((mp4->udtaMetadataValue[i]) && 87 | (strlen(mp4->udtaMetadataValue[i]) > 0) && 88 | (mp4->udtaMetadataKey[i]) && 89 | (strlen(mp4->udtaMetadataKey[i]) > 0)) { 90 | mp4->finalMetadataKey[k] = mp4->udtaMetadataKey[i]; 91 | mp4->finalMetadataValue[k] = mp4->udtaMetadataValue[i]; 92 | k++; 93 | } 94 | } 95 | 96 | if ((mp4->udtaLocationValue) && (strlen(mp4->udtaLocationValue) > 0) && 97 | (mp4->udtaLocationKey) && (strlen(mp4->udtaLocationKey) > 0)) { 98 | mp4->finalMetadataKey[k] = mp4->udtaLocationKey; 99 | mp4->finalMetadataValue[k] = mp4->udtaLocationValue; 100 | k++; 101 | } 102 | 103 | if (mp4->metaCoverSize > 0) { 104 | mp4->finalCoverSize = mp4->metaCoverSize; 105 | mp4->finalCoverOffset = mp4->metaCoverOffset; 106 | mp4->finalCoverType = mp4->metaCoverType; 107 | } else if (mp4->udtaCoverSize > 0) { 108 | mp4->finalCoverSize = mp4->udtaCoverSize; 109 | mp4->finalCoverOffset = mp4->udtaCoverOffset; 110 | mp4->finalCoverType = mp4->udtaCoverType; 111 | } 112 | 113 | return 0; 114 | } 115 | 116 | 117 | int mp4_demux_open(const char *filename, struct mp4_demux **ret_obj) 118 | { 119 | int ret; 120 | off_t err; 121 | off_t retBytes; 122 | struct mp4_demux *demux; 123 | struct mp4_file *mp4; 124 | 125 | ULOG_ERRNO_RETURN_ERR_IF(filename == NULL, EINVAL); 126 | ULOG_ERRNO_RETURN_ERR_IF(strlen(filename) == 0, EINVAL); 127 | ULOG_ERRNO_RETURN_ERR_IF(ret_obj == NULL, EINVAL); 128 | 129 | demux = calloc(sizeof(*demux), 1); 130 | if (demux == NULL) { 131 | ret = -ENOMEM; 132 | ULOG_ERRNO("calloc", -ret); 133 | goto error; 134 | } 135 | mp4 = &demux->mp4; 136 | list_init(&mp4->tracks); 137 | 138 | mp4->fd = open(filename, O_RDONLY); 139 | if (mp4->fd == -1) { 140 | ret = -errno; 141 | ULOG_ERRNO("open:'%s'", -ret, filename); 142 | goto error; 143 | } 144 | 145 | mp4->fileSize = lseek(mp4->fd, 0, SEEK_END); 146 | if (mp4->fileSize < 0) { 147 | ret = -errno; 148 | ULOG_ERRNO("lseek", -ret); 149 | goto error; 150 | } else if (mp4->fileSize == 0) { 151 | ret = -ENODATA; 152 | ULOGW("empty file: '%s'", filename); 153 | goto error; 154 | } 155 | err = lseek(mp4->fd, 0, SEEK_SET); 156 | if (err == -1) { 157 | ret = -errno; 158 | ULOG_ERRNO("lseek", -ret); 159 | goto error; 160 | } 161 | 162 | mp4->root = mp4_box_new(NULL); 163 | if (mp4->root == NULL) { 164 | ret = -ENOMEM; 165 | ULOG_ERRNO("mp4_box_new", -ret); 166 | goto error; 167 | } 168 | mp4->root->type = MP4_ROOT_BOX; 169 | mp4->root->size = 1; 170 | mp4->root->largesize = mp4->fileSize; 171 | 172 | retBytes = mp4_box_children_read(mp4, mp4->root, mp4->fileSize, NULL); 173 | if (retBytes < 0) { 174 | ret = OFF_T_TO_ERRNO(retBytes, EPROTO); 175 | goto error; 176 | } 177 | mp4->readBytes += retBytes; 178 | 179 | ret = mp4_tracks_build(mp4); 180 | if (ret < 0) 181 | goto error; 182 | 183 | ret = mp4_metadata_build(mp4); 184 | if (ret < 0) 185 | goto error; 186 | 187 | mp4_box_log(mp4->root, ULOG_DEBUG); 188 | 189 | *ret_obj = demux; 190 | return 0; 191 | 192 | error: 193 | mp4_demux_close(demux); 194 | *ret_obj = NULL; 195 | return ret; 196 | } 197 | 198 | 199 | int mp4_demux_close(struct mp4_demux *demux) 200 | { 201 | if (demux == NULL) 202 | return 0; 203 | 204 | if (demux) { 205 | struct mp4_file *mp4 = &demux->mp4; 206 | if (mp4->fd) 207 | close(mp4->fd); 208 | mp4_box_destroy(mp4->root); 209 | mp4_tracks_destroy(mp4); 210 | unsigned int i; 211 | for (i = 0; i < mp4->chaptersCount; i++) 212 | free(mp4->chaptersName[i]); 213 | free(mp4->udtaLocationKey); 214 | free(mp4->udtaLocationValue); 215 | for (i = 0; i < mp4->udtaMetadataCount; i++) { 216 | free(mp4->udtaMetadataKey[i]); 217 | free(mp4->udtaMetadataValue[i]); 218 | } 219 | free(mp4->udtaMetadataKey); 220 | free(mp4->udtaMetadataValue); 221 | for (i = 0; i < mp4->metaMetadataCount; i++) { 222 | free(mp4->metaMetadataKey[i]); 223 | free(mp4->metaMetadataValue[i]); 224 | } 225 | free(mp4->metaMetadataKey); 226 | free(mp4->metaMetadataValue); 227 | free(mp4->finalMetadataKey); 228 | free(mp4->finalMetadataValue); 229 | } 230 | 231 | free(demux); 232 | 233 | return 0; 234 | } 235 | 236 | 237 | static int 238 | get_seek_sample(struct mp4_track *tk, int start, enum mp4_seek_method method) 239 | { 240 | int is_sync, prev_sync = -1, next_sync; 241 | uint64_t ts = tk->sampleDecodingTime[start], prev_ts, next_ts; 242 | 243 | switch (method) { 244 | case MP4_SEEK_METHOD_PREVIOUS: 245 | return start; 246 | case MP4_SEEK_METHOD_PREVIOUS_SYNC: 247 | is_sync = mp4_track_is_sync_sample(tk, start, &prev_sync); 248 | if (is_sync) 249 | return start; 250 | else if (prev_sync >= 0) 251 | return prev_sync; 252 | else 253 | return -ENOENT; 254 | case MP4_SEEK_METHOD_NEXT_SYNC: 255 | is_sync = mp4_track_is_sync_sample(tk, start, &prev_sync); 256 | next_sync = mp4_track_find_sample_by_time( 257 | tk, ts, MP4_TIME_CMP_GT, 1, start); 258 | if (is_sync) 259 | return start; 260 | else if (next_sync >= 0) 261 | return next_sync; 262 | else 263 | return -ENOENT; 264 | case MP4_SEEK_METHOD_NEAREST_SYNC: 265 | is_sync = mp4_track_is_sync_sample(tk, start, &prev_sync); 266 | next_sync = mp4_track_find_sample_by_time( 267 | tk, ts, MP4_TIME_CMP_GT, 1, start); 268 | if (is_sync) { 269 | return start; 270 | } else if (prev_sync >= 0 && next_sync >= 0) { 271 | prev_ts = tk->sampleDecodingTime[prev_sync]; 272 | next_ts = tk->sampleDecodingTime[next_sync]; 273 | if (ts - prev_ts > next_ts - ts) 274 | return next_sync; 275 | else 276 | return prev_sync; 277 | } else if (prev_sync >= 0) { 278 | return prev_sync; 279 | } else if (next_sync >= 0) { 280 | return next_sync; 281 | } else { 282 | return -ENOENT; 283 | } 284 | default: 285 | ULOGE("unsupported seek method: %d", method); 286 | return -EINVAL; 287 | } 288 | } 289 | 290 | 291 | static int get_metadata_sample_from_ref_track(struct mp4_track *ref_tk, 292 | int ref_sample) 293 | { 294 | int idx; 295 | struct mp4_track *metatk = ref_tk->metadata; 296 | uint64_t prev_sample_time = INT64_MAX, next_sample_time = INT64_MAX; 297 | 298 | if (!metatk) 299 | return -ENOENT; 300 | 301 | uint64_t sample_time = ref_tk->sampleDecodingTime[ref_sample]; 302 | if ((ref_sample - 1) >= 0) 303 | prev_sample_time = ref_tk->sampleDecodingTime[ref_sample - 1]; 304 | if ((ref_sample + 1) < (int)ref_tk->sampleCount) 305 | next_sample_time = ref_tk->sampleDecodingTime[ref_sample + 1]; 306 | 307 | idx = mp4_track_find_sample_by_time( 308 | metatk, 309 | mp4_convert_timescale( 310 | sample_time, ref_tk->timescale, metatk->timescale), 311 | MP4_TIME_CMP_NEAREST, 312 | 0, 313 | 0); 314 | if (idx < 0) 315 | return -ENOENT; 316 | 317 | uint64_t meta_sample_time = 318 | mp4_convert_timescale(metatk->sampleDecodingTime[idx], 319 | metatk->timescale, 320 | ref_tk->timescale); 321 | 322 | /* Exact match: return */ 323 | if (meta_sample_time == sample_time) 324 | return idx; 325 | 326 | uint64_t delta_time = 327 | llabs((int64_t)meta_sample_time - (int64_t)sample_time); 328 | uint64_t prev_delta_time = 329 | llabs((int64_t)meta_sample_time - (int64_t)prev_sample_time); 330 | uint64_t next_delta_time = 331 | llabs((int64_t)meta_sample_time - (int64_t)next_sample_time); 332 | 333 | /* Return metadata sample only if closest to the current sample */ 334 | if ((delta_time < prev_delta_time) && (delta_time < next_delta_time)) 335 | return idx; 336 | 337 | return -ENOENT; 338 | } 339 | 340 | 341 | int mp4_demux_seek(struct mp4_demux *demux, 342 | uint64_t time_offset, 343 | enum mp4_seek_method method) 344 | { 345 | struct mp4_track *tk = NULL; 346 | struct mp4_file *mp4; 347 | 348 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 349 | 350 | mp4 = &demux->mp4; 351 | 352 | list_walk_entry_forward(&mp4->tracks, tk, node) 353 | { 354 | int found = 0, i, idx = 0; 355 | uint64_t ts = 356 | mp4_usec_to_sample_time(time_offset, tk->timescale); 357 | uint64_t newPendingSeekTime = 0; 358 | int start = (unsigned int)(((uint64_t)tk->sampleCount * ts + 359 | tk->duration - 1) / 360 | tk->duration); 361 | if (start < 0) 362 | start = 0; 363 | if ((unsigned)start >= tk->sampleCount) 364 | start = tk->sampleCount - 1; 365 | while (((unsigned)start < tk->sampleCount - 1) && 366 | (tk->sampleDecodingTime[start] < ts)) 367 | start++; 368 | for (i = start; i >= 0; i--) { 369 | if (tk->sampleDecodingTime[i] <= ts) { 370 | idx = get_seek_sample(tk, i, method); 371 | if (idx < 0) 372 | break; 373 | newPendingSeekTime = 374 | (idx == i) ? 0 375 | : tk->sampleDecodingTime[i]; 376 | found = 1; 377 | break; 378 | } 379 | } 380 | if (found) { 381 | tk->nextSample = idx; 382 | tk->pendingSeekTime = newPendingSeekTime; 383 | ULOGD("seek to %" PRIu64 " -> sample #%d time %" PRIu64, 384 | time_offset, 385 | idx, 386 | mp4_sample_time_to_usec( 387 | tk->sampleDecodingTime[idx], 388 | tk->timescale)); 389 | } else { 390 | ULOGE("unable to seek in track"); 391 | return -ENOENT; 392 | } 393 | } 394 | 395 | return 0; 396 | } 397 | 398 | 399 | int mp4_demux_get_media_info(struct mp4_demux *demux, 400 | struct mp4_media_info *media_info) 401 | { 402 | struct mp4_file *mp4; 403 | 404 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 405 | ULOG_ERRNO_RETURN_ERR_IF(media_info == NULL, EINVAL); 406 | 407 | mp4 = &demux->mp4; 408 | 409 | memset(media_info, 0, sizeof(*media_info)); 410 | 411 | media_info->duration = 412 | mp4_sample_time_to_usec(mp4->duration, mp4->timescale); 413 | media_info->creation_time = 414 | mp4->creationTime - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 415 | media_info->modification_time = 416 | mp4->creationTime - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 417 | media_info->track_count = mp4->trackCount; 418 | 419 | return 0; 420 | } 421 | 422 | 423 | int mp4_demux_get_track_count(struct mp4_demux *demux) 424 | { 425 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 426 | 427 | return demux->mp4.trackCount; 428 | } 429 | 430 | 431 | int mp4_demux_get_track_info(struct mp4_demux *demux, 432 | unsigned int track_idx, 433 | struct mp4_track_info *track_info) 434 | { 435 | struct mp4_file *mp4; 436 | struct mp4_track *tk = NULL; 437 | 438 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 439 | ULOG_ERRNO_RETURN_ERR_IF(track_info == NULL, EINVAL); 440 | 441 | mp4 = &demux->mp4; 442 | 443 | ULOG_ERRNO_RETURN_ERR_IF(track_idx >= mp4->trackCount, ENOENT); 444 | 445 | tk = mp4_track_find_by_idx(mp4, track_idx); 446 | if (tk == NULL) { 447 | ULOGE("track index=%d not found", track_idx); 448 | return -ENOENT; 449 | } 450 | 451 | memset(track_info, 0, sizeof(*track_info)); 452 | track_info->id = tk->id; 453 | track_info->name = tk->name; 454 | track_info->enabled = tk->enabled; 455 | track_info->in_movie = tk->in_movie; 456 | track_info->in_preview = tk->in_preview; 457 | track_info->type = tk->type; 458 | track_info->timescale = tk->timescale; 459 | track_info->duration = tk->duration; 460 | track_info->creation_time = 461 | tk->creationTime - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 462 | track_info->modification_time = 463 | tk->creationTime - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 464 | track_info->sample_count = tk->sampleCount; 465 | track_info->sample_max_size = tk->sampleMaxSize; 466 | track_info->sample_offsets = tk->sampleOffset; 467 | track_info->sample_sizes = tk->sampleSize; 468 | track_info->has_metadata = (tk->metadata) ? 1 : 0; 469 | if (tk->metadata) { 470 | track_info->metadata_content_encoding = 471 | tk->metadata->contentEncoding; 472 | track_info->metadata_mime_format = tk->metadata->mimeFormat; 473 | } 474 | if (tk->type == MP4_TRACK_TYPE_METADATA) { 475 | track_info->content_encoding = tk->contentEncoding; 476 | track_info->mime_format = tk->mimeFormat; 477 | } else if (tk->type == MP4_TRACK_TYPE_VIDEO) { 478 | track_info->video_codec = tk->vdc.codec; 479 | track_info->video_width = tk->vdc.width; 480 | track_info->video_height = tk->vdc.height; 481 | } else if (tk->type == MP4_TRACK_TYPE_AUDIO) { 482 | track_info->audio_codec = tk->audioCodec; 483 | track_info->audio_channel_count = tk->audioChannelCount; 484 | track_info->audio_sample_size = tk->audioSampleSize; 485 | track_info->audio_sample_rate = 486 | (float)tk->audioSampleRate / 65536.; 487 | } 488 | 489 | return 0; 490 | } 491 | 492 | 493 | int mp4_demux_get_track_video_decoder_config( 494 | struct mp4_demux *demux, 495 | unsigned int track_id, 496 | struct mp4_video_decoder_config *vdc) 497 | { 498 | struct mp4_file *mp4; 499 | struct mp4_track *tk = NULL; 500 | 501 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 502 | ULOG_ERRNO_RETURN_ERR_IF(vdc == NULL, EINVAL); 503 | 504 | mp4 = &demux->mp4; 505 | 506 | tk = mp4_track_find_by_id(mp4, track_id); 507 | if (tk == NULL) { 508 | ULOGE("track id=%d not found", track_id); 509 | return -ENOENT; 510 | } 511 | if (tk->type != MP4_TRACK_TYPE_VIDEO) { 512 | ULOGE("track id=%d is not of video type", track_id); 513 | return -EINVAL; 514 | } 515 | 516 | vdc->width = tk->vdc.width; 517 | vdc->height = tk->vdc.height; 518 | 519 | switch (tk->vdc.codec) { 520 | case MP4_VIDEO_CODEC_HEVC: 521 | vdc->codec = MP4_VIDEO_CODEC_HEVC; 522 | vdc->hevc.hvcc_info = tk->vdc.hevc.hvcc_info; 523 | if (tk->vdc.hevc.vps) { 524 | vdc->hevc.vps = tk->vdc.hevc.vps; 525 | vdc->hevc.vps_size = tk->vdc.hevc.vps_size; 526 | } 527 | if (tk->vdc.hevc.sps) { 528 | vdc->hevc.sps = tk->vdc.hevc.sps; 529 | vdc->hevc.sps_size = tk->vdc.hevc.sps_size; 530 | } 531 | if (tk->vdc.hevc.pps) { 532 | vdc->hevc.pps = tk->vdc.hevc.pps; 533 | vdc->hevc.pps_size = tk->vdc.hevc.pps_size; 534 | } 535 | break; 536 | case MP4_VIDEO_CODEC_AVC: 537 | vdc->codec = MP4_VIDEO_CODEC_AVC; 538 | if (tk->vdc.avc.sps) { 539 | vdc->avc.sps = tk->vdc.avc.sps; 540 | vdc->avc.sps_size = tk->vdc.avc.sps_size; 541 | } 542 | if (tk->vdc.avc.pps) { 543 | vdc->avc.pps = tk->vdc.avc.pps; 544 | vdc->avc.pps_size = tk->vdc.avc.pps_size; 545 | } 546 | break; 547 | default: 548 | ULOGE("track id=%d video codec is neither AVC nor HEVC", 549 | track_id); 550 | return -EINVAL; 551 | } 552 | 553 | return 0; 554 | } 555 | 556 | 557 | int mp4_demux_get_track_audio_specific_config(struct mp4_demux *demux, 558 | unsigned int track_id, 559 | uint8_t **audio_specific_config, 560 | unsigned int *asc_size) 561 | { 562 | struct mp4_file *mp4; 563 | struct mp4_track *tk = NULL; 564 | 565 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 566 | 567 | if (audio_specific_config != NULL) 568 | *audio_specific_config = NULL; 569 | if (asc_size != NULL) 570 | *asc_size = 0; 571 | 572 | mp4 = &demux->mp4; 573 | 574 | tk = mp4_track_find_by_id(mp4, track_id); 575 | if (tk == NULL) { 576 | ULOGE("track id=%d not found", track_id); 577 | return -ENOENT; 578 | } 579 | if (tk->type != MP4_TRACK_TYPE_AUDIO) { 580 | ULOGE("track id=%d is not of audio type", track_id); 581 | return -EINVAL; 582 | } 583 | 584 | if (tk->audioSpecificConfig == NULL) { 585 | ULOGE("track does not have an AudioSpecificConfig"); 586 | return -EPROTO; 587 | } 588 | 589 | if (audio_specific_config != NULL) 590 | *audio_specific_config = tk->audioSpecificConfig; 591 | 592 | if (asc_size != NULL) 593 | *asc_size = tk->audioSpecificConfigSize; 594 | 595 | return 0; 596 | } 597 | 598 | 599 | int mp4_demux_get_track_sample(struct mp4_demux *demux, 600 | unsigned int track_id, 601 | int advance, 602 | uint8_t *sample_buffer, 603 | unsigned int sample_buffer_size, 604 | uint8_t *metadata_buffer, 605 | unsigned int metadata_buffer_size, 606 | struct mp4_track_sample *track_sample) 607 | { 608 | struct mp4_file *mp4; 609 | struct mp4_track *tk = NULL; 610 | int idx; 611 | uint64_t sampleTime; 612 | uint32_t sample_size, metadata_size; 613 | uint64_t sample_offset, metadata_offset; 614 | 615 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 616 | ULOG_ERRNO_RETURN_ERR_IF(track_sample == NULL, EINVAL); 617 | 618 | mp4 = &demux->mp4; 619 | 620 | tk = mp4_track_find_by_id(mp4, track_id); 621 | if (tk == NULL) { 622 | ULOGE("track id=%d not found", track_id); 623 | return -ENOENT; 624 | } 625 | 626 | memset(track_sample, 0, sizeof(*track_sample)); 627 | 628 | if (tk->nextSample >= tk->sampleCount) 629 | return 0; 630 | 631 | sample_size = tk->sampleSize[tk->nextSample]; 632 | sample_offset = tk->sampleOffset[tk->nextSample]; 633 | track_sample->size = sample_size; 634 | track_sample->offset = sample_offset; 635 | if ((sample_buffer) && (sample_size > 0) && 636 | (sample_size <= sample_buffer_size)) { 637 | off_t _ret = lseek(mp4->fd, sample_offset, SEEK_SET); 638 | if (_ret == -1) { 639 | ULOG_ERRNO("lseek", errno); 640 | return -errno; 641 | } 642 | ssize_t count = read(mp4->fd, sample_buffer, sample_size); 643 | if (count == -1) { 644 | track_sample->size = 0; 645 | sample_size = 0; 646 | _ret = -errno; 647 | ULOG_ERRNO("read", -_ret); 648 | return _ret; 649 | } else if (count != (ssize_t)sample_size) { 650 | track_sample->size = 0; 651 | sample_size = 0; 652 | _ret = -ENODATA; 653 | ULOG_ERRNO("read", -_ret); 654 | return _ret; 655 | } 656 | } else if ((sample_buffer) && (sample_size > sample_buffer_size)) { 657 | ULOGE("buffer too small (%d bytes, %d needed)", 658 | sample_buffer_size, 659 | sample_size); 660 | return -ENOBUFS; 661 | } 662 | sampleTime = tk->sampleDecodingTime[tk->nextSample]; 663 | if (tk->metadata) { 664 | struct mp4_track *metatk = tk->metadata; 665 | int idx = 666 | get_metadata_sample_from_ref_track(tk, tk->nextSample); 667 | if (idx < 0) { 668 | ULOGD("no metadata available at sample time: %" PRIu64, 669 | sampleTime); 670 | } else { 671 | metadata_size = metatk->sampleSize[idx]; 672 | metadata_offset = metatk->sampleOffset[idx]; 673 | track_sample->metadata_size = metadata_size; 674 | if ((metadata_buffer) && (metadata_size > 0) && 675 | (metadata_size <= metadata_buffer_size)) { 676 | off_t _ret = lseek( 677 | mp4->fd, metadata_offset, SEEK_SET); 678 | if (_ret == -1) { 679 | ULOG_ERRNO("lseek", errno); 680 | return -errno; 681 | } 682 | ssize_t count = read(mp4->fd, 683 | metadata_buffer, 684 | metadata_size); 685 | if (count == -1) { 686 | track_sample->metadata_size = 0; 687 | _ret = -errno; 688 | if (_ret == 0) 689 | _ret = -ENODATA; 690 | ULOG_ERRNO("read", -_ret); 691 | return _ret; 692 | } 693 | } else if ((metadata_buffer) && 694 | (metadata_size > metadata_buffer_size)) { 695 | ULOGE("buffer too small for metadata " 696 | "(%d bytes, %d needed)", 697 | metadata_buffer_size, 698 | metadata_size); 699 | return -ENOBUFS; 700 | } 701 | } 702 | } 703 | track_sample->silent = 704 | ((tk->pendingSeekTime) && (sampleTime < tk->pendingSeekTime)); 705 | if (sampleTime >= tk->pendingSeekTime) 706 | tk->pendingSeekTime = 0; 707 | track_sample->dts = sampleTime; 708 | track_sample->next_dts = 709 | (tk->nextSample < tk->sampleCount - 1) 710 | ? tk->sampleDecodingTime[tk->nextSample + 1] 711 | : 0; 712 | idx = mp4_track_find_sample_by_time( 713 | tk, sampleTime, MP4_TIME_CMP_LT, 1, tk->nextSample); 714 | if (idx >= 0) 715 | track_sample->prev_sync_dts = tk->sampleDecodingTime[idx]; 716 | idx = mp4_track_find_sample_by_time( 717 | tk, sampleTime, MP4_TIME_CMP_GT, 1, tk->nextSample); 718 | if (idx >= 0) 719 | track_sample->next_sync_dts = tk->sampleDecodingTime[idx]; 720 | track_sample->sync = mp4_track_is_sync_sample(tk, tk->nextSample, NULL); 721 | 722 | if (advance) 723 | tk->nextSample++; 724 | 725 | return 0; 726 | } 727 | 728 | 729 | int mp4_demux_seek_to_track_prev_sample(struct mp4_demux *demux, 730 | unsigned int track_id) 731 | { 732 | struct mp4_file *mp4; 733 | struct mp4_track *tk = NULL; 734 | int idx; 735 | uint64_t ts; 736 | 737 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 738 | 739 | mp4 = &demux->mp4; 740 | 741 | tk = mp4_track_find_by_id(mp4, track_id); 742 | if (tk == NULL) { 743 | ULOGE("track id=%d not found", track_id); 744 | return -ENOENT; 745 | } 746 | 747 | idx = (tk->nextSample >= 2) ? tk->nextSample - 2 : 0; 748 | ts = mp4_sample_time_to_usec(tk->sampleDecodingTime[idx], 749 | tk->timescale); 750 | 751 | return mp4_demux_seek(demux, ts, MP4_SEEK_METHOD_PREVIOUS_SYNC); 752 | } 753 | 754 | 755 | int mp4_demux_seek_to_track_next_sample(struct mp4_demux *demux, 756 | unsigned int track_id) 757 | { 758 | struct mp4_file *mp4; 759 | struct mp4_track *tk = NULL; 760 | int idx; 761 | uint64_t ts; 762 | 763 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 764 | 765 | mp4 = &demux->mp4; 766 | 767 | tk = mp4_track_find_by_id(mp4, track_id); 768 | if (tk == NULL) { 769 | ULOGE("track id=%d not found", track_id); 770 | return -ENOENT; 771 | } 772 | 773 | idx = (tk->nextSample < tk->sampleCount - 1) ? tk->nextSample + 1 : 0; 774 | ts = mp4_sample_time_to_usec(tk->sampleDecodingTime[idx], 775 | tk->timescale); 776 | 777 | return mp4_demux_seek(demux, ts, MP4_SEEK_METHOD_PREVIOUS); 778 | } 779 | 780 | 781 | int mp4_demux_get_track_prev_sample_time(struct mp4_demux *demux, 782 | unsigned int track_id, 783 | uint64_t *sample_time) 784 | { 785 | int ret = 0; 786 | struct mp4_file *mp4; 787 | struct mp4_track *tk = NULL; 788 | uint64_t prev_ts = 0; 789 | 790 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 791 | ULOG_ERRNO_RETURN_ERR_IF(sample_time == NULL, EINVAL); 792 | 793 | mp4 = &demux->mp4; 794 | 795 | tk = mp4_track_find_by_id(mp4, track_id); 796 | if (tk == NULL) { 797 | ULOGE("track id=%d not found", track_id); 798 | ret = -ENOENT; 799 | goto exit; 800 | } 801 | 802 | if (tk->nextSample >= 2) { 803 | prev_ts = mp4_sample_time_to_usec( 804 | tk->sampleDecodingTime[tk->nextSample - 2], 805 | tk->timescale); 806 | } else { 807 | ret = -ENOENT; 808 | } 809 | 810 | exit: 811 | *sample_time = prev_ts; 812 | return ret; 813 | } 814 | 815 | 816 | int mp4_demux_get_track_next_sample_time(struct mp4_demux *demux, 817 | unsigned int track_id, 818 | uint64_t *sample_time) 819 | { 820 | int ret = 0; 821 | struct mp4_file *mp4; 822 | struct mp4_track *tk = NULL; 823 | uint64_t next_ts = 0; 824 | 825 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 826 | ULOG_ERRNO_RETURN_ERR_IF(sample_time == NULL, EINVAL); 827 | 828 | mp4 = &demux->mp4; 829 | 830 | tk = mp4_track_find_by_id(mp4, track_id); 831 | if (tk == NULL) { 832 | ULOGE("track id=%d not found", track_id); 833 | ret = -ENOENT; 834 | goto exit; 835 | } 836 | 837 | if (tk->nextSample < tk->sampleCount) { 838 | next_ts = mp4_sample_time_to_usec( 839 | tk->sampleDecodingTime[tk->nextSample], tk->timescale); 840 | } else { 841 | ret = -ENOENT; 842 | } 843 | 844 | exit: 845 | *sample_time = next_ts; 846 | return ret; 847 | } 848 | 849 | 850 | static int mp4_demux_get_track_sample_time(struct mp4_demux *demux, 851 | unsigned int track_id, 852 | uint64_t time, 853 | int sync, 854 | enum mp4_time_cmp cmp, 855 | uint64_t *sample_time) 856 | { 857 | struct mp4_file *mp4; 858 | struct mp4_track *tk = NULL; 859 | int idx, ret; 860 | uint64_t ts = 0, sample_ts = 0; 861 | 862 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 863 | ULOG_ERRNO_RETURN_ERR_IF(sample_time == NULL, EINVAL); 864 | 865 | mp4 = &demux->mp4; 866 | 867 | tk = mp4_track_find_by_id(mp4, track_id); 868 | if (tk == NULL) { 869 | ULOGE("track id=%d not found", track_id); 870 | ret = -ENOENT; 871 | goto exit; 872 | } 873 | 874 | ts = mp4_usec_to_sample_time(time, tk->timescale); 875 | idx = mp4_track_find_sample_by_time(tk, ts, cmp, sync, -1); 876 | 877 | if (idx >= 0) { 878 | sample_ts = mp4_sample_time_to_usec(tk->sampleDecodingTime[idx], 879 | tk->timescale); 880 | ret = 0; 881 | } else { 882 | ULOGE("no sample found for the requested time"); 883 | ret = -ENOENT; 884 | } 885 | 886 | exit: 887 | *sample_time = sample_ts; 888 | return ret; 889 | } 890 | 891 | int mp4_demux_get_track_prev_sample_time_before(struct mp4_demux *demux, 892 | unsigned int track_id, 893 | uint64_t time, 894 | int sync, 895 | uint64_t *sample_time) 896 | { 897 | return mp4_demux_get_track_sample_time( 898 | demux, track_id, time, sync, MP4_TIME_CMP_LT, sample_time); 899 | } 900 | 901 | 902 | int mp4_demux_get_track_next_sample_time_after(struct mp4_demux *demux, 903 | unsigned int track_id, 904 | uint64_t time, 905 | int sync, 906 | uint64_t *sample_time) 907 | { 908 | return mp4_demux_get_track_sample_time( 909 | demux, track_id, time, sync, MP4_TIME_CMP_GT, sample_time); 910 | } 911 | 912 | 913 | int mp4_demux_get_chapters(struct mp4_demux *demux, 914 | unsigned int *chapters_count, 915 | uint64_t **chapters_time, 916 | char ***chapters_name) 917 | { 918 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 919 | ULOG_ERRNO_RETURN_ERR_IF(chapters_count == NULL, EINVAL); 920 | ULOG_ERRNO_RETURN_ERR_IF(chapters_time == NULL, EINVAL); 921 | ULOG_ERRNO_RETURN_ERR_IF(chapters_name == NULL, EINVAL); 922 | 923 | *chapters_count = demux->mp4.chaptersCount; 924 | *chapters_time = demux->mp4.chaptersTime; 925 | *chapters_name = demux->mp4.chaptersName; 926 | 927 | return 0; 928 | } 929 | 930 | 931 | int mp4_demux_get_metadata_strings(struct mp4_demux *demux, 932 | unsigned int *count, 933 | char ***keys, 934 | char ***values) 935 | { 936 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 937 | ULOG_ERRNO_RETURN_ERR_IF(count == NULL, EINVAL); 938 | ULOG_ERRNO_RETURN_ERR_IF(keys == NULL, EINVAL); 939 | ULOG_ERRNO_RETURN_ERR_IF(values == NULL, EINVAL); 940 | 941 | *count = demux->mp4.finalMetadataCount; 942 | *keys = demux->mp4.finalMetadataKey; 943 | *values = demux->mp4.finalMetadataValue; 944 | 945 | return 0; 946 | } 947 | 948 | 949 | int mp4_demux_get_track_metadata_strings(struct mp4_demux *demux, 950 | unsigned int track_id, 951 | unsigned int *count, 952 | char ***keys, 953 | char ***values) 954 | { 955 | struct mp4_file *mp4; 956 | struct mp4_track *tk = NULL; 957 | 958 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 959 | ULOG_ERRNO_RETURN_ERR_IF(count == NULL, EINVAL); 960 | ULOG_ERRNO_RETURN_ERR_IF(keys == NULL, EINVAL); 961 | ULOG_ERRNO_RETURN_ERR_IF(values == NULL, EINVAL); 962 | 963 | mp4 = &demux->mp4; 964 | 965 | tk = mp4_track_find_by_id(mp4, track_id); 966 | if (tk == NULL) { 967 | ULOGE("track id=%d not found", track_id); 968 | return -ENOENT; 969 | } 970 | 971 | *count = tk->staticMetadataCount; 972 | *keys = tk->staticMetadataKey; 973 | *values = tk->staticMetadataValue; 974 | 975 | return 0; 976 | } 977 | 978 | 979 | int mp4_demux_get_metadata_cover(struct mp4_demux *demux, 980 | uint8_t *cover_buffer, 981 | unsigned int cover_buffer_size, 982 | unsigned int *cover_size, 983 | enum mp4_metadata_cover_type *cover_type) 984 | { 985 | int ret; 986 | struct mp4_file *mp4; 987 | 988 | ULOG_ERRNO_RETURN_ERR_IF(demux == NULL, EINVAL); 989 | ULOG_ERRNO_RETURN_ERR_IF(cover_size == NULL, EINVAL); 990 | 991 | mp4 = &demux->mp4; 992 | 993 | if (mp4->finalCoverSize > 0) { 994 | *cover_size = mp4->finalCoverSize; 995 | if (cover_type) 996 | *cover_type = mp4->finalCoverType; 997 | if ((cover_buffer) && 998 | (mp4->finalCoverSize <= cover_buffer_size)) { 999 | off_t _ret = 1000 | lseek(mp4->fd, mp4->finalCoverOffset, SEEK_SET); 1001 | if (_ret == -1) { 1002 | ULOG_ERRNO("lseek", errno); 1003 | return -errno; 1004 | } 1005 | ssize_t count = read( 1006 | mp4->fd, cover_buffer, mp4->finalCoverSize); 1007 | if (count == -1) { 1008 | ret = -errno; 1009 | ULOG_ERRNO("read", -ret); 1010 | return ret; 1011 | } else if (count != (ssize_t)mp4->finalCoverSize) { 1012 | ret = -ENODATA; 1013 | ULOG_ERRNO("read", -ret); 1014 | return ret; 1015 | } 1016 | } else if (cover_buffer) { 1017 | ULOGE("buffer too small (%d bytes, %d needed)", 1018 | cover_buffer_size, 1019 | mp4->finalCoverSize); 1020 | return -ENOBUFS; 1021 | } 1022 | } else { 1023 | *cover_size = 0; 1024 | } 1025 | 1026 | return 0; 1027 | } 1028 | -------------------------------------------------------------------------------- /src/mp4_priv.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _MP4_H_ 29 | #define _MP4_H_ 30 | 31 | #ifndef ANDROID 32 | # ifndef _FILE_OFFSET_BITS 33 | # define _FILE_OFFSET_BITS 64 34 | # endif 35 | #endif 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #ifdef _WIN32 48 | # include 49 | #else /* !_WIN32 */ 50 | # include 51 | #endif /* !_WIN32 */ 52 | 53 | #define ULOG_TAG libmp4 54 | #include 55 | 56 | #include 57 | #include 58 | 59 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) 60 | 61 | /* Note: MAX_ALLOC_SIZE must be large enough to hold thumbnail */ 62 | #define MAX_ALLOC_SIZE (10 * 1000 * 1000) 63 | 64 | 65 | /* clang-format off */ 66 | #define MP4_ISOM 0x69736f6d /* "isom" */ 67 | #define MP4_ISO2 0x69736f32 /* "iso2" */ 68 | #define MP4_MP41 0x6d703431 /* "mp41" */ 69 | #define MP4_AVC1 0x61766331 /* "avc1" */ 70 | #define MP4_HVC1 0x68766331 /* "hvc1" */ 71 | #define MP4_MP4A 0x6d703461 /* "mp4a" */ 72 | #define MP4_UUID 0x75756964 /* "uuid" */ 73 | #define MP4_MHLR 0x6d686c72 /* "mhlr" */ 74 | #define MP4_ROOT_BOX 0x726f6f74 /* "root" */ 75 | #define MP4_FILE_TYPE_BOX 0x66747970 /* "ftyp" */ 76 | #define MP4_FREE_BOX 0x66726565 /* "free" */ 77 | #define MP4_MDAT_BOX 0x6d646174 /* "mdat" */ 78 | #define MP4_MOVIE_BOX 0x6d6f6f76 /* "moov" */ 79 | #define MP4_USER_DATA_BOX 0x75647461 /* "udta" */ 80 | #define MP4_MOVIE_HEADER_BOX 0x6d766864 /* "mvhd" */ 81 | #define MP4_TRACK_BOX 0x7472616b /* "trak" */ 82 | #define MP4_TRACK_HEADER_BOX 0x746b6864 /* "tkhd" */ 83 | #define MP4_TRACK_REFERENCE_BOX 0x74726566 /* "tref" */ 84 | #define MP4_MEDIA_BOX 0x6d646961 /* "mdia" */ 85 | #define MP4_MEDIA_HEADER_BOX 0x6d646864 /* "mdhd" */ 86 | #define MP4_HANDLER_REFERENCE_BOX 0x68646c72 /* "hdlr" */ 87 | #define MP4_MEDIA_INFORMATION_BOX 0x6d696e66 /* "minf" */ 88 | #define MP4_VIDEO_MEDIA_HEADER_BOX 0x766d6864 /* "vmhd" */ 89 | #define MP4_SOUND_MEDIA_HEADER_BOX 0x736d6864 /* "smhd" */ 90 | #define MP4_HINT_MEDIA_HEADER_BOX 0x686d6864 /* "hmhd" */ 91 | #define MP4_NULL_MEDIA_HEADER_BOX 0x6e6d6864 /* "nmhd" */ 92 | #define MP4_GENERIC_MEDIA_HEADER_BOX 0x676d6864 /* "gmhd" */ 93 | #define MP4_GENERIC_MEDIA_INFO_BOX 0x676d696e /* "gmin" */ 94 | #define MP4_DATA_INFORMATION_BOX 0x64696e66 /* "dinf" */ 95 | #define MP4_DATA_REFERENCE_BOX 0x64726566 /* "dref" */ 96 | #define MP4_SAMPLE_TABLE_BOX 0x7374626c /* "stbl" */ 97 | #define MP4_SAMPLE_DESCRIPTION_BOX 0x73747364 /* "stsd" */ 98 | #define MP4_AVC_DECODER_CONFIG_BOX 0x61766343 /* "avcC" */ 99 | #define MP4_HEVC_DECODER_CONFIG_BOX 0x68766343 /* "hvcC" */ 100 | #define MP4_AUDIO_DECODER_CONFIG_BOX 0x65736473 /* "esds" */ 101 | #define MP4_DECODING_TIME_TO_SAMPLE_BOX 0x73747473 /* "stts" */ 102 | #define MP4_SYNC_SAMPLE_BOX 0x73747373 /* "stss" */ 103 | #define MP4_SAMPLE_SIZE_BOX 0x7374737a /* "stsz" */ 104 | #define MP4_SAMPLE_TO_CHUNK_BOX 0x73747363 /* "stsc" */ 105 | #define MP4_CHUNK_OFFSET_BOX 0x7374636f /* "stco" */ 106 | #define MP4_CHUNK_OFFSET_64_BOX 0x636f3634 /* "co64" */ 107 | #define MP4_META_BOX 0x6d657461 /* "meta" */ 108 | #define MP4_KEYS_BOX 0x6b657973 /* "keys" */ 109 | #define MP4_ILST_BOX 0x696c7374 /* "ilst" */ 110 | #define MP4_DATA_BOX 0x64617461 /* "data" */ 111 | #define MP4_LOCATION_BOX 0xa978797a /* ".xyz" */ 112 | #define MP4_EDTS_BOX 0x65647473 /* "edts" */ 113 | #define MP4_ELST 0x656c7374 /* "elst" */ 114 | 115 | #define MP4_HANDLER_TYPE_VIDEO 0x76696465 /* "vide" */ 116 | #define MP4_HANDLER_TYPE_AUDIO 0x736f756e /* "soun" */ 117 | #define MP4_HANDLER_TYPE_HINT 0x68696e74 /* "hint" */ 118 | #define MP4_HANDLER_TYPE_METADATA 0x6d657461 /* "meta" */ 119 | #define MP4_HANDLER_TYPE_TEXT 0x74657874 /* "text" */ 120 | 121 | #define MP4_REFERENCE_TYPE_HINT 0x68696e74 /* "hint" */ 122 | #define MP4_REFERENCE_TYPE_DESCRIPTION 0x63647363 /* "cdsc" */ 123 | #define MP4_REFERENCE_TYPE_HINT_USED 0x68696e64 /* "hind" */ 124 | #define MP4_REFERENCE_TYPE_CHAPTERS 0x63686170 /* "chap" */ 125 | 126 | #define MP4_DATA_REFERENCE_TYPE_URL 0x75726c20 /* "url " */ 127 | 128 | #define MP4_XML_METADATA_SAMPLE_ENTRY 0x6d657478 /* "metx" */ 129 | #define MP4_TEXT_METADATA_SAMPLE_ENTRY 0x6d657474 /* "mett" */ 130 | #define MP4_TEXT_SAMPLE_ENTRY 0x74657874 /* "text" */ 131 | 132 | #define MP4_METADATA_NAMESPACE_MDTA 0x6d647461 /* "mdta" */ 133 | #define MP4_METADATA_HANDLER_TYPE_MDIR 0x6d646972 /* "mdir" */ 134 | #define MP4_METADATA_HANDLER_TYPE_APPL 0x6170706c /* "appl" */ 135 | 136 | #define MP4_METADATA_CLASS_UTF8 (1) 137 | #define MP4_METADATA_CLASS_JPEG (13) 138 | #define MP4_METADATA_CLASS_PNG (14) 139 | #define MP4_METADATA_CLASS_BMP (27) 140 | 141 | #define MP4_METADATA_TAG_TYPE_ARTIST 0x00415254 /* ".ART" */ 142 | #define MP4_METADATA_TAG_TYPE_TITLE 0x006e616d /* ".nam" */ 143 | #define MP4_METADATA_TAG_TYPE_DATE 0x00646179 /* ".day" */ 144 | #define MP4_METADATA_TAG_TYPE_COMMENT 0x00636d74 /* ".cmt" */ 145 | #define MP4_METADATA_TAG_TYPE_COPYRIGHT 0x00637079 /* ".cpy" */ 146 | #define MP4_METADATA_TAG_TYPE_MAKER 0x006d616b /* ".mak" */ 147 | #define MP4_METADATA_TAG_TYPE_MODEL 0x006d6f64 /* ".mod" */ 148 | #define MP4_METADATA_TAG_TYPE_VERSION 0x00737772 /* ".swr" */ 149 | #define MP4_METADATA_TAG_TYPE_ENCODER 0x00746f6f /* ".too" */ 150 | #define MP4_METADATA_TAG_TYPE_COVER 0x636f7672 /* "covr" */ 151 | /* clang-format on */ 152 | 153 | #define MP4_METADATA_KEY_COVER "com.apple.quicktime.artwork" 154 | 155 | #define MP4_MAC_TO_UNIX_EPOCH_OFFSET (0x7c25b080UL) 156 | 157 | #define MP4_CHAPTERS_MAX 100 158 | #define MP4_TRACK_REF_MAX 10 159 | 160 | /* Track flags definition */ 161 | #define TRACK_FLAG_ENABLED (1 << 0) 162 | #define TRACK_FLAG_IN_MOVIE (1 << 1) 163 | #define TRACK_FLAG_IN_PREVIEW (1 << 2) 164 | 165 | 166 | enum mp4_h265_nalu_type { 167 | MP4_H265_NALU_TYPE_UNKNOWN = 0, /* Unknown type */ 168 | MP4_H265_NALU_TYPE_VPS = 32, /* Video parameter set */ 169 | MP4_H265_NALU_TYPE_SPS = 33, /* Sequence parameter set */ 170 | MP4_H265_NALU_TYPE_PPS = 34, /* Picture parameter set */ 171 | }; 172 | 173 | 174 | enum mp4_time_cmp { 175 | MP4_TIME_CMP_EXACT, /* Exact match */ 176 | MP4_TIME_CMP_NEAREST, /* Nearest sample */ 177 | MP4_TIME_CMP_LT, /* Less than */ 178 | MP4_TIME_CMP_GT, /* Greater than */ 179 | MP4_TIME_CMP_LT_EQ, /* Less than or equal */ 180 | MP4_TIME_CMP_GT_EQ, /* Greater than or equal */ 181 | }; 182 | 183 | 184 | enum mp4_mux_meta_storage { 185 | /* Stored in moov/meta, keys/ilst format */ 186 | MP4_MUX_META_META = 0, 187 | /* Stored in moov/udta/meta, ilst only format */ 188 | MP4_MUX_META_UDTA, 189 | /* Stored in moov/udta, ilst only format */ 190 | MP4_MUX_META_UDTA_ROOT, 191 | }; 192 | 193 | 194 | struct mp4_box { 195 | uint32_t size; 196 | uint32_t type; 197 | uint64_t largesize; 198 | uint8_t uuid[16]; 199 | unsigned int level; 200 | struct mp4_box *parent; 201 | struct list_node children; 202 | 203 | struct { 204 | off_t (*func)(struct mp4_mux *mux, 205 | struct mp4_box *box, 206 | size_t maxBytes); 207 | void *args; 208 | int need_free; 209 | } writer; 210 | 211 | struct list_node node; 212 | }; 213 | 214 | 215 | struct mp4_time_to_sample_entry { 216 | uint32_t sampleCount; 217 | uint32_t sampleDelta; 218 | }; 219 | 220 | 221 | struct mp4_sample_to_chunk_entry { 222 | uint32_t firstChunk; 223 | uint32_t samplesPerChunk; 224 | uint32_t sampleDescriptionIndex; 225 | }; 226 | 227 | 228 | /* track structure used by demuxer */ 229 | struct mp4_track { 230 | uint32_t id; 231 | enum mp4_track_type type; 232 | uint32_t timescale; 233 | uint64_t duration; 234 | uint64_t creationTime; 235 | uint64_t modificationTime; 236 | uint32_t nextSample; 237 | uint64_t pendingSeekTime; 238 | uint32_t sampleCount; 239 | uint32_t *sampleSize; 240 | uint32_t sampleMaxSize; 241 | uint64_t *sampleDecodingTime; 242 | uint64_t *sampleOffset; 243 | uint32_t chunkCount; 244 | uint64_t *chunkOffset; 245 | uint32_t timeToSampleEntryCount; 246 | struct mp4_time_to_sample_entry *timeToSampleEntries; 247 | uint32_t sampleToChunkEntryCount; 248 | struct mp4_sample_to_chunk_entry *sampleToChunkEntries; 249 | uint32_t syncSampleEntryCount; 250 | uint32_t *syncSampleEntries; 251 | uint32_t referenceType; 252 | uint32_t referenceTrackId[MP4_TRACK_REF_MAX]; 253 | unsigned int referenceTrackIdCount; 254 | 255 | struct mp4_video_decoder_config vdc; 256 | 257 | enum mp4_audio_codec audioCodec; 258 | uint32_t audioChannelCount; 259 | uint32_t audioSampleSize; 260 | uint32_t audioSampleRate; 261 | uint32_t audioSpecificConfigSize; 262 | uint8_t *audioSpecificConfig; 263 | 264 | char *contentEncoding; 265 | char *mimeFormat; 266 | unsigned int staticMetadataCount; 267 | char **staticMetadataKey; 268 | char **staticMetadataValue; 269 | 270 | struct mp4_track *metadata; 271 | struct mp4_track *chapters; 272 | 273 | char *name; 274 | int enabled; 275 | int in_movie; 276 | int in_preview; 277 | 278 | struct list_node node; 279 | }; 280 | 281 | 282 | struct mp4_file { 283 | int fd; 284 | off_t fileSize; 285 | off_t readBytes; 286 | struct mp4_box *root; 287 | struct list_node tracks; 288 | unsigned int trackCount; 289 | uint32_t timescale; 290 | uint64_t duration; 291 | uint64_t creationTime; 292 | uint64_t modificationTime; 293 | 294 | char *chaptersName[MP4_CHAPTERS_MAX]; 295 | uint64_t chaptersTime[MP4_CHAPTERS_MAX]; 296 | unsigned int chaptersCount; 297 | unsigned int finalMetadataCount; 298 | char **finalMetadataKey; 299 | char **finalMetadataValue; 300 | char *udtaLocationKey; 301 | char *udtaLocationValue; 302 | off_t finalCoverOffset; 303 | uint32_t finalCoverSize; 304 | enum mp4_metadata_cover_type finalCoverType; 305 | 306 | off_t udtaCoverOffset; 307 | uint32_t udtaCoverSize; 308 | enum mp4_metadata_cover_type udtaCoverType; 309 | off_t metaCoverOffset; 310 | uint32_t metaCoverSize; 311 | enum mp4_metadata_cover_type metaCoverType; 312 | 313 | unsigned int udtaMetadataCount; 314 | unsigned int udtaMetadataParseIdx; 315 | char **udtaMetadataKey; 316 | char **udtaMetadataValue; 317 | unsigned int metaMetadataCount; 318 | char **metaMetadataKey; 319 | char **metaMetadataValue; 320 | }; 321 | 322 | 323 | struct mp4_demux { 324 | struct mp4_file mp4; 325 | }; 326 | 327 | 328 | struct mp4_mux_metadata_info { 329 | struct list_node *metadatas; 330 | uint8_t *cover; 331 | enum mp4_metadata_cover_type cover_type; 332 | size_t cover_size; 333 | }; 334 | 335 | 336 | /* track structure used by muxer */ 337 | struct mp4_mux_track { 338 | /* Opaque handle used to identify the track. */ 339 | uint32_t handle; 340 | /* Unset: track IDs are computed when writing to the MP4 file to ensure 341 | * that enabled tracks have lowest IDs. */ 342 | uint32_t id; 343 | char *name; 344 | uint32_t flags; 345 | uint32_t referenceTrackHandle[MP4_TRACK_REF_MAX]; 346 | size_t referenceTrackHandleCount; 347 | uint32_t ref; 348 | int has_ref; 349 | enum mp4_track_type type; 350 | uint32_t timescale; 351 | uint64_t duration; 352 | uint64_t duration_moov; 353 | uint64_t creation_time; 354 | uint64_t modification_time; 355 | struct { 356 | uint32_t count; 357 | uint32_t capacity; 358 | uint32_t *sizes; 359 | uint64_t *decoding_times; 360 | uint64_t *offsets; 361 | } samples; 362 | struct { 363 | uint32_t count; 364 | uint32_t capacity; 365 | uint64_t *offsets; 366 | } chunks; 367 | struct { 368 | uint32_t count; 369 | uint32_t capacity; 370 | struct mp4_time_to_sample_entry *entries; 371 | } time_to_sample; 372 | struct { 373 | uint32_t count; 374 | uint32_t capacity; 375 | struct mp4_sample_to_chunk_entry *entries; 376 | } sample_to_chunk; 377 | struct { 378 | uint32_t count; 379 | uint32_t capacity; 380 | uint32_t *entries; 381 | } sync; 382 | struct { 383 | uint32_t samples; 384 | uint32_t chunks; 385 | uint32_t time_to_sample; 386 | uint32_t sample_to_chunk; 387 | uint32_t sync; 388 | } stbl_index_write_count; 389 | bool track_info_written; 390 | uint32_t meta_write_count; 391 | 392 | union { 393 | struct mp4_video_decoder_config video; 394 | struct { 395 | enum mp4_audio_codec codec; 396 | uint32_t channel_count; 397 | uint32_t sample_size; 398 | uint32_t sample_rate; 399 | uint32_t specific_config_size; 400 | uint8_t *specific_config; 401 | } audio; 402 | struct { 403 | char *content_encoding; 404 | char *mime_type; 405 | } metadata; 406 | }; 407 | 408 | struct mp4_mux_metadata_info track_metadata; 409 | 410 | struct list_node metadatas; 411 | struct list_node node; 412 | }; 413 | 414 | struct mp4_mux_metadata { 415 | char *key; 416 | char *value; 417 | enum mp4_mux_meta_storage storage; 418 | 419 | struct list_node node; 420 | }; 421 | 422 | struct mp4_mux { 423 | int fd; 424 | char *filename; 425 | struct { 426 | char *link_file; 427 | int fd_link; 428 | char *tables_file; 429 | char *tmp_tables_file; 430 | int fd_tables; 431 | bool failed_in_close; 432 | bool enabled; 433 | uint32_t meta_write_count; 434 | bool thumb_written; 435 | } recovery; 436 | uint64_t duration; 437 | uint64_t creation_time; 438 | uint64_t modification_time; 439 | uint32_t timescale; 440 | off_t data_offset; 441 | off_t boxes_offset; 442 | /* Tracks */ 443 | struct list_node tracks; 444 | uint32_t track_count; 445 | /* Metadata */ 446 | struct list_node metadatas; 447 | struct mp4_mux_metadata_info file_metadata; 448 | bool max_tables_size_reached; 449 | struct { 450 | uint8_t *buf; 451 | off_t offset; 452 | off_t buf_size; 453 | } tables; 454 | }; 455 | 456 | 457 | #define OFF_T_TO_ERRNO(_off, default_errno) \ 458 | ((_off >= INT_MIN) ? (int)_off : -default_errno) 459 | 460 | 461 | #define MP4_READ_32(_file, _val32, _readBytes) \ 462 | do { \ 463 | _val32 = 0; \ 464 | ssize_t _count = read(_file, &_val32, sizeof(uint32_t)); \ 465 | if (_count == -1) { \ 466 | ULOG_ERRNO("read", errno); \ 467 | return -errno; \ 468 | } else if ((size_t)_count != sizeof(uint32_t)) { \ 469 | ULOG_ERRNO("only %zd bytes read instead of %zu", \ 470 | EIO, \ 471 | _count, \ 472 | sizeof(uint32_t)); \ 473 | return -EIO; \ 474 | } \ 475 | _readBytes += sizeof(uint32_t); \ 476 | } while (0) 477 | 478 | 479 | #define MP4_READ_16(_file, _val16, _readBytes) \ 480 | do { \ 481 | _val16 = 0; \ 482 | ssize_t _count = read(_file, &_val16, sizeof(uint16_t)); \ 483 | if (_count == -1) { \ 484 | ULOG_ERRNO("read", errno); \ 485 | return -errno; \ 486 | } else if ((size_t)_count != sizeof(uint16_t)) { \ 487 | ULOG_ERRNO("only %zd bytes read instead of %zu", \ 488 | EIO, \ 489 | _count, \ 490 | sizeof(uint16_t)); \ 491 | return -EIO; \ 492 | } \ 493 | _readBytes += sizeof(uint16_t); \ 494 | } while (0) 495 | 496 | 497 | #define MP4_READ_8(_file, _val8, _readBytes) \ 498 | do { \ 499 | _val8 = 0; \ 500 | ssize_t _count = read(_file, &_val8, sizeof(uint8_t)); \ 501 | if (_count == -1) { \ 502 | ULOG_ERRNO("read", errno); \ 503 | return -errno; \ 504 | } else if ((size_t)_count != sizeof(uint8_t)) { \ 505 | ULOG_ERRNO("only %zd bytes read instead of %zu", \ 506 | EIO, \ 507 | _count, \ 508 | sizeof(uint8_t)); \ 509 | return -EIO; \ 510 | } \ 511 | _readBytes += sizeof(uint8_t); \ 512 | } while (0) 513 | 514 | 515 | #define MP4_READ_SKIP(_file, _nBytes, _readBytes) \ 516 | do { \ 517 | __typeof__(_readBytes) _i_nBytes = _nBytes; \ 518 | if (_i_nBytes > 0) { \ 519 | off_t _ret = lseek(_file, _i_nBytes, SEEK_CUR); \ 520 | if (_ret == -1) { \ 521 | ULOG_ERRNO("lseek", errno); \ 522 | return -errno; \ 523 | } \ 524 | _readBytes += _i_nBytes; \ 525 | } \ 526 | } while (0) 527 | 528 | 529 | #define MP4_WRITE_32(_mux, _val32, _writeBytes, _maxBytes) \ 530 | do { \ 531 | if (_writeBytes + sizeof(uint32_t) > _maxBytes) \ 532 | return -ENOSPC; \ 533 | void *_dst = memcpy(_mux->tables.buf + _mux->tables.offset, \ 534 | &_val32, \ 535 | sizeof(uint32_t)); \ 536 | if (_dst == NULL) { \ 537 | int _ret = -errno; \ 538 | ULOG_ERRNO("memcpy", -_ret); \ 539 | return _ret; \ 540 | } \ 541 | _mux->tables.offset += sizeof(uint32_t); \ 542 | _writeBytes += sizeof(uint32_t); \ 543 | } while (0) 544 | 545 | 546 | #define MP4_WRITE_32_INTERNAL(_mux, _val32, _writeBytes, _maxBytes, _reason) \ 547 | do { \ 548 | if (_writeBytes + sizeof(uint32_t) > _maxBytes) \ 549 | return -ENOSPC; \ 550 | ssize_t _res = write(_mux->fd, &val32, sizeof(uint32_t)); \ 551 | if (_res != sizeof(uint32_t)) { \ 552 | ULOGE("%s: write failed writing %s (%zd)", \ 553 | _reason, \ 554 | __func__, \ 555 | _res); \ 556 | return -ENOSPC; \ 557 | } \ 558 | _writeBytes += sizeof(uint32_t); \ 559 | } while (0) 560 | 561 | 562 | #define MP4_WRITE_16(_mux, _val16, _writeBytes, _maxBytes) \ 563 | do { \ 564 | if (_writeBytes + sizeof(uint16_t) > _maxBytes) \ 565 | return -ENOSPC; \ 566 | void *_dst = memcpy(_mux->tables.buf + _mux->tables.offset, \ 567 | &_val16, \ 568 | sizeof(uint16_t)); \ 569 | if (_dst == NULL) { \ 570 | int _ret = -errno; \ 571 | ULOG_ERRNO("memcpy", -_ret); \ 572 | return _ret; \ 573 | } \ 574 | _mux->tables.offset += sizeof(uint16_t); \ 575 | _writeBytes += sizeof(uint16_t); \ 576 | } while (0) 577 | 578 | 579 | #define MP4_WRITE_8(_mux, _val8, _writeBytes, _maxBytes) \ 580 | do { \ 581 | if (_writeBytes + sizeof(uint8_t) > _maxBytes) \ 582 | return -ENOSPC; \ 583 | void *_dst = memcpy(_mux->tables.buf + _mux->tables.offset, \ 584 | &_val8, \ 585 | sizeof(uint8_t)); \ 586 | if (_dst == NULL) { \ 587 | int _ret = -errno; \ 588 | ULOG_ERRNO("memcpy", -_ret); \ 589 | return _ret; \ 590 | } \ 591 | _mux->tables.offset += sizeof(uint8_t); \ 592 | _writeBytes += sizeof(uint8_t); \ 593 | } while (0) 594 | 595 | 596 | #define MP4_WRITE_SKIP(_mux, _byteCount, _writeBytes, _maxBytes) \ 597 | do { \ 598 | __typeof__(_byteCount) _i_nBytes = _byteCount; \ 599 | if (_writeBytes + _i_nBytes > _maxBytes) \ 600 | return -ENOSPC; \ 601 | if (_mux->tables.offset + (off_t)_i_nBytes > \ 602 | _mux->tables.buf_size) { \ 603 | return -EPROTO; \ 604 | } \ 605 | _mux->tables.offset += _i_nBytes; \ 606 | _writeBytes += _i_nBytes; \ 607 | } while (0) 608 | 609 | 610 | #define MP4_WRITE_SKIP_INTERNAL(_mux, _byteCount, _writeBytes, _maxBytes) \ 611 | do { \ 612 | __typeof__(_byteCount) _i_nBytes = _byteCount; \ 613 | if (_writeBytes + _i_nBytes > _maxBytes) \ 614 | return -ENOSPC; \ 615 | if (lseek(_mux->fd, _i_nBytes, SEEK_CUR) == -1) { \ 616 | ULOG_ERRNO("lseek", errno); \ 617 | return -errno; \ 618 | } \ 619 | _writeBytes += _i_nBytes; \ 620 | } while (0) 621 | 622 | 623 | #define MP4_WRITE_ZEROES(_mux, _byteCount, _writeBytes, _maxBytes) \ 624 | do { \ 625 | __typeof__(_byteCount) _i_nBytes = _byteCount; \ 626 | if (_writeBytes + _i_nBytes > _maxBytes) \ 627 | return -ENOSPC; \ 628 | if (_mux->tables.offset + (off_t)_i_nBytes > \ 629 | _mux->tables.buf_size) { \ 630 | return -EPROTO; \ 631 | } \ 632 | memset(_mux->tables.buf + _mux->tables.offset, \ 633 | 0, \ 634 | (size_t)_i_nBytes); \ 635 | _mux->tables.offset += _i_nBytes; \ 636 | _writeBytes += _i_nBytes; \ 637 | } while (0) 638 | 639 | 640 | #define MP4_WRITE_CHECK_SIZE(_mux, _computedSize, _actualSize) \ 641 | do { \ 642 | if (_computedSize != _actualSize) { \ 643 | uint32_t _size32 = htonl(_actualSize); \ 644 | if (_computedSize != 0) \ 645 | ULOGE("bad size in box (%zu instead of %zu)," \ 646 | " fixing size", \ 647 | (size_t)_actualSize, \ 648 | (size_t)_computedSize); \ 649 | if (_mux->tables.offset < _actualSize) { \ 650 | ULOGE("tables offset too small"); \ 651 | return -EPROTO; \ 652 | } \ 653 | _mux->tables.offset -= _actualSize; \ 654 | void *_dst = \ 655 | memcpy(_mux->tables.buf + _mux->tables.offset, \ 656 | &_size32, \ 657 | sizeof(uint32_t)); \ 658 | if (_dst == NULL) { \ 659 | int _ret = -errno; \ 660 | ULOG_ERRNO("memcpy", -_ret); \ 661 | return _ret; \ 662 | } \ 663 | _mux->tables.offset += _actualSize; \ 664 | } \ 665 | } while (0) 666 | 667 | 668 | static inline char *xstrdup(const char *s) 669 | { 670 | return s == NULL ? NULL : strdup(s); 671 | } 672 | 673 | 674 | struct mp4_box *mp4_box_new(struct mp4_box *parent); 675 | 676 | 677 | /* mp4_box for mux creation */ 678 | struct mp4_box *mp4_box_new_container(struct mp4_box *parent, uint32_t type); 679 | struct mp4_box *mp4_box_new_mvhd(struct mp4_box *parent, struct mp4_mux *mux); 680 | struct mp4_box *mp4_box_new_tkhd(struct mp4_box *parent, 681 | struct mp4_mux_track *track); 682 | struct mp4_box *mp4_box_new_cdsc(struct mp4_box *parent, 683 | struct mp4_mux_track *track); 684 | struct mp4_box *mp4_box_new_chap(struct mp4_box *parent, 685 | struct mp4_mux_track *track); 686 | struct mp4_box *mp4_box_new_mdhd(struct mp4_box *parent, 687 | struct mp4_mux_track *track); 688 | struct mp4_box *mp4_box_new_hdlr(struct mp4_box *parent, 689 | struct mp4_mux_track *track); 690 | struct mp4_box *mp4_box_new_vmhd(struct mp4_box *parent, 691 | struct mp4_mux_track *track); 692 | struct mp4_box *mp4_box_new_smhd(struct mp4_box *parent, 693 | struct mp4_mux_track *track); 694 | struct mp4_box *mp4_box_new_nmhd(struct mp4_box *parent, 695 | struct mp4_mux_track *track); 696 | struct mp4_box *mp4_box_new_gmhd(struct mp4_box *parent, 697 | struct mp4_mux_track *track); 698 | struct mp4_box *mp4_box_new_dref(struct mp4_box *parent, 699 | struct mp4_mux_track *track); 700 | struct mp4_box *mp4_box_new_stsd(struct mp4_box *parent, 701 | struct mp4_mux_track *track); 702 | struct mp4_box *mp4_box_new_stts(struct mp4_box *parent, 703 | struct mp4_mux_track *track); 704 | struct mp4_box *mp4_box_new_stss(struct mp4_box *parent, 705 | struct mp4_mux_track *track); 706 | struct mp4_box *mp4_box_new_stsc(struct mp4_box *parent, 707 | struct mp4_mux_track *track); 708 | struct mp4_box *mp4_box_new_stsz(struct mp4_box *parent, 709 | struct mp4_mux_track *track); 710 | struct mp4_box *mp4_box_new_stco(struct mp4_box *parent, 711 | struct mp4_mux_track *track); 712 | struct mp4_box *mp4_box_new_meta(struct mp4_box *parent, 713 | struct mp4_mux_metadata_info *meta_info); 714 | struct mp4_box *mp4_box_new_meta_udta(struct mp4_box *parent, 715 | struct mp4_mux_metadata_info *meta_info); 716 | struct mp4_box *mp4_box_new_udta_entry(struct mp4_box *parent, 717 | struct mp4_mux_metadata *meta); 718 | 719 | 720 | void mp4_box_destroy(struct mp4_box *box); 721 | 722 | 723 | void mp4_box_log(struct mp4_box *box, int level); 724 | 725 | 726 | off_t mp4_box_children_read(struct mp4_file *mp4, 727 | struct mp4_box *parent, 728 | off_t maxBytes, 729 | struct mp4_track *track); 730 | 731 | 732 | off_t mp4_box_ftyp_write(struct mp4_mux *mux); 733 | 734 | 735 | off_t mp4_box_free_write(struct mp4_mux *mux, size_t len); 736 | 737 | 738 | off_t mp4_box_mdat_write(struct mp4_mux *mux, uint64_t size); 739 | 740 | 741 | int mp4_track_is_sync_sample(struct mp4_track *track, 742 | unsigned int sampleIdx, 743 | int *prevSyncSampleIdx); 744 | 745 | 746 | int mp4_track_find_sample_by_time(struct mp4_track *track, 747 | uint64_t time, 748 | enum mp4_time_cmp cmp, 749 | int sync, 750 | int start); 751 | 752 | 753 | struct mp4_track *mp4_track_add(struct mp4_file *mp4); 754 | 755 | 756 | int mp4_track_remove(struct mp4_file *mp4, struct mp4_track *track); 757 | 758 | 759 | struct mp4_track *mp4_track_find(struct mp4_file *mp4, struct mp4_track *track); 760 | 761 | 762 | struct mp4_track *mp4_track_find_by_idx(struct mp4_file *mp4, 763 | unsigned int track_idx); 764 | 765 | 766 | struct mp4_track *mp4_track_find_by_id(struct mp4_file *mp4, 767 | unsigned int track_id); 768 | 769 | 770 | void mp4_tracks_destroy(struct mp4_file *mp4); 771 | 772 | 773 | int mp4_tracks_build(struct mp4_file *mp4); 774 | 775 | 776 | void mp4_video_decoder_config_destroy(struct mp4_video_decoder_config *vdc); 777 | 778 | 779 | int mp4_prepare_link_file(int fd_link_file, 780 | const char *tables_file, 781 | const char *filepath, 782 | off_t tables_size_bytes, 783 | bool check_storage_uuid); 784 | 785 | 786 | struct mp4_mux_track *mp4_mux_track_find_by_handle(struct mp4_mux *mux, 787 | uint32_t track_handle); 788 | 789 | 790 | int mp4_mux_sort_tracks(struct mp4_mux *mux); 791 | 792 | 793 | int mp4_mux_track_compute_tts(struct mp4_mux *mux, struct mp4_mux_track *track); 794 | 795 | 796 | int mp4_mux_grow_samples(struct mp4_mux_track *track, int new_samples); 797 | 798 | 799 | int mp4_mux_grow_chunks(struct mp4_mux_track *track, int new_chunks); 800 | 801 | 802 | int mp4_mux_grow_tts(struct mp4_mux_track *track, int new_tts); 803 | 804 | 805 | int mp4_mux_grow_stc(struct mp4_mux_track *track, int new_stc); 806 | 807 | 808 | int mp4_mux_grow_sync(struct mp4_mux_track *track, int new_sync); 809 | 810 | 811 | int mp4_mux_incremental_sync(struct mp4_mux *mux); 812 | 813 | 814 | int mp4_mux_fill_from_file(const char *file_path, 815 | struct mp4_mux *mux, 816 | char **error_msg); 817 | 818 | 819 | #endif /* !_MP4_H_ */ 820 | -------------------------------------------------------------------------------- /src/mp4_recovery.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_priv.h" 29 | #if BUILD_UTIL_LINUX_NG 30 | # include 31 | # include 32 | #endif /* BUILD_UTIL_LINUX_NG */ 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #define MP4_MUX_TABLES_GROW_SIZE 128 41 | #define MS_TO_S 1000 42 | #define SECONDS_IN_MONTH 267840 43 | #define FTYP_SIZE 32 44 | #define DEFAULT_UUID_MSG "DON'T CHECK UUID" 45 | 46 | #ifndef PATH_MAX 47 | # define PATH_MAX 4096 48 | #endif 49 | 50 | #ifdef WIN32 51 | # include 52 | # define F_OK 0 53 | # define access _access 54 | #else 55 | # include 56 | #endif 57 | 58 | static const uint32_t recovery_version = 2; 59 | 60 | /* recovery version 1: moov atom entirely written in mrf file. Not supported 61 | * anymore */ 62 | /* recovery version 2: allows incremental tables */ 63 | 64 | #ifdef _WIN32 65 | static ssize_t getline(char **lineptr, size_t *n, FILE *stream) 66 | { 67 | char *bufptr = NULL; 68 | char *p = bufptr; 69 | size_t size; 70 | int c; 71 | 72 | if (lineptr == NULL || stream == NULL || n == NULL) 73 | return -EINVAL; 74 | 75 | bufptr = *lineptr; 76 | size = *n; 77 | 78 | c = fgetc(stream); 79 | if (c == EOF) 80 | return -1; 81 | if (bufptr == NULL) { 82 | bufptr = malloc(128); 83 | if (bufptr == NULL) 84 | return -1; 85 | size = 128; 86 | } 87 | p = bufptr; 88 | while (c != EOF) { 89 | if ((size_t)(p - bufptr) > (size_t)(size - 1)) { 90 | size = size + 128; 91 | bufptr = realloc(bufptr, size); 92 | if (bufptr == NULL) 93 | return -1; 94 | } 95 | *p++ = c; 96 | if (c == '\n') 97 | break; 98 | c = fgetc(stream); 99 | } 100 | *p++ = '\0'; 101 | *lineptr = bufptr; 102 | *n = size; 103 | 104 | return p - bufptr - 1; 105 | } 106 | #endif 107 | 108 | 109 | static char *get_mnt_fsname(const char *path) 110 | { 111 | #if BUILD_UTIL_LINUX_NG 112 | FILE *fstab = setmntent("/etc/mtab", "r"); 113 | struct mntent *e; 114 | char *devname = NULL; 115 | size_t length_mnt = 0; 116 | size_t curr_len = 0; 117 | 118 | if (fstab == NULL || path == NULL) 119 | goto out; 120 | 121 | while ((e = getmntent(fstab))) { 122 | curr_len = strlen(e->mnt_dir); 123 | if (curr_len > length_mnt) { 124 | if (strncmp(path, e->mnt_dir, curr_len) == 0) { 125 | if (devname) 126 | free(devname); 127 | devname = strdup(e->mnt_fsname); 128 | length_mnt = curr_len; 129 | } 130 | } 131 | } 132 | out: 133 | endmntent(fstab); 134 | return devname; 135 | #else 136 | return NULL; 137 | #endif 138 | } 139 | 140 | 141 | static char *get_uuid_from_mnt_fsname(const char *path) 142 | { 143 | #if BUILD_UTIL_LINUX_NG 144 | blkid_probe pr = NULL; 145 | const char *uuid; 146 | char *ret = NULL; 147 | int err = 0; 148 | 149 | if (path == NULL) 150 | goto out; 151 | pr = blkid_new_probe_from_filename(path); 152 | if (pr == NULL) 153 | goto out; 154 | err = blkid_do_probe(pr); 155 | if (err < 0) { 156 | ULOGE("blkid_do_probe"); 157 | goto out; 158 | } 159 | err = blkid_probe_lookup_value(pr, "UUID", &uuid, NULL); 160 | if (err < 0) { 161 | ULOGE("blkid_probe_lookup_value"); 162 | goto out; 163 | } 164 | ret = strdup(uuid); 165 | if (ret == NULL) 166 | ULOG_ERRNO("strdup", ENOMEM); 167 | out: 168 | if (pr) 169 | blkid_free_probe(pr); 170 | return ret; 171 | #else 172 | return NULL; 173 | #endif 174 | } 175 | 176 | 177 | int mp4_prepare_link_file(int fd_link_file, 178 | const char *tables_file, 179 | const char *filepath, 180 | off_t tables_size_bytes, 181 | bool check_storage_uuid) 182 | { 183 | int ret = 0; 184 | char bl = '\n'; 185 | ssize_t written = 0; 186 | char *str = NULL; 187 | char *fsname = NULL; 188 | char *uuid = NULL; 189 | 190 | written = asprintf(&str, "%d\n", recovery_version); 191 | if (written < 0) { 192 | ret = -ENOMEM; 193 | goto out; 194 | } 195 | written = write(fd_link_file, str, strlen(str)); 196 | if (written != (ssize_t)strlen(str)) { 197 | ret = -errno; 198 | goto out; 199 | } 200 | free(str); 201 | 202 | written = asprintf(&str, "%s\n", filepath); 203 | if (written < 0) { 204 | ret = -ENOMEM; 205 | goto out; 206 | } 207 | written = write(fd_link_file, str, strlen(str)); 208 | if (written != (ssize_t)strlen(str)) { 209 | ret = -errno; 210 | goto out; 211 | } 212 | free(str); 213 | 214 | written = asprintf(&str, "%s\n", tables_file); 215 | if (written < 0) { 216 | ret = -ENOMEM; 217 | goto out; 218 | } 219 | written = write(fd_link_file, str, strlen(str)); 220 | if (written != (ssize_t)strlen(str)) { 221 | ret = -errno; 222 | goto out; 223 | } 224 | free(str); 225 | 226 | written = asprintf(&str, "%jd\n", (intmax_t)tables_size_bytes); 227 | if (written < 0) { 228 | ret = -ENOMEM; 229 | goto out; 230 | } 231 | written = write(fd_link_file, str, strlen(str)); 232 | if (written != (ssize_t)strlen(str)) { 233 | ret = -errno; 234 | goto out; 235 | } 236 | 237 | if (check_storage_uuid) { 238 | fsname = get_mnt_fsname(filepath); 239 | if (fsname == NULL) { 240 | ULOGE("get_mnt_fsname failed (%s)", filepath); 241 | goto out; 242 | } else { 243 | uuid = get_uuid_from_mnt_fsname(fsname); 244 | if (uuid == NULL) { 245 | ULOGE("%s: get_uuid_from_mnt_fsname %s failed.", 246 | filepath, 247 | fsname); 248 | goto out; 249 | } 250 | } 251 | } else { 252 | uuid = strdup(DEFAULT_UUID_MSG); 253 | } 254 | 255 | written = write(fd_link_file, uuid, strlen(uuid)); 256 | if (written != (ssize_t)strlen(uuid)) { 257 | ret = -errno; 258 | goto out; 259 | } 260 | written = write(fd_link_file, &bl, 1); 261 | if (written != (ssize_t)1) { 262 | ret = -errno; 263 | goto out; 264 | } 265 | 266 | out: 267 | free(str); 268 | free(fsname); 269 | free(uuid); 270 | return ret; 271 | } 272 | 273 | 274 | MP4_API int mp4_recovery_parse_link_file(const char *link_file, 275 | struct link_file_info *info) 276 | { 277 | int ret = 0; 278 | ssize_t read_char = 0; 279 | FILE *f_link = NULL; 280 | char *curr_recovery_version = NULL; 281 | char *tables_size_b = NULL; 282 | size_t len = 0; 283 | char *end_ptr = NULL; 284 | unsigned long parse_long; 285 | 286 | ULOG_ERRNO_RETURN_ERR_IF(link_file == NULL, EINVAL); 287 | ULOG_ERRNO_RETURN_ERR_IF(info == NULL, EINVAL); 288 | 289 | memset(info, 0, sizeof(*info)); 290 | f_link = fopen(link_file, "r"); 291 | if (f_link == NULL) { 292 | ret = -errno; 293 | goto out; 294 | } 295 | 296 | /* recovery version */ 297 | if (getline(&curr_recovery_version, &len, f_link) < 0) { 298 | ret = -EINVAL; 299 | goto out; 300 | } 301 | info->recovery_version = atoi(curr_recovery_version); 302 | if (info->recovery_version != recovery_version) { 303 | ULOGE("unsupported recovery version (%d)", 304 | (int)info->recovery_version); 305 | ret = -EINVAL; 306 | goto out; 307 | } 308 | 309 | /* data_file */ 310 | read_char = getline(&info->data_file, &len, f_link); 311 | if (read_char <= 0 || info->data_file == NULL) { 312 | ULOGE("getline"); 313 | ret = -EINVAL; 314 | goto out; 315 | } 316 | info->data_file[strlen(info->data_file) - 1] = '\0'; 317 | 318 | /* tables_file */ 319 | read_char = getline(&info->tables_file, &len, f_link); 320 | if (read_char <= 0 || info->tables_file == NULL) { 321 | ULOGE("getline"); 322 | ret = -EINVAL; 323 | goto out; 324 | } 325 | info->tables_file[strlen(info->tables_file) - 1] = '\0'; 326 | 327 | /* tables size */ 328 | read_char = getline(&tables_size_b, &len, f_link); 329 | if (read_char <= 0 || tables_size_b == NULL) { 330 | ULOGE("getline"); 331 | ret = -EINVAL; 332 | goto out; 333 | } 334 | tables_size_b[strlen(tables_size_b) - 1] = '\0'; 335 | errno = 0; 336 | parse_long = strtoul(tables_size_b, &end_ptr, 0); 337 | if (errno != 0 || tables_size_b[0] == '\0' || end_ptr[0] != '\0') { 338 | ret = -errno; 339 | ULOG_ERRNO("strtoul", -ret); 340 | goto out; 341 | } 342 | if (parse_long == 0) { 343 | ret = -EINVAL; 344 | ULOGE("invalid tables size (%ld)", parse_long); 345 | goto out; 346 | } 347 | info->tables_size_b = (size_t)parse_long; 348 | 349 | /* uuid */ 350 | read_char = getline(&info->uuid, &len, f_link); 351 | if (read_char > 0 && info->uuid != NULL) { 352 | info->uuid[strlen(info->uuid) - 1] = '\0'; 353 | if (strncmp(DEFAULT_UUID_MSG, 354 | info->uuid, 355 | MIN(sizeof(DEFAULT_UUID_MSG), 356 | strlen(info->uuid))) == 0) { 357 | /* don't check storage uuid */ 358 | free(info->uuid); 359 | info->uuid = NULL; 360 | } 361 | } else { 362 | ULOGW("invalid storage uuid"); 363 | free(info->uuid); 364 | info->uuid = NULL; 365 | } 366 | 367 | out: 368 | if (info->data_file == NULL || info->tables_file == NULL) 369 | ret = -EINVAL; 370 | if (f_link != NULL) { 371 | ret = fclose(f_link); 372 | if (ret < 0) { 373 | ret = -errno; 374 | ULOG_ERRNO("fclose", -ret); 375 | return ret; 376 | } 377 | } 378 | free(curr_recovery_version); 379 | free(tables_size_b); 380 | if (ret < 0) 381 | (void)mp4_recovery_link_file_info_destroy(info); 382 | return ret; 383 | } 384 | 385 | 386 | MP4_API int mp4_recovery_link_file_info_destroy(struct link_file_info *info) 387 | { 388 | ULOG_ERRNO_RETURN_ERR_IF(info == NULL, EINVAL); 389 | 390 | free(info->tables_file); 391 | info->tables_file = NULL; 392 | 393 | free(info->data_file); 394 | info->data_file = NULL; 395 | 396 | free(info->uuid); 397 | info->uuid = NULL; 398 | 399 | return 0; 400 | } 401 | 402 | 403 | MP4_API int mp4_recovery_finalize(const char *link_file, bool truncate_file) 404 | { 405 | struct link_file_info info = {0}; 406 | int ret = 0; 407 | int err = 0; 408 | 409 | ULOG_ERRNO_RETURN_ERR_IF(link_file == NULL, EINVAL); 410 | 411 | ret = mp4_recovery_parse_link_file(link_file, &info); 412 | if (ret < 0) { 413 | ULOG_ERRNO("mp4_recovery_parse_link_file", -ret); 414 | goto out; 415 | } 416 | 417 | if (truncate_file) { 418 | err = truncate(info.data_file, 0); 419 | if (err < 0) 420 | ULOG_ERRNO("truncate (%s)", errno, info.data_file); 421 | } 422 | 423 | err = remove(info.tables_file); 424 | if (err < 0) 425 | ULOG_ERRNO("remove (%s)", errno, info.tables_file); 426 | 427 | out: 428 | err = remove(link_file); 429 | if (err < 0) 430 | ULOG_ERRNO("remove (%s)", errno, link_file); 431 | 432 | (void)mp4_recovery_link_file_info_destroy(&info); 433 | return ret; 434 | } 435 | 436 | 437 | MP4_API int mp4_recovery_recover_file(const char *link_file, 438 | char **error_msg, 439 | char **recovered_file) 440 | { 441 | int ret = 0; 442 | char *fsname = NULL; 443 | char *uuid2 = NULL; 444 | struct mp4_mux *mux; 445 | struct mp4_mux_config config = {}; 446 | struct stat st_tables; 447 | struct stat st_data; 448 | struct link_file_info info = {0}; 449 | 450 | ULOG_ERRNO_RETURN_ERR_IF(error_msg == NULL, EINVAL); 451 | ULOG_ERRNO_RETURN_ERR_IF(link_file == NULL, EINVAL); 452 | 453 | *error_msg = NULL; 454 | if (recovered_file != NULL) 455 | *recovered_file = NULL; 456 | 457 | ret = mp4_recovery_parse_link_file(link_file, &info); 458 | if (ret < 0) { 459 | *error_msg = strdup("failed to parse link file"); 460 | ULOGE("%s (%s)", *error_msg, link_file); 461 | ret = -ENOENT; 462 | goto out; 463 | } 464 | 465 | if (access(info.data_file, F_OK)) { 466 | *error_msg = strdup("failed to find data file"); 467 | ULOGE("%s (%s)", *error_msg, info.data_file); 468 | ret = -ENOENT; 469 | goto out; 470 | } 471 | 472 | if (info.uuid != NULL) { 473 | fsname = get_mnt_fsname(info.data_file); 474 | uuid2 = get_uuid_from_mnt_fsname(fsname); 475 | if (uuid2 == NULL) { 476 | *error_msg = strdup("cannot get storage UUID"); 477 | ULOGE("%s (%s)", *error_msg, info.data_file); 478 | ret = -EAGAIN; 479 | goto out; 480 | } 481 | if (strlen(uuid2) != strlen(info.uuid) || 482 | strcmp(info.uuid, uuid2) != 0) { 483 | *error_msg = strdup("storage uuid doesn't match"); 484 | ULOGE("%s (%s %s)", *error_msg, info.uuid, uuid2); 485 | ret = -EAGAIN; 486 | goto out; 487 | } 488 | } 489 | 490 | if (access(info.tables_file, F_OK)) { 491 | *error_msg = strdup("failed to find tables file"); 492 | ULOGE("%s (%s)", *error_msg, info.tables_file); 493 | ret = -ENOENT; 494 | goto out; 495 | } 496 | 497 | if (stat(info.tables_file, &st_tables) < 0) { 498 | ret = -errno; 499 | *error_msg = strdup("invalid tables file"); 500 | ULOGE("%s (%s)", *error_msg, info.tables_file); 501 | goto out; 502 | } 503 | 504 | if (stat(info.data_file, &st_data) < 0) { 505 | ret = -errno; 506 | *error_msg = strdup("invalid data file"); 507 | ULOGE("%s (%s)", *error_msg, info.data_file); 508 | goto out; 509 | } 510 | 511 | if (st_tables.st_size == 0) { 512 | /* Record was probably stopped before any sync */ 513 | *error_msg = strdup("failed to parse tables file"); 514 | ULOGE("%s (%s): empty tables file (record probably stopped" 515 | " before any sync)", 516 | *error_msg, 517 | info.tables_file); 518 | ret = -ENODATA; 519 | goto out; 520 | } 521 | 522 | ULOGI("starting recovery of file: %s " 523 | "using recovery file path: %s", 524 | info.data_file, 525 | info.tables_file); 526 | 527 | config.filename = info.data_file; 528 | config.timescale = 1000000; /* unused - can't be 0 for mux_open */ 529 | config.creation_time = 1000; /* unused - can't be 0 for mux_open */ 530 | config.modification_time = 1000; /* unused - can't be 0 for mux_open */ 531 | config.tables_size_mbytes = info.tables_size_b / 1024 / 1024; 532 | if (config.tables_size_mbytes == 0) 533 | config.tables_size_mbytes = MP4_MUX_DEFAULT_TABLE_SIZE_MB; 534 | config.recovery.link_file = NULL; 535 | config.recovery.tables_file = NULL; 536 | ret = mp4_mux_open(&config, &mux); 537 | if (ret < 0) { 538 | ULOG_ERRNO("mp4_mux_open", -ret); 539 | *error_msg = strdup("failed to open data_file"); 540 | goto out; 541 | } 542 | 543 | ret = mp4_mux_fill_from_file(info.tables_file, mux, error_msg); 544 | if (ret < 0) 545 | ULOG_ERRNO("recovery failed (%s)", -ret, *error_msg); 546 | 547 | ret = mp4_mux_close(mux); 548 | if (ret < 0) { 549 | ULOG_ERRNO("mp4_mux_close", -ret); 550 | goto out; 551 | } 552 | 553 | if (recovered_file != NULL) { 554 | *recovered_file = strdup(info.data_file); 555 | if (*recovered_file == NULL) { 556 | ret = -ENOMEM; 557 | ULOG_ERRNO("strdup", -ret); 558 | } 559 | } 560 | 561 | out: 562 | free(fsname); 563 | free(uuid2); 564 | (void)mp4_recovery_link_file_info_destroy(&info); 565 | return ret; 566 | } 567 | 568 | 569 | MP4_API int mp4_recovery_recover_file_from_paths(const char *link_file, 570 | const char *tables_file, 571 | const char *data_file, 572 | char **error_msg, 573 | char **recovered_file) 574 | { 575 | int ret = 0; 576 | struct link_file_info info = {0}; 577 | int fd_link; 578 | struct stat st_tables; 579 | struct stat st_data; 580 | 581 | ULOG_ERRNO_RETURN_ERR_IF(link_file == NULL, EINVAL); 582 | ULOG_ERRNO_RETURN_ERR_IF(tables_file == NULL, EINVAL); 583 | ULOG_ERRNO_RETURN_ERR_IF(data_file == NULL, EINVAL); 584 | ULOG_ERRNO_RETURN_ERR_IF(error_msg == NULL, EINVAL); 585 | 586 | fd_link = open(link_file, O_WRONLY | O_CREAT, 0600); 587 | if (fd_link < 0) { 588 | ret = -errno; 589 | ULOG_ERRNO("open:'%s'", -ret, link_file); 590 | return ret; 591 | } 592 | 593 | if (stat(tables_file, &st_tables) < 0) { 594 | ret = -errno; 595 | *error_msg = strdup("invalid tables file"); 596 | ULOGE("%s (%s)", *error_msg, tables_file); 597 | goto error; 598 | } 599 | 600 | if (stat(data_file, &st_data) < 0) { 601 | ret = -errno; 602 | *error_msg = strdup("invalid data file"); 603 | ULOGE("%s (%s)", *error_msg, data_file); 604 | goto error; 605 | } 606 | 607 | ret = mp4_recovery_parse_link_file(link_file, &info); 608 | if (ret < 0) { 609 | *error_msg = strdup("failed to parse link file"); 610 | ULOGE("%s (%s)", *error_msg, link_file); 611 | ret = -ENOENT; 612 | goto error; 613 | } 614 | 615 | ret = mp4_prepare_link_file( 616 | fd_link, tables_file, data_file, info.tables_size_b, false); 617 | if (ret < 0) { 618 | ULOG_ERRNO("mp4_prepare_link_file", -ret); 619 | goto error; 620 | } 621 | close(fd_link); 622 | (void)mp4_recovery_link_file_info_destroy(&info); 623 | return mp4_recovery_recover_file(link_file, error_msg, recovered_file); 624 | 625 | error: 626 | close(fd_link); 627 | return ret; 628 | } 629 | -------------------------------------------------------------------------------- /src/mp4_recovery_reader.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Parrot Drones SAS 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * * Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * * Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * * Neither the name of the copyright holders nor the names of its 12 | * contributors may be used to endorse or promote products derived from 13 | * this software without specific prior written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "mp4_priv.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | #define MAX_ITEM_NUMBER 1000000 34 | 35 | #define RECOVERY_READ_VAL(_val) \ 36 | do { \ 37 | err = read(file_fd, &_val, sizeof(_val)); \ 38 | if (err < 0) { \ 39 | ret = -errno; \ 40 | ULOG_ERRNO("read", errno); \ 41 | goto out; \ 42 | } else if ((size_t)err != sizeof(_val)) { \ 43 | ret = -ENODATA; \ 44 | ULOG_ERRNO("read", -ret); \ 45 | goto out; \ 46 | } \ 47 | } while (0) 48 | 49 | 50 | #define RECOVERY_READ_ARR(_val, _size) \ 51 | do { \ 52 | RECOVERY_READ_VAL(_size); \ 53 | if (_size > sizeof(_val)) { \ 54 | ret = -EPROTO; \ 55 | ULOGE("'%s': read size (%zu) exceeds " \ 56 | "size (%zu)", \ 57 | #_val, \ 58 | (size_t)_size, \ 59 | sizeof(_val)); \ 60 | goto out; \ 61 | } \ 62 | if (_size == 0) \ 63 | break; \ 64 | err = read(file_fd, _val, _size); \ 65 | if (err < 0) { \ 66 | ret = -errno; \ 67 | ULOG_ERRNO("read", errno); \ 68 | goto out; \ 69 | } else if ((size_t)err != _size) { \ 70 | ret = -ENODATA; \ 71 | ULOG_ERRNO("read", -ret); \ 72 | goto out; \ 73 | } \ 74 | } while (0) 75 | 76 | 77 | #define RECOVERY_READ_PTR(_val, _size) \ 78 | do { \ 79 | RECOVERY_READ_VAL(_size); \ 80 | if (_size > MAX_ALLOC_SIZE) { \ 81 | ret = -EPROTO; \ 82 | ULOGE("'%s': read size (%zu) exceeds " \ 83 | "maximum allocation size (%zu)", \ 84 | #_val, \ 85 | (size_t)_size, \ 86 | (size_t)MAX_ALLOC_SIZE); \ 87 | goto out; \ 88 | } \ 89 | free(_val); \ 90 | _val = calloc(_size, 1); \ 91 | if (_val == NULL) { \ 92 | ret = -ENOMEM; \ 93 | ULOG_ERRNO("calloc", -ret); \ 94 | goto out; \ 95 | } \ 96 | err = read(file_fd, _val, _size); \ 97 | if (err < 0) { \ 98 | free(_val); \ 99 | _val = NULL; \ 100 | ret = -errno; \ 101 | ULOG_ERRNO("read", errno); \ 102 | goto out; \ 103 | } else if ((size_t)err != _size) { \ 104 | free(_val); \ 105 | _val = NULL; \ 106 | ret = -ENODATA; \ 107 | ULOG_ERRNO("read", -ret); \ 108 | goto out; \ 109 | } \ 110 | } while (0) 111 | 112 | 113 | #define RECOVERY_READ_STR(_val, _size) \ 114 | do { \ 115 | RECOVERY_READ_VAL(_size); \ 116 | if (_size > MAX_ALLOC_SIZE) { \ 117 | ret = -EPROTO; \ 118 | ULOGE("'%s': read size (%zu) exceeds " \ 119 | "maximum allocation size (%zu)", \ 120 | #_val, \ 121 | (size_t)_size, \ 122 | (size_t)MAX_ALLOC_SIZE); \ 123 | goto out; \ 124 | } \ 125 | free(_val); \ 126 | _val = calloc(_size + 1, 1); \ 127 | if (_val == NULL) { \ 128 | ret = -ENOMEM; \ 129 | ULOG_ERRNO("calloc", -ret); \ 130 | goto out; \ 131 | } \ 132 | err = read(file_fd, _val, _size); \ 133 | if (err < 0) { \ 134 | free(_val); \ 135 | _val = NULL; \ 136 | ret = -errno; \ 137 | ULOG_ERRNO("read", errno); \ 138 | goto out; \ 139 | } else if ((size_t)err != _size) { \ 140 | free(_val); \ 141 | _val = NULL; \ 142 | ret = -ENODATA; \ 143 | ULOG_ERRNO("read", -ret); \ 144 | goto out; \ 145 | } \ 146 | _val[_size] = '\0'; \ 147 | } while (0) 148 | 149 | 150 | struct recovery_box_info { 151 | /* track id or 0 if parent is not a track */ 152 | uint32_t track_handle; 153 | /* MP4 box type */ 154 | uint32_t type; 155 | /* number of elements to read */ 156 | uint32_t number; 157 | }; 158 | 159 | 160 | static int mp4_mux_recovery_read_stsc(int file_fd, 161 | struct mp4_mux *mux, 162 | const struct recovery_box_info *item) 163 | { 164 | int ret = 0; 165 | ssize_t err = 0; 166 | struct mp4_mux_track *track; 167 | struct mp4_sample_to_chunk_entry entry; 168 | 169 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 170 | if (track == NULL) { 171 | ret = -ENOENT; 172 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 173 | return ret; 174 | } 175 | 176 | for (size_t i = 0; i < item->number; i++) { 177 | RECOVERY_READ_VAL(entry.firstChunk); 178 | RECOVERY_READ_VAL(entry.samplesPerChunk); 179 | RECOVERY_READ_VAL(entry.sampleDescriptionIndex); 180 | 181 | if (track->sample_to_chunk.count + 1 > 182 | track->sample_to_chunk.capacity) { 183 | ret = mp4_mux_grow_stc(track, 1); 184 | if (ret < 0) { 185 | ULOG_ERRNO("mp4_mux_grow_stc", -ret); 186 | goto out; 187 | } 188 | track->sample_to_chunk.count++; 189 | } 190 | track->sample_to_chunk 191 | .entries[track->sample_to_chunk.count - 1] = entry; 192 | } 193 | 194 | out: 195 | return ret; 196 | } 197 | 198 | 199 | static int mp4_mux_recovery_read_stsz(int file_fd, 200 | struct mp4_mux *mux, 201 | const struct recovery_box_info *item) 202 | { 203 | int ret = 0; 204 | ssize_t err = 0; 205 | struct mp4_mux_track *track; 206 | uint32_t sample_size; 207 | uint64_t sample_offset; 208 | uint64_t sample_decoding_time; 209 | 210 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 211 | if (track == NULL) { 212 | ret = -ENOENT; 213 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 214 | return ret; 215 | } 216 | 217 | for (size_t i = 0; i < item->number; i++) { 218 | RECOVERY_READ_VAL(sample_size); 219 | RECOVERY_READ_VAL(sample_offset); 220 | RECOVERY_READ_VAL(sample_decoding_time); 221 | 222 | if (track->samples.count + 1 > track->samples.capacity) { 223 | ret = mp4_mux_grow_samples(track, 1); 224 | if (ret < 0) { 225 | ULOG_ERRNO("mp4_mux_grow_samples", -ret); 226 | goto out; 227 | } 228 | } 229 | 230 | track->samples.sizes[track->samples.count] = sample_size; 231 | track->samples.offsets[track->samples.count] = sample_offset; 232 | track->samples.decoding_times[track->samples.count] = 233 | sample_decoding_time; 234 | track->samples.count++; 235 | } 236 | 237 | out: 238 | return ret; 239 | } 240 | 241 | 242 | static int 243 | mp4_mux_recovery_read_audio_specific_config(int file_fd, 244 | struct mp4_mux *mux, 245 | struct mp4_mux_track *track) 246 | { 247 | int ret = 0; 248 | ssize_t err = 0; 249 | uint32_t val32; 250 | 251 | RECOVERY_READ_VAL(val32); 252 | track->audio.codec = (enum mp4_audio_codec)val32; 253 | 254 | RECOVERY_READ_PTR(track->audio.specific_config, 255 | track->audio.specific_config_size); 256 | 257 | RECOVERY_READ_VAL(track->audio.channel_count); 258 | 259 | RECOVERY_READ_VAL(track->audio.sample_size); 260 | 261 | RECOVERY_READ_VAL(track->audio.sample_rate); 262 | 263 | out: 264 | return ret; 265 | } 266 | 267 | 268 | static int mp4_mux_recovery_read_vdec(int file_fd, 269 | struct mp4_mux *mux, 270 | struct mp4_mux_track *track) 271 | { 272 | int ret = 0; 273 | ssize_t err = 0; 274 | uint32_t codec; 275 | 276 | RECOVERY_READ_VAL(codec); 277 | 278 | switch (codec) { 279 | case MP4_AVC1: 280 | track->video.codec = MP4_VIDEO_CODEC_AVC; 281 | RECOVERY_READ_PTR(track->video.avc.sps, 282 | track->video.avc.sps_size); 283 | RECOVERY_READ_PTR(track->video.avc.pps, 284 | track->video.avc.pps_size); 285 | break; 286 | case MP4_HVC1: 287 | track->video.codec = MP4_VIDEO_CODEC_HEVC; 288 | RECOVERY_READ_PTR(track->video.hevc.sps, 289 | track->video.hevc.sps_size); 290 | RECOVERY_READ_PTR(track->video.hevc.pps, 291 | track->video.hevc.pps_size); 292 | RECOVERY_READ_PTR(track->video.hevc.vps, 293 | track->video.hevc.vps_size); 294 | break; 295 | default: 296 | ULOGE("invalid video codec %d", codec); 297 | ret = -EINVAL; 298 | goto out; 299 | } 300 | RECOVERY_READ_VAL(track->video.width); 301 | RECOVERY_READ_VAL(track->video.height); 302 | 303 | out: 304 | return ret; 305 | } 306 | 307 | 308 | static int mp4_mux_recovery_read_metadata_stsd(int file_fd, 309 | struct mp4_mux *mux, 310 | struct mp4_mux_track *track) 311 | { 312 | int ret = 0; 313 | ssize_t err = 0; 314 | uint32_t encoding_len = 0; 315 | uint32_t mime_len = 0; 316 | char *content_encoding = NULL; 317 | char *mime_type = NULL; 318 | 319 | /* content encoding */ 320 | RECOVERY_READ_STR(content_encoding, encoding_len); 321 | 322 | /* mime format */ 323 | RECOVERY_READ_STR(mime_type, mime_len); 324 | 325 | ret = mp4_mux_track_set_metadata_mime_type( 326 | mux, track->handle, content_encoding, mime_type); 327 | if (ret < 0) 328 | ULOG_ERRNO("mp4_mux_track_set_metadata_mime_type", -ret); 329 | 330 | out: 331 | free(content_encoding); 332 | free(mime_type); 333 | return ret; 334 | } 335 | 336 | 337 | static int mp4_mux_recovery_read_stsd(int file_fd, 338 | struct mp4_mux *mux, 339 | const struct recovery_box_info *item) 340 | { 341 | int ret = 0; 342 | struct mp4_mux_track *track; 343 | 344 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 345 | if (track == NULL) { 346 | ret = -ENOENT; 347 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 348 | return ret; 349 | } 350 | 351 | switch (track->type) { 352 | case MP4_TRACK_TYPE_VIDEO: 353 | ret = mp4_mux_recovery_read_vdec(file_fd, mux, track); 354 | if (ret < 0) { 355 | ULOG_ERRNO("mp4_mux_recovery_read_vdec", -ret); 356 | goto out; 357 | } 358 | break; 359 | case MP4_TRACK_TYPE_AUDIO: 360 | ret = mp4_mux_recovery_read_audio_specific_config( 361 | file_fd, mux, track); 362 | if (ret < 0) { 363 | ULOG_ERRNO( 364 | "mp4_mux_recovery_read_audio_specific_config", 365 | -ret); 366 | goto out; 367 | } 368 | break; 369 | case MP4_TRACK_TYPE_METADATA: 370 | ret = mp4_mux_recovery_read_metadata_stsd(file_fd, mux, track); 371 | if (ret < 0) { 372 | ULOG_ERRNO("mp4_mux_recovery_read_metadata_stsd", -ret); 373 | goto out; 374 | } 375 | break; 376 | case MP4_TRACK_TYPE_CHAPTERS: 377 | break; 378 | default: 379 | return -EINVAL; 380 | } 381 | out: 382 | return ret; 383 | } 384 | 385 | 386 | static int mp4_mux_recovery_read_meta(int file_fd, 387 | struct mp4_mux *mux, 388 | const struct recovery_box_info *item) 389 | { 390 | int ret = 0; 391 | ssize_t err = 0; 392 | uint32_t val32; 393 | enum mp4_mux_meta_storage storage; 394 | char *key = NULL; 395 | char *value = NULL; 396 | 397 | /* storage */ 398 | RECOVERY_READ_VAL(val32); 399 | storage = val32; 400 | 401 | /* key */ 402 | RECOVERY_READ_STR(key, val32); 403 | 404 | /* value */ 405 | RECOVERY_READ_STR(value, val32); 406 | 407 | if (item->track_handle == 0) { 408 | ret = mp4_mux_add_file_metadata(mux, key, value); 409 | if (ret < 0) 410 | ULOG_ERRNO("mp4_mux_add_file_metadata", -ret); 411 | } else { 412 | ret = mp4_mux_add_track_metadata( 413 | mux, item->track_handle, key, value); 414 | if (ret < 0) 415 | ULOG_ERRNO("mp4_mux_add_track_metadata", -ret); 416 | } 417 | 418 | out: 419 | free(key); 420 | free(value); 421 | return ret; 422 | } 423 | 424 | 425 | static int mp4_mux_recovery_read_stss(int file_fd, 426 | struct mp4_mux *mux, 427 | const struct recovery_box_info *item) 428 | { 429 | int ret = 0; 430 | ssize_t err = 0; 431 | struct mp4_mux_track *track; 432 | uint32_t sync; 433 | 434 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 435 | if (track == NULL) { 436 | ret = -ENOENT; 437 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 438 | return ret; 439 | } 440 | 441 | for (size_t i = 0; i < item->number; i++) { 442 | RECOVERY_READ_VAL(sync); 443 | 444 | if (track->sync.count + 1 > track->sync.capacity) { 445 | ret = mp4_mux_grow_sync(track, 1); 446 | if (ret < 0) { 447 | ULOG_ERRNO("mp4_mux_grow_sync", -ret); 448 | goto out; 449 | } 450 | } 451 | 452 | track->sync.entries[track->sync.count] = sync; 453 | track->sync.count++; 454 | } 455 | out: 456 | return ret; 457 | } 458 | 459 | 460 | static int mp4_mux_recovery_read_stts(int file_fd, 461 | struct mp4_mux *mux, 462 | const struct recovery_box_info *item) 463 | { 464 | int ret = 0; 465 | ssize_t err = 0; 466 | struct mp4_mux_track *track; 467 | struct mp4_time_to_sample_entry entry; 468 | 469 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 470 | if (track == NULL) { 471 | ret = -ENOENT; 472 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 473 | return ret; 474 | } 475 | 476 | for (size_t i = 0; i < item->number; i++) { 477 | RECOVERY_READ_VAL(entry.sampleCount); 478 | RECOVERY_READ_VAL(entry.sampleDelta); 479 | 480 | if (track->time_to_sample.count + 1 > 481 | track->time_to_sample.capacity) { 482 | ret = mp4_mux_grow_tts(track, 1); 483 | if (ret < 0) { 484 | ULOG_ERRNO("mp4_mux_grow_tts", -ret); 485 | goto out; 486 | } 487 | } 488 | track->time_to_sample.entries[track->time_to_sample.count] = 489 | entry; 490 | track->time_to_sample.count++; 491 | } 492 | 493 | out: 494 | return ret; 495 | } 496 | 497 | 498 | static int mp4_mux_recovery_read_thumb(int file_fd, 499 | struct mp4_mux *mux, 500 | const struct recovery_box_info *item) 501 | { 502 | int ret = 0; 503 | ssize_t err = 0; 504 | uint32_t val32; 505 | 506 | /* cover type */ 507 | RECOVERY_READ_VAL(val32); 508 | mux->file_metadata.cover_type = (enum mp4_metadata_cover_type)val32; 509 | 510 | /* cover */ 511 | RECOVERY_READ_PTR(mux->file_metadata.cover, 512 | mux->file_metadata.cover_size); 513 | 514 | out: 515 | if (ret < 0) 516 | mux->file_metadata.cover_type = MP4_METADATA_COVER_TYPE_UNKNOWN; 517 | 518 | return ret; 519 | } 520 | 521 | 522 | static int mp4_mux_recovery_read_stco(int file_fd, 523 | struct mp4_mux *mux, 524 | const struct recovery_box_info *item) 525 | { 526 | int ret = 0; 527 | ssize_t err = 0; 528 | struct mp4_mux_track *track; 529 | uint64_t offset; 530 | 531 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 532 | if (track == NULL) { 533 | ret = -ENOENT; 534 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 535 | return ret; 536 | } 537 | 538 | for (size_t i = 0; i < item->number; i++) { 539 | /* 64 bits written whether it's co or co64 */ 540 | RECOVERY_READ_VAL(offset); 541 | 542 | if (track->chunks.count + 1 > track->chunks.capacity) { 543 | ret = mp4_mux_grow_chunks(track, 1); 544 | if (ret < 0) { 545 | ULOG_ERRNO("mp4_mux_grow_chunks", -ret); 546 | goto out; 547 | } 548 | } 549 | 550 | track->chunks.offsets[track->chunks.count] = offset; 551 | 552 | track->chunks.count++; 553 | } 554 | 555 | out: 556 | return ret; 557 | } 558 | 559 | 560 | static int mp4_mux_recovery_read_track(int file_fd, 561 | struct mp4_mux *mux, 562 | const struct recovery_box_info *item) 563 | { 564 | ssize_t err = 0; 565 | int ret = 0; 566 | struct mp4_mux_track_params params = {}; 567 | uint32_t len_name; 568 | char *name = NULL; 569 | uint32_t flags; 570 | uint64_t val64; 571 | struct mp4_mux_track *track = NULL; 572 | 573 | RECOVERY_READ_VAL(params.type); 574 | RECOVERY_READ_STR(name, len_name); 575 | RECOVERY_READ_VAL(flags); 576 | 577 | params.name = name; 578 | params.enabled = !!(flags & TRACK_FLAG_ENABLED); 579 | params.in_movie = !!(flags & TRACK_FLAG_IN_MOVIE); 580 | params.in_preview = !!(flags & TRACK_FLAG_IN_PREVIEW); 581 | 582 | RECOVERY_READ_VAL(params.timescale); 583 | 584 | /* mp4_mux_add_track adds MP4_MAC_TO_UNIX_EPOCH_OFFSET */ 585 | RECOVERY_READ_VAL(val64); 586 | if (val64 < MP4_MAC_TO_UNIX_EPOCH_OFFSET) { 587 | ret = -EPROTO; 588 | ULOG_ERRNO("creation time is invalid", -ret); 589 | goto out; 590 | } 591 | params.creation_time = val64 - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 592 | 593 | /* mp4_mux_add_track adds MP4_MAC_TO_UNIX_EPOCH_OFFSET */ 594 | RECOVERY_READ_VAL(val64); 595 | if (val64 < MP4_MAC_TO_UNIX_EPOCH_OFFSET) { 596 | ret = -EPROTO; 597 | ULOG_ERRNO("modification time is invalid", -ret); 598 | goto out; 599 | } 600 | params.modification_time = val64 - MP4_MAC_TO_UNIX_EPOCH_OFFSET; 601 | 602 | /* if track already present, only update references */ 603 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 604 | if (track == NULL) { 605 | err = mp4_mux_add_track(mux, ¶ms); 606 | if (err < 0) 607 | ULOG_ERRNO("mp4_mux_add_track", -err); 608 | 609 | track = mp4_mux_track_find_by_handle(mux, item->track_handle); 610 | if (track == NULL) { 611 | ret = -ENOENT; 612 | ULOG_ERRNO("mp4_mux_track_find_by_handle", -ret); 613 | return ret; 614 | } 615 | } 616 | RECOVERY_READ_ARR(track->referenceTrackHandle, 617 | track->referenceTrackHandleCount); 618 | 619 | out: 620 | free(name); 621 | return ret; 622 | } 623 | 624 | 625 | static const struct { 626 | uint32_t type; 627 | int (*func)(int file_fd, 628 | struct mp4_mux *mux, 629 | const struct recovery_box_info *item); 630 | bool fatal; 631 | } type_map[] = { 632 | {MP4_TRACK_BOX, &mp4_mux_recovery_read_track, true}, 633 | {MP4_DECODING_TIME_TO_SAMPLE_BOX, &mp4_mux_recovery_read_stts, false}, 634 | {MP4_SYNC_SAMPLE_BOX, &mp4_mux_recovery_read_stss, false}, 635 | {MP4_SAMPLE_TO_CHUNK_BOX, &mp4_mux_recovery_read_stsc, false}, 636 | {MP4_SAMPLE_SIZE_BOX, &mp4_mux_recovery_read_stsz, false}, 637 | {MP4_CHUNK_OFFSET_BOX, &mp4_mux_recovery_read_stco, false}, 638 | {MP4_CHUNK_OFFSET_64_BOX, &mp4_mux_recovery_read_stco, false}, 639 | {MP4_SAMPLE_DESCRIPTION_BOX, &mp4_mux_recovery_read_stsd, true}, 640 | {MP4_META_BOX, &mp4_mux_recovery_read_meta, false}, 641 | {MP4_METADATA_TAG_TYPE_COVER, &mp4_mux_recovery_read_thumb, false}, 642 | }; 643 | 644 | 645 | static int mp4_mux_recovery_read_box_info(int file_fd, 646 | struct recovery_box_info *item, 647 | struct mp4_mux *mux, 648 | bool *minor_fail) 649 | { 650 | int ret = 0; 651 | ssize_t err = 0; 652 | 653 | RECOVERY_READ_VAL(item->track_handle); 654 | RECOVERY_READ_VAL(item->type); 655 | RECOVERY_READ_VAL(item->number); 656 | 657 | for (size_t i = 0; i < ARRAY_SIZE(type_map); i++) { 658 | if (item->type != type_map[i].type) 659 | continue; 660 | if (item->number > MAX_ITEM_NUMBER) { 661 | ULOGE("item count is too big"); 662 | return -EPROTO; 663 | } 664 | ret = type_map[i].func(file_fd, mux, item); 665 | *minor_fail = !type_map[i].fatal && (ret < 0); 666 | return ret; 667 | } 668 | 669 | ULOGE("unknown box %d", item->type); 670 | return -EPROTO; 671 | 672 | out: 673 | return ret; 674 | } 675 | 676 | 677 | int mp4_mux_fill_from_file(const char *file_path, 678 | struct mp4_mux *mux, 679 | char **error_msg) 680 | { 681 | int ret = 0; 682 | int file_fd = open(file_path, O_RDONLY); 683 | ssize_t end_off; 684 | ssize_t curr_off; 685 | struct recovery_box_info item; 686 | struct mp4_mux_track *track; 687 | uint32_t resized_samples = 0; 688 | off_t end_of_file; 689 | off_t max_offset = 0; 690 | off_t tmp_offset; 691 | bool minor_fail = false; 692 | uint32_t min_count = 0; 693 | 694 | if (file_fd == -1) { 695 | ret = -errno; 696 | *error_msg = strdup("failed to open tables file"); 697 | ULOG_ERRNO("%s (%s)", errno, *error_msg, file_path); 698 | goto out; 699 | } 700 | 701 | end_of_file = lseek(mux->fd, 0, SEEK_END); 702 | if (end_of_file < 0) { 703 | ret = -errno; 704 | *error_msg = strdup("failed to parse data file"); 705 | ULOG_ERRNO("lseek: %s (%s)", errno, *error_msg, mux->filename); 706 | goto out; 707 | } 708 | 709 | end_off = lseek(file_fd, 0, SEEK_END); 710 | if (end_off <= 0) { 711 | ret = -errno; 712 | ULOG_ERRNO("lseek", -ret); 713 | *error_msg = strdup("Failed to parse tables file"); 714 | goto out; 715 | } 716 | 717 | curr_off = lseek(file_fd, 0, SEEK_SET); 718 | if (curr_off < 0) { 719 | ret = -errno; 720 | ULOG_ERRNO("lseek", -ret); 721 | goto out; 722 | } 723 | 724 | while ((curr_off + 12) < end_off) { 725 | minor_fail = false; 726 | ret = mp4_mux_recovery_read_box_info( 727 | file_fd, &item, mux, &minor_fail); 728 | if (minor_fail) { 729 | /* crashed occurred during sync but mp4 is still 730 | * recoverable */ 731 | ULOGW_ERRNO(-ret, "mp4_mux_recovery_read_box_info"); 732 | break; 733 | } else if (ret < 0) { 734 | /* mp4 will not be recoverable, quit with error */ 735 | *error_msg = strdup("Failed to parse tables file"); 736 | ULOG_ERRNO("mp4_mux_recovery_read_box_info: %s (%s)", 737 | -ret, 738 | *error_msg, 739 | file_path); 740 | goto out; 741 | } 742 | curr_off = lseek(file_fd, 0, SEEK_CUR); 743 | if (curr_off < 0) { 744 | ret = -errno; 745 | ULOG_ERRNO("lseek", -ret); 746 | goto out; 747 | } 748 | } 749 | 750 | /* remove samples referencing unexisting data */ 751 | list_walk_entry_forward(&mux->tracks, track, node) 752 | { 753 | min_count = MIN(track->chunks.count, track->samples.count); 754 | resized_samples = 0; 755 | for (size_t i = 0; i < min_count; i++) { 756 | tmp_offset = track->chunks.offsets[i] + 757 | track->samples.sizes[i]; 758 | if (tmp_offset > end_of_file) 759 | break; 760 | max_offset = MAX(max_offset, tmp_offset); 761 | resized_samples++; 762 | } 763 | track->samples.count = resized_samples; 764 | track->chunks.count = resized_samples; 765 | } 766 | 767 | /* remove unreferenced data */ 768 | ret = ftruncate(mux->fd, max_offset); 769 | if (ret < 0) { 770 | ret = -errno; 771 | *error_msg = strdup("Failed to parse data file"); 772 | ULOG_ERRNO( 773 | "ftruncate: %s (%s)", errno, *error_msg, mux->filename); 774 | } 775 | 776 | out: 777 | if (file_fd != -1) 778 | close(file_fd); 779 | return ret; 780 | } 781 | -------------------------------------------------------------------------------- /src/mp4_recovery_writer.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Parrot Drones SAS 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * * Redistributions of source code must retain the above copyright 7 | * notice, this list of conditions and the following disclaimer. 8 | * * Redistributions in binary form must reproduce the above copyright 9 | * notice, this list of conditions and the following disclaimer in the 10 | * documentation and/or other materials provided with the distribution. 11 | * * Neither the name of the copyright holders nor the names of its 12 | * contributors may be used to endorse or promote products derived from 13 | * this software without specific prior written permission. 14 | * 15 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 19 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | */ 26 | 27 | #include "mp4_priv.h" 28 | 29 | 30 | #define RECOVERY_WRITE_VAL(_val) \ 31 | do { \ 32 | err = write(mux->recovery.fd_tables, &_val, sizeof(_val)); \ 33 | if (err < 0) { \ 34 | ret = -errno; \ 35 | ULOG_ERRNO("write", errno); \ 36 | goto out; \ 37 | } else if (err != (ssize_t)sizeof(_val)) { \ 38 | ret = -ENODATA; \ 39 | ULOG_ERRNO("write", -ret); \ 40 | goto out; \ 41 | } \ 42 | } while (0) 43 | 44 | 45 | #define RECOVERY_WRITE_ARR(_val, _size) \ 46 | do { \ 47 | RECOVERY_WRITE_VAL(_size); \ 48 | if (_size == 0) \ 49 | break; \ 50 | err = write(mux->recovery.fd_tables, _val, _size); \ 51 | if (err < 0) { \ 52 | ret = -errno; \ 53 | ULOG_ERRNO("write", errno); \ 54 | goto out; \ 55 | } else if (err != (ssize_t)_size) { \ 56 | ret = -ENODATA; \ 57 | ULOG_ERRNO("write", -ret); \ 58 | goto out; \ 59 | } \ 60 | } while (0) 61 | 62 | 63 | static int mp4_mux_recovery_write_box_info(struct mp4_mux *mux, 64 | uint32_t track_handle, 65 | uint32_t type, 66 | uint32_t number) 67 | { 68 | int ret = 0; 69 | ssize_t err = 0; 70 | 71 | RECOVERY_WRITE_VAL(track_handle); 72 | RECOVERY_WRITE_VAL(type); 73 | RECOVERY_WRITE_VAL(number); 74 | 75 | out: 76 | return ret; 77 | } 78 | 79 | 80 | static int 81 | mp4_mux_recovery_write_audio_specific_config(struct mp4_mux *mux, 82 | struct mp4_mux_track *track) 83 | { 84 | uint32_t val32; 85 | int ret = 0; 86 | ssize_t err = 0; 87 | 88 | /* audio codec */ 89 | val32 = (uint32_t)track->audio.codec; 90 | RECOVERY_WRITE_VAL(val32); 91 | 92 | /* audio specific config */ 93 | val32 = track->audio.specific_config_size; 94 | RECOVERY_WRITE_ARR(track->audio.specific_config, val32); 95 | 96 | /* channel count */ 97 | val32 = track->audio.channel_count; 98 | RECOVERY_WRITE_VAL(val32); 99 | 100 | /* sample size */ 101 | val32 = track->audio.sample_size; 102 | RECOVERY_WRITE_VAL(val32); 103 | 104 | /* sample rate */ 105 | val32 = track->audio.sample_rate; 106 | RECOVERY_WRITE_VAL(val32); 107 | 108 | out: 109 | return ret; 110 | } 111 | 112 | 113 | static int mp4_mux_recovery_write_vdec(struct mp4_mux *mux, 114 | struct mp4_mux_track *track) 115 | { 116 | int ret = 0; 117 | ssize_t err = 0; 118 | uint32_t val32; 119 | 120 | val32 = track->video.codec == MP4_VIDEO_CODEC_AVC ? MP4_AVC1 : MP4_HVC1; 121 | RECOVERY_WRITE_VAL(val32); 122 | 123 | switch (track->video.codec) { 124 | case MP4_VIDEO_CODEC_AVC: 125 | RECOVERY_WRITE_ARR(track->video.avc.sps, 126 | track->video.avc.sps_size); 127 | RECOVERY_WRITE_ARR(track->video.avc.pps, 128 | track->video.avc.pps_size); 129 | break; 130 | case MP4_VIDEO_CODEC_HEVC: 131 | RECOVERY_WRITE_ARR(track->video.hevc.sps, 132 | track->video.hevc.sps_size); 133 | RECOVERY_WRITE_ARR(track->video.hevc.pps, 134 | track->video.hevc.pps_size); 135 | RECOVERY_WRITE_ARR(track->video.hevc.vps, 136 | track->video.hevc.vps_size); 137 | break; 138 | default: 139 | ULOGE("invalid video codec %d", track->video.codec); 140 | return -EINVAL; 141 | } 142 | RECOVERY_WRITE_VAL(track->video.width); 143 | RECOVERY_WRITE_VAL(track->video.height); 144 | 145 | out: 146 | return ret; 147 | } 148 | 149 | 150 | static int mp4_mux_recovery_write_metadata_stsd(struct mp4_mux *mux, 151 | struct mp4_mux_track *track) 152 | { 153 | int ret = 0; 154 | ssize_t err = 0; 155 | uint32_t encoding_len = 0; 156 | uint32_t mime_len = 0; 157 | 158 | if (track->metadata.content_encoding != NULL) 159 | encoding_len = strlen(track->metadata.content_encoding); 160 | if (track->metadata.mime_type != NULL) 161 | mime_len = strlen(track->metadata.mime_type); 162 | 163 | /* content encoding */ 164 | RECOVERY_WRITE_ARR(track->metadata.content_encoding, encoding_len); 165 | 166 | /* mime format */ 167 | RECOVERY_WRITE_ARR(track->metadata.mime_type, mime_len); 168 | 169 | out: 170 | return ret; 171 | } 172 | 173 | 174 | static int mp4_mux_recovery_write_stsd(struct mp4_mux *mux, 175 | struct mp4_mux_track *track) 176 | { 177 | int ret = 0; 178 | ssize_t err = 0; 179 | 180 | err = mp4_mux_recovery_write_box_info( 181 | mux, track->handle, MP4_SAMPLE_DESCRIPTION_BOX, 1); 182 | if (err < 0) { 183 | ret = -errno; 184 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 185 | goto out; 186 | } 187 | 188 | switch (track->type) { 189 | case MP4_TRACK_TYPE_VIDEO: 190 | ret = mp4_mux_recovery_write_vdec(mux, track); 191 | if (ret < 0) { 192 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", -ret); 193 | goto out; 194 | } 195 | break; 196 | case MP4_TRACK_TYPE_AUDIO: 197 | ret = mp4_mux_recovery_write_audio_specific_config(mux, track); 198 | if (ret < 0) { 199 | ULOG_ERRNO( 200 | "mp4_mux_recovery_write_audio_specific_config", 201 | -ret); 202 | goto out; 203 | } 204 | break; 205 | case MP4_TRACK_TYPE_METADATA: 206 | ret = mp4_mux_recovery_write_metadata_stsd(mux, track); 207 | if (ret < 0) { 208 | ULOG_ERRNO("mp4_mux_recovery_write_metadata_stsd", 209 | -ret); 210 | goto out; 211 | } 212 | break; 213 | case MP4_TRACK_TYPE_CHAPTERS: 214 | break; 215 | default: 216 | ULOGE("invalid track type %d", track->type); 217 | return -EINVAL; 218 | } 219 | 220 | out: 221 | return ret; 222 | } 223 | 224 | 225 | static int mp4_mux_recovery_write_stco(struct mp4_mux *mux, 226 | struct mp4_mux_track *track) 227 | { 228 | int ret = 0; 229 | ssize_t err = 0; 230 | bool co64 = track->chunks.offsets[track->chunks.count - 1] > UINT32_MAX; 231 | 232 | err = mp4_mux_recovery_write_box_info( 233 | mux, 234 | track->handle, 235 | co64 ? MP4_CHUNK_OFFSET_64_BOX : MP4_CHUNK_OFFSET_BOX, 236 | track->chunks.count - track->stbl_index_write_count.chunks); 237 | if (err < 0) { 238 | ret = -errno; 239 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 240 | goto out; 241 | } 242 | 243 | for (uint32_t i = track->stbl_index_write_count.chunks; 244 | i < track->chunks.count; 245 | i++) { 246 | /* 64 bits written whether it's co or co64 */ 247 | RECOVERY_WRITE_VAL(track->chunks.offsets[i]); 248 | 249 | track->stbl_index_write_count.chunks++; 250 | } 251 | 252 | out: 253 | return ret; 254 | } 255 | 256 | 257 | static int mp4_mux_recovery_write_stsz(struct mp4_mux *mux, 258 | struct mp4_mux_track *track) 259 | { 260 | int ret = 0; 261 | ssize_t err = 0; 262 | 263 | err = mp4_mux_recovery_write_box_info( 264 | mux, 265 | track->handle, 266 | MP4_SAMPLE_SIZE_BOX, 267 | track->samples.count - track->stbl_index_write_count.samples); 268 | if (err < 0) { 269 | ret = -errno; 270 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 271 | goto out; 272 | } 273 | 274 | for (uint32_t i = track->stbl_index_write_count.samples; 275 | i < track->samples.count; 276 | i++) { 277 | /* 'entry size' */ 278 | RECOVERY_WRITE_VAL(track->samples.sizes[i]); 279 | 280 | /* 'entry offset' */ 281 | RECOVERY_WRITE_VAL(track->samples.offsets[i]); 282 | 283 | /* 'entry decoding time' */ 284 | RECOVERY_WRITE_VAL(track->samples.decoding_times[i]); 285 | 286 | track->stbl_index_write_count.samples++; 287 | } 288 | 289 | out: 290 | return ret; 291 | } 292 | 293 | 294 | static int mp4_mux_recovery_write_stsc(struct mp4_mux *mux, 295 | struct mp4_mux_track *track) 296 | { 297 | uint32_t val32; 298 | int ret = 0; 299 | ssize_t err = 0; 300 | 301 | err = mp4_mux_recovery_write_box_info( 302 | mux, 303 | track->handle, 304 | MP4_SAMPLE_TO_CHUNK_BOX, 305 | track->sample_to_chunk.count - 306 | track->stbl_index_write_count.sample_to_chunk); 307 | if (err < 0) { 308 | ret = -errno; 309 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 310 | goto out; 311 | } 312 | 313 | for (uint32_t i = track->stbl_index_write_count.sample_to_chunk; 314 | i < track->sample_to_chunk.count; 315 | i++) { 316 | struct mp4_sample_to_chunk_entry *entry; 317 | entry = &track->sample_to_chunk.entries[i]; 318 | 319 | /* 'first_chunk' */ 320 | val32 = entry->firstChunk; 321 | RECOVERY_WRITE_VAL(val32); 322 | 323 | /* 'samples_per_chunk' */ 324 | val32 = entry->samplesPerChunk; 325 | RECOVERY_WRITE_VAL(val32); 326 | 327 | /* 'sample_description_id' */ 328 | val32 = entry->sampleDescriptionIndex; 329 | RECOVERY_WRITE_VAL(val32); 330 | 331 | track->stbl_index_write_count.sample_to_chunk++; 332 | } 333 | 334 | out: 335 | return ret; 336 | } 337 | 338 | 339 | static int mp4_mux_recovery_write_stss(struct mp4_mux *mux, 340 | struct mp4_mux_track *track) 341 | { 342 | uint32_t val32; 343 | int ret = 0; 344 | ssize_t err = 0; 345 | 346 | err = mp4_mux_recovery_write_box_info( 347 | mux, 348 | track->handle, 349 | MP4_SYNC_SAMPLE_BOX, 350 | track->sync.count - track->stbl_index_write_count.sync); 351 | if (err < 0) { 352 | ret = -errno; 353 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 354 | goto out; 355 | } 356 | 357 | for (uint32_t i = track->stbl_index_write_count.sync; 358 | i < track->sync.count; 359 | i++) { 360 | /* 'sample_number' */ 361 | val32 = track->sync.entries[i]; 362 | RECOVERY_WRITE_VAL(val32); 363 | 364 | track->stbl_index_write_count.sync++; 365 | } 366 | 367 | out: 368 | return ret; 369 | } 370 | 371 | 372 | static int mp4_mux_recovery_write_stts(struct mp4_mux *mux, 373 | struct mp4_mux_track *track) 374 | { 375 | uint32_t val32; 376 | int ret = 0; 377 | ssize_t err = 0; 378 | 379 | err = mp4_mux_recovery_write_box_info( 380 | mux, 381 | track->handle, 382 | MP4_DECODING_TIME_TO_SAMPLE_BOX, 383 | track->time_to_sample.count - 384 | track->stbl_index_write_count.time_to_sample); 385 | if (err < 0) { 386 | ret = -errno; 387 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 388 | goto out; 389 | } 390 | 391 | for (uint32_t i = track->stbl_index_write_count.time_to_sample; 392 | i < track->time_to_sample.count; 393 | i++) { 394 | struct mp4_time_to_sample_entry *entry; 395 | entry = &track->time_to_sample.entries[i]; 396 | 397 | /* 'sample_count' */ 398 | val32 = entry->sampleCount; 399 | RECOVERY_WRITE_VAL(val32); 400 | 401 | /* 'sample_delta' */ 402 | val32 = entry->sampleDelta; 403 | RECOVERY_WRITE_VAL(val32); 404 | 405 | track->stbl_index_write_count.time_to_sample++; 406 | } 407 | 408 | out: 409 | return ret; 410 | } 411 | 412 | 413 | static int mp4_mux_recovery_write_thumb(struct mp4_mux *mux) 414 | { 415 | int ret = 0; 416 | ssize_t err = 0; 417 | 418 | err = mp4_mux_recovery_write_box_info( 419 | mux, 0, MP4_METADATA_TAG_TYPE_COVER, 1); 420 | if (err < 0) { 421 | ret = -errno; 422 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 423 | goto out; 424 | } 425 | 426 | /* cover type */ 427 | RECOVERY_WRITE_VAL(mux->file_metadata.cover_type); 428 | 429 | /* cover */ 430 | RECOVERY_WRITE_ARR(mux->file_metadata.cover, 431 | mux->file_metadata.cover_size); 432 | 433 | out: 434 | return ret; 435 | } 436 | 437 | 438 | static int mp4_mux_recovery_write_track(struct mp4_mux *mux, 439 | struct mp4_mux_track *track) 440 | { 441 | int ret = 0; 442 | ssize_t err = 0; 443 | uint32_t len_name = track->name == NULL ? 0 : strlen(track->name); 444 | 445 | err = mp4_mux_recovery_write_box_info( 446 | mux, track->handle, MP4_TRACK_BOX, 1); 447 | if (err < 0) { 448 | ret = -errno; 449 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", errno); 450 | goto out; 451 | } 452 | 453 | RECOVERY_WRITE_VAL(track->type); 454 | RECOVERY_WRITE_ARR(track->name, len_name); 455 | RECOVERY_WRITE_VAL(track->flags); 456 | RECOVERY_WRITE_VAL(track->timescale); 457 | RECOVERY_WRITE_VAL(track->creation_time); 458 | RECOVERY_WRITE_VAL(track->modification_time); 459 | RECOVERY_WRITE_ARR(track->referenceTrackHandle, 460 | track->referenceTrackHandleCount); 461 | 462 | track->track_info_written = true; 463 | out: 464 | return ret; 465 | } 466 | 467 | 468 | static int mp4_mux_recovery_write_meta(struct mp4_mux *mux, 469 | struct mp4_mux_metadata *meta, 470 | uint32_t track_handle) 471 | { 472 | int ret = 0; 473 | ssize_t err = 0; 474 | uint32_t val32; 475 | 476 | ret = mp4_mux_recovery_write_box_info( 477 | mux, track_handle, MP4_META_BOX, 1); 478 | if (ret < 0) { 479 | ULOG_ERRNO("mp4_mux_recovery_write_box_info", -ret); 480 | goto out; 481 | } 482 | 483 | /* storage */ 484 | val32 = meta->storage; 485 | RECOVERY_WRITE_VAL(val32); 486 | 487 | /* key */ 488 | val32 = strlen(meta->key); 489 | RECOVERY_WRITE_ARR(meta->key, val32); 490 | 491 | /* value */ 492 | val32 = strlen(meta->value); 493 | RECOVERY_WRITE_ARR(meta->value, val32); 494 | 495 | out: 496 | return ret; 497 | } 498 | 499 | 500 | int mp4_mux_incremental_sync(struct mp4_mux *mux) 501 | { 502 | int ret = 0; 503 | struct mp4_mux_track *track; 504 | struct mp4_mux_metadata *meta; 505 | uint32_t meta_count = 0; 506 | uint32_t track_meta_count = 0; 507 | 508 | list_walk_entry_forward(&mux->tracks, track, node) 509 | { 510 | /* write only once */ 511 | if (!track->track_info_written) { 512 | ret = mp4_mux_recovery_write_track(mux, track); 513 | if (ret < 0) { 514 | ULOG_ERRNO("mp4_mux_recovery_write_track", 515 | -ret); 516 | goto out; 517 | } 518 | ret = mp4_mux_recovery_write_stsd(mux, track); 519 | if (ret < 0) { 520 | ULOG_ERRNO("mp4_mux_recovery_write_stsd", -ret); 521 | goto out; 522 | } 523 | } 524 | 525 | /* Skip empty tracks */ 526 | if (track->samples.count == 0) 527 | continue; 528 | 529 | ret = mp4_mux_recovery_write_stts(mux, track); 530 | if (ret < 0) { 531 | ULOG_ERRNO("mp4_mux_recovery_write_stts", -ret); 532 | goto out; 533 | } 534 | ret = mp4_mux_recovery_write_stss(mux, track); 535 | if (ret < 0) { 536 | ULOG_ERRNO("mp4_mux_recovery_write_stss", -ret); 537 | goto out; 538 | } 539 | ret = mp4_mux_recovery_write_stsc(mux, track); 540 | if (ret < 0) { 541 | ULOG_ERRNO("mp4_mux_recovery_write_stsc", -ret); 542 | goto out; 543 | } 544 | ret = mp4_mux_recovery_write_stsz(mux, track); 545 | if (ret < 0) { 546 | ULOG_ERRNO("mp4_mux_recovery_write_stsz", -ret); 547 | goto out; 548 | } 549 | ret = mp4_mux_recovery_write_stco(mux, track); 550 | if (ret < 0) { 551 | ULOG_ERRNO("mp4_mux_recovery_write_stco", -ret); 552 | goto out; 553 | } 554 | 555 | /* Metadata */ 556 | list_walk_entry_forward(&track->metadatas, meta, node) 557 | { 558 | track_meta_count++; 559 | if (track_meta_count < track->meta_write_count) 560 | continue; 561 | 562 | ret = mp4_mux_recovery_write_meta( 563 | mux, meta, track->handle); 564 | if (ret < 0) { 565 | ULOG_ERRNO("mp4_mux_recovery_write_meta", -ret); 566 | goto out; 567 | } 568 | } 569 | track->meta_write_count = track_meta_count + 1; 570 | } 571 | 572 | /* Metadata */ 573 | list_walk_entry_forward(&mux->metadatas, meta, node) 574 | { 575 | meta_count++; 576 | if (meta_count < mux->recovery.meta_write_count) 577 | continue; 578 | 579 | ret = mp4_mux_recovery_write_meta(mux, meta, 0); 580 | if (ret < 0) { 581 | ULOG_ERRNO("mp4_mux_recovery_write_meta", -ret); 582 | goto out; 583 | } 584 | } 585 | mux->recovery.meta_write_count = meta_count + 1; 586 | 587 | /* thumbnail */ 588 | if (!mux->recovery.thumb_written && mux->file_metadata.cover != NULL) { 589 | mp4_mux_recovery_write_thumb(mux); 590 | mux->recovery.thumb_written = true; 591 | } 592 | 593 | out: 594 | return ret; 595 | } 596 | -------------------------------------------------------------------------------- /src/mp4_track.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_priv.h" 29 | 30 | 31 | int mp4_track_is_sync_sample(struct mp4_track *track, 32 | unsigned int sampleIdx, 33 | int *prevSyncSampleIdx) 34 | { 35 | unsigned int i; 36 | 37 | ULOG_ERRNO_RETURN_ERR_IF(track == NULL, EINVAL); 38 | 39 | if (!track->syncSampleEntries) 40 | return 1; 41 | 42 | for (i = 0; i < track->syncSampleEntryCount; i++) { 43 | if (track->syncSampleEntries[i] - 1 == sampleIdx) 44 | return 1; 45 | else if (track->syncSampleEntries[i] - 1 > sampleIdx) { 46 | if ((prevSyncSampleIdx) && (i > 0)) { 47 | *prevSyncSampleIdx = 48 | track->syncSampleEntries[i - 1] - 1; 49 | } 50 | return 0; 51 | } 52 | } 53 | 54 | if ((prevSyncSampleIdx) && (i > 0)) 55 | *prevSyncSampleIdx = track->syncSampleEntries[i - 1] - 1; 56 | return 0; 57 | } 58 | 59 | 60 | int mp4_track_find_sample_by_time(struct mp4_track *track, 61 | uint64_t time, 62 | enum mp4_time_cmp cmp, 63 | int sync, 64 | int start) 65 | { 66 | int i, idx, is_sync, found = 0; 67 | 68 | ULOG_ERRNO_RETURN_ERR_IF(track == NULL, EINVAL); 69 | 70 | switch (cmp) { 71 | case MP4_TIME_CMP_EXACT: 72 | if (start < 0) 73 | start = 0; 74 | if (start >= (int)track->sampleCount) 75 | start = (int)track->sampleCount - 1; 76 | for (i = start; i < (int)track->sampleCount; i++, is_sync = 0) { 77 | if (track->sampleDecodingTime[i] == time) { 78 | if (sync) { 79 | is_sync = mp4_track_is_sync_sample( 80 | track, i, NULL); 81 | } 82 | if ((!sync) || (is_sync)) { 83 | idx = i; 84 | found = 1; 85 | break; 86 | } 87 | } else if (track->sampleDecodingTime[i] > time) { 88 | break; 89 | } 90 | } 91 | break; 92 | case MP4_TIME_CMP_NEAREST: { 93 | int min_idx = -1; 94 | uint64_t delta_ts; 95 | uint64_t prev_delta_ts = UINT64_MAX, min_delta_ts = UINT64_MAX; 96 | if (start < 0) 97 | start = 0; 98 | if (start >= (int)track->sampleCount) 99 | start = (int)track->sampleCount - 1; 100 | for (i = start; i < (int)track->sampleCount; i++, is_sync = 0) { 101 | if (sync) { 102 | is_sync = mp4_track_is_sync_sample( 103 | track, i, NULL); 104 | } 105 | if (sync && !is_sync) 106 | continue; 107 | delta_ts = llabs((int64_t)time - 108 | (int64_t)track->sampleDecodingTime[i]); 109 | if (delta_ts < min_delta_ts) { 110 | min_delta_ts = delta_ts; 111 | min_idx = i; 112 | } 113 | if (prev_delta_ts < delta_ts) 114 | break; 115 | prev_delta_ts = delta_ts; 116 | } 117 | if (min_idx >= 0) { 118 | idx = min_idx; 119 | found = 1; 120 | } 121 | break; 122 | } 123 | case MP4_TIME_CMP_LT: 124 | case MP4_TIME_CMP_LT_EQ: 125 | if (start < 0) 126 | start = (int)track->sampleCount - 1; 127 | if (start >= (int)track->sampleCount) 128 | start = (int)track->sampleCount - 1; 129 | for (i = start; i >= 0; i--, is_sync = 0) { 130 | if (((cmp == MP4_TIME_CMP_LT) && 131 | (track->sampleDecodingTime[i] < time)) || 132 | ((cmp == MP4_TIME_CMP_LT_EQ) && 133 | (track->sampleDecodingTime[i] <= time))) { 134 | if (sync) { 135 | is_sync = mp4_track_is_sync_sample( 136 | track, i, NULL); 137 | } 138 | if ((!sync) || (is_sync)) { 139 | idx = i; 140 | found = 1; 141 | break; 142 | } 143 | } 144 | } 145 | break; 146 | case MP4_TIME_CMP_GT: 147 | case MP4_TIME_CMP_GT_EQ: 148 | if (start < 0) 149 | start = 0; 150 | if (start >= (int)track->sampleCount) 151 | start = (int)track->sampleCount - 1; 152 | for (i = start; i < (int)track->sampleCount; i++, is_sync = 0) { 153 | if (((cmp == MP4_TIME_CMP_GT) && 154 | (track->sampleDecodingTime[i] > time)) || 155 | ((cmp == MP4_TIME_CMP_GT_EQ) && 156 | (track->sampleDecodingTime[i] >= time))) { 157 | if (sync) { 158 | is_sync = mp4_track_is_sync_sample( 159 | track, i, NULL); 160 | } 161 | if ((!sync) || (is_sync)) { 162 | idx = i; 163 | found = 1; 164 | break; 165 | } 166 | } 167 | } 168 | break; 169 | default: 170 | ULOGE("unsupported comparison type: %d", cmp); 171 | return -EINVAL; 172 | } 173 | 174 | return (found) ? idx : -ENOENT; 175 | } 176 | 177 | 178 | static struct mp4_track *mp4_track_new(void) 179 | { 180 | struct mp4_track *track = calloc(1, sizeof(*track)); 181 | if (track == NULL) { 182 | ULOG_ERRNO("calloc", ENOMEM); 183 | return NULL; 184 | } 185 | list_node_unref(&track->node); 186 | 187 | return track; 188 | } 189 | 190 | 191 | static int mp4_track_destroy(struct mp4_track *track) 192 | { 193 | if (track == NULL) 194 | return 0; 195 | 196 | mp4_video_decoder_config_destroy(&track->vdc); 197 | free(track->timeToSampleEntries); 198 | free(track->sampleDecodingTime); 199 | free(track->sampleSize); 200 | free(track->chunkOffset); 201 | free(track->sampleToChunkEntries); 202 | free(track->sampleOffset); 203 | free(track->syncSampleEntries); 204 | free(track->audioSpecificConfig); 205 | free(track->contentEncoding); 206 | free(track->mimeFormat); 207 | for (unsigned int i = 0; i < track->staticMetadataCount; i++) { 208 | free(track->staticMetadataKey[i]); 209 | free(track->staticMetadataValue[i]); 210 | } 211 | free(track->staticMetadataKey); 212 | free(track->staticMetadataValue); 213 | free(track->name); 214 | free(track); 215 | 216 | return 0; 217 | } 218 | 219 | 220 | struct mp4_track *mp4_track_add(struct mp4_file *mp4) 221 | { 222 | ULOG_ERRNO_RETURN_VAL_IF(mp4 == NULL, EINVAL, NULL); 223 | 224 | struct mp4_track *track = mp4_track_new(); 225 | if (track == NULL) { 226 | ULOG_ERRNO("mp4_track_new", ENOMEM); 227 | return NULL; 228 | } 229 | list_node_unref(&track->node); /* TODO: remove */ 230 | 231 | /* Add to the list */ 232 | list_add_after(list_last(&mp4->tracks), &track->node); 233 | mp4->trackCount++; 234 | 235 | return track; 236 | } 237 | 238 | 239 | int mp4_track_remove(struct mp4_file *mp4, struct mp4_track *track) 240 | { 241 | struct mp4_track *_track = NULL; 242 | 243 | ULOG_ERRNO_RETURN_ERR_IF(mp4 == NULL, EINVAL); 244 | ULOG_ERRNO_RETURN_ERR_IF(track == NULL, EINVAL); 245 | 246 | _track = mp4_track_find(mp4, track); 247 | if (_track != track) { 248 | ULOG_ERRNO("mp4_track_find", ENOENT); 249 | return -ENOENT; 250 | } 251 | 252 | /* Remove from the list */ 253 | list_del(&track->node); 254 | mp4->trackCount--; 255 | 256 | return mp4_track_destroy(track); 257 | } 258 | 259 | 260 | struct mp4_track *mp4_track_find(struct mp4_file *mp4, struct mp4_track *track) 261 | { 262 | struct mp4_track *_track = NULL; 263 | int found = 0; 264 | 265 | ULOG_ERRNO_RETURN_VAL_IF(mp4 == NULL, EINVAL, NULL); 266 | ULOG_ERRNO_RETURN_VAL_IF(track == NULL, EINVAL, NULL); 267 | 268 | list_walk_entry_forward(&mp4->tracks, _track, node) 269 | { 270 | if (_track == track) { 271 | found = 1; 272 | break; 273 | } 274 | } 275 | 276 | if (!found) 277 | return NULL; 278 | 279 | return _track; 280 | } 281 | 282 | 283 | struct mp4_track *mp4_track_find_by_idx(struct mp4_file *mp4, 284 | unsigned int track_idx) 285 | { 286 | struct mp4_track *_track = NULL; 287 | int found = 0; 288 | unsigned int k = 0; 289 | 290 | ULOG_ERRNO_RETURN_VAL_IF(mp4 == NULL, EINVAL, NULL); 291 | 292 | list_walk_entry_forward(&mp4->tracks, _track, node) 293 | { 294 | if (k == track_idx) { 295 | found = 1; 296 | break; 297 | } 298 | k++; 299 | } 300 | 301 | if (!found) 302 | return NULL; 303 | 304 | return _track; 305 | } 306 | 307 | 308 | struct mp4_track *mp4_track_find_by_id(struct mp4_file *mp4, 309 | unsigned int track_id) 310 | { 311 | struct mp4_track *_track = NULL; 312 | int found = 0; 313 | 314 | ULOG_ERRNO_RETURN_VAL_IF(mp4 == NULL, EINVAL, NULL); 315 | 316 | list_walk_entry_forward(&mp4->tracks, _track, node) 317 | { 318 | if (_track->id == track_id) { 319 | found = 1; 320 | break; 321 | } 322 | } 323 | 324 | if (!found) 325 | return NULL; 326 | 327 | return _track; 328 | } 329 | 330 | 331 | void mp4_tracks_destroy(struct mp4_file *mp4) 332 | { 333 | struct mp4_track *track = NULL, *tmp = NULL; 334 | 335 | ULOG_ERRNO_RETURN_IF(mp4 == NULL, EINVAL); 336 | 337 | list_walk_entry_forward_safe(&mp4->tracks, track, tmp, node) 338 | { 339 | mp4_track_destroy(track); 340 | } 341 | } 342 | 343 | 344 | int mp4_tracks_build(struct mp4_file *mp4) 345 | { 346 | int ret; 347 | struct mp4_track *tk = NULL, *videoTk = NULL; 348 | struct mp4_track *metaTk = NULL, *chapTk = NULL; 349 | int videoTrackCount = 0, audioTrackCount = 0, hintTrackCount = 0; 350 | int metadataTrackCount = 0, textTrackCount = 0; 351 | 352 | ULOG_ERRNO_RETURN_ERR_IF(mp4 == NULL, EINVAL); 353 | 354 | list_walk_entry_forward(&mp4->tracks, tk, node) 355 | { 356 | unsigned int i, j, k, n; 357 | uint32_t lastFirstChunk = 1, lastSamplesPerChunk = 0; 358 | uint32_t chunkCount, sampleCount = 0, chunkIdx; 359 | uint64_t offsetInChunk; 360 | for (i = 0; i < tk->sampleToChunkEntryCount; i++) { 361 | chunkCount = tk->sampleToChunkEntries[i].firstChunk - 362 | lastFirstChunk; 363 | sampleCount += chunkCount * lastSamplesPerChunk; 364 | lastFirstChunk = tk->sampleToChunkEntries[i].firstChunk; 365 | lastSamplesPerChunk = 366 | tk->sampleToChunkEntries[i].samplesPerChunk; 367 | } 368 | chunkCount = tk->chunkCount - lastFirstChunk + 1; 369 | sampleCount += chunkCount * lastSamplesPerChunk; 370 | 371 | if (sampleCount != tk->sampleCount) { 372 | ULOGE("sample count mismatch: %d, expected %d", 373 | sampleCount, 374 | tk->sampleCount); 375 | return -EPROTO; 376 | } 377 | 378 | tk->sampleOffset = malloc(sampleCount * sizeof(uint64_t)); 379 | if (tk->sampleOffset == NULL) { 380 | ULOG_ERRNO("malloc", ENOMEM); 381 | return -ENOMEM; 382 | } 383 | 384 | lastFirstChunk = 1; 385 | lastSamplesPerChunk = 0; 386 | for (i = 0, n = 0, chunkIdx = 0; 387 | i < tk->sampleToChunkEntryCount; 388 | i++) { 389 | chunkCount = tk->sampleToChunkEntries[i].firstChunk - 390 | lastFirstChunk; 391 | for (j = 0; j < chunkCount; j++, chunkIdx++) { 392 | for (k = 0, offsetInChunk = 0; 393 | k < lastSamplesPerChunk; 394 | k++, n++) { 395 | tk->sampleOffset[n] = 396 | tk->chunkOffset[chunkIdx] + 397 | offsetInChunk; 398 | offsetInChunk += tk->sampleSize[n]; 399 | } 400 | } 401 | lastFirstChunk = tk->sampleToChunkEntries[i].firstChunk; 402 | lastSamplesPerChunk = 403 | tk->sampleToChunkEntries[i].samplesPerChunk; 404 | } 405 | chunkCount = tk->chunkCount - lastFirstChunk + 1; 406 | for (j = 0; j < chunkCount; j++, chunkIdx++) { 407 | for (k = 0, offsetInChunk = 0; k < lastSamplesPerChunk; 408 | k++, n++) { 409 | tk->sampleOffset[n] = 410 | tk->chunkOffset[chunkIdx] + 411 | offsetInChunk; 412 | offsetInChunk += tk->sampleSize[n]; 413 | } 414 | } 415 | 416 | for (i = 0, sampleCount = 0; i < tk->timeToSampleEntryCount; 417 | i++) 418 | sampleCount += tk->timeToSampleEntries[i].sampleCount; 419 | 420 | if (sampleCount != tk->sampleCount) { 421 | ULOGE("sample count mismatch: %d, expected %d", 422 | sampleCount, 423 | tk->sampleCount); 424 | return -EPROTO; 425 | } 426 | 427 | tk->sampleDecodingTime = malloc(sampleCount * sizeof(uint64_t)); 428 | if (tk->sampleDecodingTime == NULL) { 429 | ULOG_ERRNO("malloc", ENOMEM); 430 | return -ENOMEM; 431 | } 432 | 433 | uint64_t ts = 0; 434 | for (i = 0, k = 0; i < tk->timeToSampleEntryCount; i++) { 435 | for (j = 0; j < tk->timeToSampleEntries[i].sampleCount; 436 | j++, k++) { 437 | tk->sampleDecodingTime[k] = ts; 438 | ts += tk->timeToSampleEntries[i].sampleDelta; 439 | } 440 | } 441 | 442 | switch (tk->type) { 443 | case MP4_TRACK_TYPE_VIDEO: 444 | videoTrackCount++; 445 | videoTk = tk; 446 | break; 447 | case MP4_TRACK_TYPE_AUDIO: 448 | audioTrackCount++; 449 | break; 450 | case MP4_TRACK_TYPE_HINT: 451 | hintTrackCount++; 452 | break; 453 | case MP4_TRACK_TYPE_METADATA: 454 | metadataTrackCount++; 455 | metaTk = tk; 456 | break; 457 | case MP4_TRACK_TYPE_TEXT: 458 | textTrackCount++; 459 | break; 460 | default: 461 | break; 462 | } 463 | 464 | /* Link tracks using track references */ 465 | for (i = 0; i < tk->referenceTrackIdCount; i++) { 466 | struct mp4_track *tkRef; 467 | tkRef = mp4_track_find_by_id(mp4, 468 | tk->referenceTrackId[i]); 469 | if (tkRef == NULL) { 470 | ULOGW("track reference: track ID %d not found", 471 | tk->referenceTrackId[i]); 472 | continue; 473 | } 474 | 475 | if ((tk->referenceType == 476 | MP4_REFERENCE_TYPE_DESCRIPTION) && 477 | (tk->type == MP4_TRACK_TYPE_METADATA)) { 478 | tkRef->metadata = tk; 479 | } else if ((tk->referenceType == 480 | MP4_REFERENCE_TYPE_CHAPTERS) && 481 | (tkRef->type == MP4_TRACK_TYPE_TEXT)) { 482 | tk->chapters = tkRef; 483 | tkRef->type = MP4_TRACK_TYPE_CHAPTERS; 484 | chapTk = tkRef; 485 | } 486 | } 487 | } 488 | 489 | /* Workaround: if we have only 1 video track and 1 metadata 490 | * track with no track reference, link them anyway */ 491 | if ((videoTrackCount == 1) && (metadataTrackCount == 1) && 492 | (audioTrackCount == 0) && (hintTrackCount == 0) && (videoTk) && 493 | (metaTk) && (!videoTk->metadata)) 494 | videoTk->metadata = metaTk; 495 | 496 | /* Build the chapter list */ 497 | if (chapTk) { 498 | unsigned int i; 499 | for (i = 0; i < chapTk->sampleCount; i++) { 500 | unsigned int sampleSize, readBytes = 0; 501 | uint16_t sz; 502 | sampleSize = chapTk->sampleSize[i]; 503 | off_t _ret = lseek( 504 | mp4->fd, chapTk->sampleOffset[i], SEEK_SET); 505 | if (_ret == -1) { 506 | ULOG_ERRNO("lseek", errno); 507 | return -errno; 508 | } 509 | MP4_READ_16(mp4->fd, sz, readBytes); 510 | sz = ntohs(sz); 511 | if (sz <= sampleSize - readBytes) { 512 | char *chapName = malloc(sz + 1); 513 | if (chapName == NULL) 514 | return -ENOMEM; 515 | mp4->chaptersName[mp4->chaptersCount] = 516 | chapName; 517 | ssize_t count = read(mp4->fd, chapName, sz); 518 | if (count == -1) { 519 | ret = -errno; 520 | ULOG_ERRNO("read", -ret); 521 | return ret; 522 | } else if (count != (ssize_t)sz) { 523 | ret = -ENODATA; 524 | ULOG_ERRNO("read", -ret); 525 | return ret; 526 | } 527 | readBytes += sz; 528 | chapName[sz] = '\0'; 529 | uint64_t chapTime = mp4_sample_time_to_usec( 530 | chapTk->sampleDecodingTime[i], 531 | chapTk->timescale); 532 | ULOGD("chapter #%d time=%" PRIu64 " '%s'", 533 | mp4->chaptersCount + 1, 534 | chapTime, 535 | chapName); 536 | mp4->chaptersTime[mp4->chaptersCount] = 537 | chapTime; 538 | mp4->chaptersCount++; 539 | } 540 | } 541 | } 542 | 543 | return 0; 544 | } 545 | -------------------------------------------------------------------------------- /tests/mp4_test.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_test.h" 29 | 30 | 31 | static CU_SuiteInfo s_suites[] = { 32 | {FN("demux"), NULL, NULL, g_mp4_test_demux}, 33 | {FN("utilities"), NULL, NULL, g_mp4_test_utilities}, 34 | 35 | CU_SUITE_INFO_NULL, 36 | }; 37 | 38 | 39 | static void run_automated() 40 | { 41 | CU_automated_run_tests(); 42 | CU_list_tests_to_file(); 43 | } 44 | 45 | 46 | static void run_basic() 47 | { 48 | CU_basic_set_mode(CU_BRM_VERBOSE); 49 | CU_basic_run_tests(); 50 | } 51 | 52 | 53 | int main() 54 | { 55 | const char *filename; 56 | 57 | CU_initialize_registry(); 58 | CU_register_suites(s_suites); 59 | 60 | /* Set filename */ 61 | filename = getenv("CUNIT_OUT_NAME"); 62 | CU_set_output_filename(filename); 63 | 64 | /* Run tests */ 65 | if (getenv("CUNIT_AUTOMATED") != NULL) 66 | run_automated(); 67 | else 68 | run_basic(); 69 | 70 | CU_cleanup_registry(); 71 | } 72 | -------------------------------------------------------------------------------- /tests/mp4_test.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _MP4_TEST_H_ 29 | #define _MP4_TEST_H_ 30 | 31 | #include 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #define FN(_name) (char *)_name 46 | 47 | #define ASSETS_ROOT "/mnt/DFS/MULTIMEDIA_DATA" 48 | 49 | 50 | extern CU_TestInfo g_mp4_test_demux[]; 51 | extern CU_TestInfo g_mp4_test_utilities[]; 52 | 53 | 54 | #endif /* _MP4_TEST_H_ */ 55 | -------------------------------------------------------------------------------- /tests/mp4_test_utilities.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #include "mp4_test.h" 29 | 30 | static const struct { 31 | const char *path; 32 | } nas_tests_mp4_utilities[] = { 33 | { 34 | "Tests/anafi/4k/video_recording/champs_1080p30.mp4", 35 | }, 36 | { 37 | "Tests/anafi/4k/video_recording/jardin_2160p30.mp4", 38 | }, 39 | }; 40 | 41 | 42 | static void test_mp4_metadata_cover_type_str(void) 43 | { 44 | CU_ASSERT_STRING_EQUAL( 45 | mp4_metadata_cover_type_str(MP4_METADATA_COVER_TYPE_JPEG), 46 | "JPEG"); 47 | CU_ASSERT_STRING_EQUAL( 48 | mp4_metadata_cover_type_str(MP4_METADATA_COVER_TYPE_PNG), 49 | "PNG"); 50 | CU_ASSERT_STRING_EQUAL( 51 | mp4_metadata_cover_type_str(MP4_METADATA_COVER_TYPE_BMP), 52 | "BMP"); 53 | CU_ASSERT_STRING_EQUAL( 54 | mp4_metadata_cover_type_str(MP4_METADATA_COVER_TYPE_UNKNOWN), 55 | "UNKNOWN"); 56 | } 57 | 58 | 59 | static void test_mp4_audio_codec_str(void) 60 | { 61 | CU_ASSERT_STRING_EQUAL(mp4_audio_codec_str(MP4_AUDIO_CODEC_AAC_LC), 62 | "AAC_LC"); 63 | CU_ASSERT_STRING_EQUAL(mp4_audio_codec_str(MP4_AUDIO_CODEC_UNKNOWN), 64 | "UNKNOWN"); 65 | } 66 | 67 | 68 | static void test_mp4_video_codec_str(void) 69 | { 70 | CU_ASSERT_STRING_EQUAL(mp4_video_codec_str(MP4_VIDEO_CODEC_AVC), "AVC"); 71 | CU_ASSERT_STRING_EQUAL(mp4_video_codec_str(MP4_VIDEO_CODEC_HEVC), 72 | "HEVC"); 73 | CU_ASSERT_STRING_EQUAL(mp4_video_codec_str(MP4_VIDEO_CODEC_UNKNOWN), 74 | "UNKNOWN"); 75 | } 76 | 77 | 78 | static void test_mp4_track_type_str(void) 79 | { 80 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_VIDEO), 81 | "VIDEO"); 82 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_AUDIO), 83 | "AUDIO"); 84 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_HINT), "HINT"); 85 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_METADATA), 86 | "METADATA"); 87 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_TEXT), "TEXT"); 88 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_CHAPTERS), 89 | "CHAPTERS"); 90 | CU_ASSERT_STRING_EQUAL(mp4_track_type_str(MP4_TRACK_TYPE_UNKNOWN), 91 | "UNKNOWN"); 92 | } 93 | 94 | 95 | static void test_mp4_utilities_mp4_to_json(void) 96 | { 97 | int res = 0; 98 | struct json_object *json_obj; 99 | char path[200]; 100 | snprintf(path, 101 | sizeof(path), 102 | "%s/%s", 103 | (getenv("ASSETS_ROOT") != NULL) ? getenv("ASSETS_ROOT") 104 | : ASSETS_ROOT, 105 | nas_tests_mp4_utilities[0].path); 106 | 107 | /* all null */ 108 | res = mp4_file_to_json(NULL, false, NULL); 109 | CU_ASSERT_EQUAL(res, -EINVAL); 110 | 111 | /* filename null */ 112 | res = mp4_file_to_json(NULL, false, &json_obj); 113 | CU_ASSERT_EQUAL(res, -EINVAL); 114 | 115 | /* json null */ 116 | res = mp4_file_to_json(path, false, NULL); 117 | CU_ASSERT_EQUAL(res, -EINVAL); 118 | 119 | for (size_t i = 0; i < FUTILS_SIZEOF_ARRAY(nas_tests_mp4_utilities); 120 | i++) { 121 | snprintf(path, 122 | sizeof(path), 123 | "%s/%s", 124 | (getenv("ASSETS_ROOT") != NULL) ? getenv("ASSETS_ROOT") 125 | : ASSETS_ROOT, 126 | nas_tests_mp4_utilities[i].path); 127 | 128 | res = mp4_file_to_json(path, false, &json_obj); 129 | CU_ASSERT_EQUAL(res, 0); 130 | CU_ASSERT_PTR_NOT_NULL(json_obj); 131 | json_object_put(json_obj); 132 | } 133 | } 134 | 135 | 136 | static void test_mp4_utilities_generate_chapter_sample(void) 137 | { 138 | int res = 0; 139 | char *chapter_str = "chapter_name"; 140 | uint8_t *buffer; 141 | unsigned int buffer_size; 142 | 143 | /* all null */ 144 | res = mp4_generate_chapter_sample(NULL, NULL, NULL); 145 | CU_ASSERT_EQUAL(res, -EINVAL); 146 | 147 | /* buffer and buffer_size null */ 148 | res = mp4_generate_chapter_sample(chapter_str, NULL, NULL); 149 | CU_ASSERT_EQUAL(res, -EINVAL); 150 | 151 | /* buffer null */ 152 | res = mp4_generate_chapter_sample(chapter_str, NULL, &buffer_size); 153 | CU_ASSERT_EQUAL(res, -EINVAL); 154 | 155 | /* buffer size null */ 156 | res = mp4_generate_chapter_sample(chapter_str, &buffer, NULL); 157 | CU_ASSERT_EQUAL(res, -EINVAL); 158 | 159 | /* name null */ 160 | res = mp4_generate_chapter_sample(NULL, &buffer, &buffer_size); 161 | CU_ASSERT_EQUAL(res, -EINVAL); 162 | 163 | /* name and buffer null */ 164 | res = mp4_generate_chapter_sample(NULL, NULL, &buffer_size); 165 | CU_ASSERT_EQUAL(res, -EINVAL); 166 | 167 | /* name and buffer_size null */ 168 | res = mp4_generate_chapter_sample(NULL, &buffer, NULL); 169 | CU_ASSERT_EQUAL(res, -EINVAL); 170 | 171 | /* valid */ 172 | res = mp4_generate_chapter_sample(chapter_str, &buffer, &buffer_size); 173 | CU_ASSERT_EQUAL(res, 0); 174 | CU_ASSERT_PTR_NOT_NULL(buffer); 175 | CU_ASSERT(buffer_size > 0); 176 | 177 | free(buffer); 178 | } 179 | 180 | 181 | CU_TestInfo g_mp4_test_utilities[] = { 182 | {FN("mp4-utilities-generate-chapter-sample"), 183 | &test_mp4_utilities_generate_chapter_sample}, 184 | {FN("mp4-utilities-mp4-to-json"), &test_mp4_utilities_mp4_to_json}, 185 | {FN("mp4-utilities-mp4-track-type-str"), &test_mp4_track_type_str}, 186 | {FN("mp4-utilities-mp4-video-codec-str"), &test_mp4_video_codec_str}, 187 | {FN("mp4-utilities-mp4-audio-codec-str"), &test_mp4_audio_codec_str}, 188 | {FN("mp4-utilities-mp4-metadata-cover-type-str"), 189 | &test_mp4_metadata_cover_type_str}, 190 | 191 | CU_TEST_INFO_NULL, 192 | }; 193 | -------------------------------------------------------------------------------- /tools/larry_covery.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2023 Parrot Drones SAS 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifndef ULOG_TAG 13 | # define ULOG_TAG larry_recovery 14 | #endif 15 | 16 | 17 | static void welcome(const char *prog_name) 18 | { 19 | printf("\n%s - MP4 file recovery program\n" 20 | "Copyright (c) 2023 Parrot Drones SAS\n", 21 | prog_name); 22 | } 23 | 24 | 25 | static void usage(const char *prog_name) 26 | { 27 | /* clang-format off */ 28 | printf("Usage: %s [options]\n" 29 | "Options:\n" 30 | " -h | --help " 31 | "Print this message\n" 32 | " -l | --link " 33 | "link file path (usually named *.CHK)\n" 34 | " -t | --tables " 35 | "tables file path (usually named *.MRF)\n" 36 | " -d | --data " 37 | "data file path (usually named *.MP4 or *.TMP)\n" 38 | "\n", 39 | prog_name); 40 | /* clang-format on */ 41 | } 42 | 43 | 44 | static const char short_options[] = "hl:t:d:"; 45 | 46 | 47 | static const struct option long_options[] = { 48 | {"help", no_argument, NULL, 'h'}, 49 | {"link", required_argument, NULL, 'l'}, 50 | {"tables", required_argument, NULL, 't'}, 51 | {"data", required_argument, NULL, 'd'}, 52 | {0, 0, 0, 0}, 53 | }; 54 | 55 | 56 | int main(int argc, char **argv) 57 | { 58 | int ret = EXIT_SUCCESS; 59 | int idx, c; 60 | char *tables_path = NULL; 61 | char *link_path = NULL; 62 | char *data_path = NULL; 63 | char *error_msg = NULL; 64 | 65 | /* Command-line parameters */ 66 | while ((c = getopt_long( 67 | argc, argv, short_options, long_options, &idx)) != -1) { 68 | switch (c) { 69 | case 0: 70 | break; 71 | case 'h': 72 | usage(argv[0]); 73 | exit(EXIT_SUCCESS); 74 | case 'l': 75 | link_path = optarg; 76 | break; 77 | case 't': 78 | tables_path = optarg; 79 | break; 80 | case 'd': 81 | data_path = optarg; 82 | break; 83 | default: 84 | usage(argv[0]); 85 | exit(EXIT_FAILURE); 86 | break; 87 | } 88 | } 89 | 90 | if (argc != optind) { 91 | usage(argv[0]); 92 | exit(EXIT_FAILURE); 93 | } 94 | 95 | if (link_path == NULL) { 96 | usage(argv[0]); 97 | exit(EXIT_FAILURE); 98 | } 99 | 100 | if ((tables_path == NULL || data_path == NULL) && 101 | (tables_path != data_path)) { 102 | usage(argv[0]); 103 | exit(EXIT_FAILURE); 104 | } 105 | 106 | if (tables_path != NULL) { 107 | ret = mp4_recovery_recover_file_from_paths( 108 | link_path, tables_path, data_path, &error_msg, NULL); 109 | if (ret < 0) { 110 | ULOG_ERRNO("mp4_recovery_recover_file_from_paths (%s)", 111 | -ret, 112 | error_msg); 113 | } 114 | } else { 115 | ret = mp4_recovery_recover_file(link_path, &error_msg, NULL); 116 | if (ret < 0) { 117 | ULOG_ERRNO("mp4_recovery_recover_file (%s)", 118 | -ret, 119 | error_msg); 120 | } 121 | } 122 | 123 | printf("recovery %s\n", ret >= 0 ? "succeeded" : "failed"); 124 | 125 | ret = mp4_recovery_finalize(link_path, (ret < 0)); 126 | if (ret < 0) { 127 | ULOG_ERRNO("mp4_recovery_finalize", -ret); 128 | ret = -EXIT_FAILURE; 129 | } 130 | 131 | free(error_msg); 132 | return ret; 133 | } 134 | -------------------------------------------------------------------------------- /tools/mp4_demux.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _FILE_OFFSET_BITS 29 | # define _FILE_OFFSET_BITS 64 30 | #endif 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #define ULOG_TAG mp4_demux 40 | #include 41 | ULOG_DECLARE_TAG(mp4_demux); 42 | 43 | #include 44 | #include 45 | 46 | 47 | #define DATE_SIZE 26 48 | 49 | 50 | static bool log_frames; 51 | static const char *cover_file; 52 | 53 | 54 | static void print_info(struct mp4_demux *demux) 55 | { 56 | struct mp4_media_info info; 57 | int ret; 58 | 59 | ret = mp4_demux_get_media_info(demux, &info); 60 | if (ret < 0) { 61 | ULOG_ERRNO("mp4_demux_get_media_info", -ret); 62 | return; 63 | } 64 | 65 | char creation_time_str[DATE_SIZE + 1]; 66 | char modification_time_str[DATE_SIZE + 1]; 67 | time_local_format(info.creation_time, 68 | 0, 69 | TIME_FMT_LONG, 70 | creation_time_str, 71 | DATE_SIZE); 72 | time_local_format(info.modification_time, 73 | 0, 74 | TIME_FMT_LONG, 75 | modification_time_str, 76 | DATE_SIZE); 77 | 78 | printf("Media\n"); 79 | unsigned int hrs = 80 | (unsigned int)((info.duration + 500000) / 1000000 / 60 / 60); 81 | unsigned int min = 82 | (unsigned int)((info.duration + 500000) / 1000000 / 60 - 83 | hrs * 60); 84 | unsigned int sec = (unsigned int)((info.duration + 500000) / 1000000 - 85 | hrs * 60 * 60 - min * 60); 86 | printf(" duration: %02d:%02d:%02d\n", hrs, min, sec); 87 | printf(" creation time: %s\n", creation_time_str); 88 | printf(" modification time: %s\n", modification_time_str); 89 | printf("\n"); 90 | } 91 | 92 | 93 | static void print_tracks(struct mp4_demux *demux) 94 | { 95 | struct mp4_track_info tk; 96 | int i, count, ret; 97 | uint32_t duration_usec; 98 | 99 | count = mp4_demux_get_track_count(demux); 100 | if (count < 0) { 101 | ULOG_ERRNO("mp4_demux_get_track_count", -count); 102 | return; 103 | } 104 | 105 | for (i = 0; i < count; i++) { 106 | ret = mp4_demux_get_track_info(demux, i, &tk); 107 | if (ret < 0) { 108 | ULOG_ERRNO("mp4_demux_get_track_info", -ret); 109 | continue; 110 | } 111 | 112 | char creation_time_str[DATE_SIZE + 1]; 113 | char modification_time_str[DATE_SIZE + 1]; 114 | time_local_format(tk.creation_time, 115 | 0, 116 | TIME_FMT_LONG, 117 | creation_time_str, 118 | DATE_SIZE); 119 | time_local_format(tk.modification_time, 120 | 0, 121 | TIME_FMT_LONG, 122 | modification_time_str, 123 | DATE_SIZE); 124 | 125 | printf("Track #%d ID=%d\n", i, tk.id); 126 | printf(" type: %s\n", mp4_track_type_str(tk.type)); 127 | printf(" name: %s\n", tk.name); 128 | printf(" enabled: %d\n", tk.enabled); 129 | printf(" in_movie: %d\n", tk.in_movie); 130 | printf(" in_preview: %d\n", tk.in_preview); 131 | switch (tk.type) { 132 | case MP4_TRACK_TYPE_VIDEO: 133 | printf(" codec: %s\n", 134 | mp4_video_codec_str(tk.video_codec)); 135 | printf(" dimensions=%" PRIu32 "x%" PRIu32 "\n", 136 | tk.video_width, 137 | tk.video_height); 138 | break; 139 | case MP4_TRACK_TYPE_AUDIO: 140 | printf(" codec: %s\n", 141 | mp4_audio_codec_str(tk.audio_codec)); 142 | printf(" channels: %" PRIu32 "\n", 143 | tk.audio_channel_count); 144 | printf(" samples: %" PRIu32 "bit @ %.2fkHz\n", 145 | tk.audio_sample_size, 146 | tk.audio_sample_rate / 1000.); 147 | break; 148 | case MP4_TRACK_TYPE_METADATA: 149 | printf(" content encoding: %s\n", tk.content_encoding); 150 | printf(" mime format: %s\n", tk.mime_format); 151 | break; 152 | default: 153 | break; 154 | } 155 | if (tk.has_metadata) { 156 | printf(" metadata: present\n"); 157 | printf(" metadata content encoding: %s\n", 158 | tk.metadata_content_encoding); 159 | printf(" metadata mime format: %s\n", 160 | tk.metadata_mime_format); 161 | } 162 | duration_usec = 163 | mp4_sample_time_to_usec(tk.duration, tk.timescale); 164 | unsigned int hrs = (unsigned int)((duration_usec + 500000) / 165 | 1000000 / 60 / 60); 166 | unsigned int min = 167 | (unsigned int)((duration_usec + 500000) / 1000000 / 60 - 168 | hrs * 60); 169 | unsigned int sec = 170 | (unsigned int)((duration_usec + 500000) / 1000000 - 171 | hrs * 60 * 60 - min * 60); 172 | printf(" duration: %02d:%02d:%02d\n", hrs, min, sec); 173 | printf(" creation time: %s\n", creation_time_str); 174 | printf(" modification time: %s\n", modification_time_str); 175 | printf(" timescale: %" PRIu32 "\n", tk.timescale); 176 | 177 | unsigned int meta_count = 0; 178 | char **keys = NULL; 179 | char **values = NULL; 180 | ret = mp4_demux_get_track_metadata_strings( 181 | demux, tk.id, &meta_count, &keys, &values); 182 | if ((ret == 0) && (meta_count > 0)) { 183 | printf(" static metadata:\n"); 184 | unsigned int j; 185 | for (j = 0; j < meta_count; j++) { 186 | if ((keys[j]) && (values[j])) { 187 | printf(" %s: %s\n", 188 | keys[j], 189 | values[j]); 190 | } 191 | } 192 | } 193 | 194 | printf("\n"); 195 | } 196 | } 197 | 198 | 199 | static void print_metadata(struct mp4_demux *demux) 200 | { 201 | int ret; 202 | unsigned int count = 0; 203 | char **keys = NULL; 204 | char **values = NULL; 205 | 206 | ret = mp4_demux_get_metadata_strings(demux, &count, &keys, &values); 207 | if (ret < 0) 208 | ULOG_ERRNO("mp4_demux_get_metadata_strings", -ret); 209 | 210 | if (count > 0) { 211 | printf("Metadata\n"); 212 | unsigned int i; 213 | for (i = 0; i < count; i++) { 214 | if ((keys[i]) && (values[i])) 215 | printf(" %s: %s\n", keys[i], values[i]); 216 | } 217 | printf("\n"); 218 | } 219 | 220 | uint8_t *cover_buffer = NULL; 221 | unsigned int cover_buffer_size = 0, cover_size = 0; 222 | enum mp4_metadata_cover_type type; 223 | ret = mp4_demux_get_metadata_cover( 224 | demux, cover_buffer, cover_buffer_size, &cover_size, &type); 225 | if (ret < 0) 226 | ULOG_ERRNO("mp4_demux_get_metadata_cover", -ret); 227 | 228 | if (cover_size > 0) { 229 | cover_buffer_size = cover_size; 230 | cover_buffer = calloc(1, cover_buffer_size); 231 | if (!cover_buffer) 232 | return; 233 | 234 | ret = mp4_demux_get_metadata_cover(demux, 235 | cover_buffer, 236 | cover_buffer_size, 237 | &cover_size, 238 | &type); 239 | if (ret < 0) { 240 | ULOG_ERRNO("mp4_demux_get_metadata_cover", -ret); 241 | } else { 242 | printf("Cover present (%s)\n\n", 243 | mp4_metadata_cover_type_str(type)); 244 | if (cover_file && strlen(cover_file)) { 245 | FILE *f = fopen(cover_file, "wb"); 246 | if (f) { 247 | fwrite(cover_buffer, cover_size, 1, f); 248 | fclose(f); 249 | } 250 | } 251 | } 252 | 253 | free(cover_buffer); 254 | } 255 | } 256 | 257 | 258 | static void print_chapters(struct mp4_demux *demux) 259 | { 260 | int ret; 261 | unsigned int chapters_count = 0, i; 262 | uint64_t *chapters_time = NULL; 263 | char **chapters_name = NULL; 264 | 265 | ret = mp4_demux_get_chapters( 266 | demux, &chapters_count, &chapters_time, &chapters_name); 267 | if (ret < 0) { 268 | ULOG_ERRNO("mp4_demux_get_chapters", -ret); 269 | return; 270 | } 271 | 272 | if (chapters_count == 0) 273 | return; 274 | 275 | printf("Chapters\n"); 276 | for (i = 0; i < chapters_count; i++) { 277 | unsigned int hrs = (unsigned int)((chapters_time[i] + 500000) / 278 | 1000000 / 60 / 60); 279 | unsigned int min = (unsigned int)((chapters_time[i] + 500000) / 280 | 1000000 / 60 - 281 | hrs * 60); 282 | unsigned int sec = 283 | (unsigned int)((chapters_time[i] + 500000) / 1000000 - 284 | hrs * 60 * 60 - min * 60); 285 | printf(" chapter #%d time=%02d:%02d:%02d '%s'\n", 286 | i + 1, 287 | hrs, 288 | min, 289 | sec, 290 | chapters_name[i]); 291 | } 292 | printf("\n"); 293 | } 294 | 295 | 296 | static void print_frames(struct mp4_demux *demux) 297 | { 298 | struct mp4_track_info tk; 299 | struct mp4_track_sample sample; 300 | int i, count, ret, found = 0; 301 | unsigned int id; 302 | 303 | count = mp4_demux_get_track_count(demux); 304 | if (count < 0) { 305 | ULOG_ERRNO("mp4_demux_get_track_count", -count); 306 | return; 307 | } 308 | 309 | for (i = 0; i < count; i++) { 310 | ret = mp4_demux_get_track_info(demux, i, &tk); 311 | if ((ret == 0) && (tk.type == MP4_TRACK_TYPE_VIDEO)) { 312 | id = tk.id; 313 | found = 1; 314 | break; 315 | } 316 | } 317 | 318 | if (!found) 319 | return; 320 | 321 | i = 0; 322 | do { 323 | ret = mp4_demux_get_track_sample( 324 | demux, id, 1, NULL, 0, NULL, 0, &sample); 325 | if (ret < 0) { 326 | ULOG_ERRNO("mp4_demux_get_track_sample", -ret); 327 | i++; 328 | continue; 329 | } 330 | 331 | if (sample.size == 0) 332 | break; 333 | 334 | printf("Frame #%d size=%06" PRIu32 " offset=0x%08" PRIX64 335 | " metadata_size=%" PRIu32 " dts=%" PRIu64 " sync=%d\n", 336 | i, 337 | sample.size, 338 | sample.offset, 339 | sample.metadata_size, 340 | sample.dts, 341 | sample.sync); 342 | 343 | i++; 344 | } while (sample.size); 345 | 346 | printf("\n"); 347 | } 348 | 349 | 350 | static void welcome(char *prog_name) 351 | { 352 | printf("\n%s - MP4 file library demuxer program\n" 353 | "Copyright (c) 2018 Parrot Drones SAS\n" 354 | "Copyright (c) 2016 Aurelien Barre\n\n", 355 | prog_name); 356 | } 357 | 358 | 359 | static void usage(char *prog_name) 360 | { 361 | /* clang-format off */ 362 | printf("Usage: %s [options]\n" 363 | "Options:\n" 364 | " -h | --help " 365 | "Print this message\n" 366 | " --frames " 367 | "Print frames information\n" 368 | " --cover " 369 | "Cover output file\n" 370 | " -j | --json " 371 | "Output to JSON file\n" 372 | " -f | --force " 373 | "Force json output on any video\n" 374 | " -p | --pretty " 375 | "Pretty output for JSON file\n" 376 | "\n", 377 | prog_name); 378 | /* clang-format off */ 379 | } 380 | 381 | 382 | enum args_id { 383 | ARGS_ID_FRAMES = 256, 384 | ARGS_ID_COVER, 385 | }; 386 | 387 | 388 | static const char short_options[] = "hj:pf"; 389 | 390 | 391 | static const struct option long_options[] = { 392 | {"help", no_argument, NULL, 'h'}, 393 | {"frames", no_argument, NULL, ARGS_ID_FRAMES}, 394 | {"cover", required_argument, NULL, ARGS_ID_COVER}, 395 | {"force", no_argument, NULL, 'f'}, 396 | {"json", required_argument, NULL, 'j'}, 397 | {"pretty", no_argument, NULL, 'p'}, 398 | {0, 0, 0, 0}, 399 | }; 400 | 401 | 402 | int main(int argc, char **argv) 403 | { 404 | int ret = 0, err = 0, status = EXIT_SUCCESS; 405 | int idx, c; 406 | struct mp4_demux *demux; 407 | struct timespec ts = {0, 0}; 408 | uint64_t start_time = 0, end_time = 0; 409 | const char *input_file = NULL; 410 | const char *json_file = NULL; 411 | bool pretty = false; 412 | bool force = false; 413 | 414 | log_frames = false; 415 | cover_file = NULL; 416 | 417 | welcome(argv[0]); 418 | 419 | /* Command-line parameters */ 420 | while ((c = getopt_long( 421 | argc, argv, short_options, long_options, &idx)) != -1) { 422 | switch (c) { 423 | case 0: 424 | break; 425 | 426 | case 'h': 427 | usage(argv[0]); 428 | exit(EXIT_SUCCESS); 429 | break; 430 | 431 | case ARGS_ID_FRAMES: 432 | log_frames = true; 433 | break; 434 | 435 | case ARGS_ID_COVER: 436 | cover_file = optarg; 437 | break; 438 | 439 | case 'j': 440 | json_file = optarg; 441 | break; 442 | 443 | case 'p': 444 | pretty = true; 445 | break; 446 | 447 | case 'f': 448 | force = true; 449 | break; 450 | 451 | default: 452 | usage(argv[0]); 453 | exit(EXIT_FAILURE); 454 | break; 455 | } 456 | } 457 | 458 | if (argc - optind < 1) { 459 | usage(argv[0]); 460 | exit(EXIT_FAILURE); 461 | } 462 | 463 | input_file = argv[optind]; 464 | 465 | if (json_file == NULL && (pretty || force)) { 466 | usage(argv[0]); 467 | exit(EXIT_FAILURE); 468 | } 469 | 470 | time_get_monotonic(&ts); 471 | time_timespec_to_us(&ts, &start_time); 472 | 473 | ret = mp4_demux_open(input_file, &demux); 474 | 475 | time_get_monotonic(&ts); 476 | time_timespec_to_us(&ts, &end_time); 477 | 478 | if (ret < 0) { 479 | ULOG_ERRNO("mp4_demux_open('%s')", -ret, input_file); 480 | status = EXIT_FAILURE; 481 | goto cleanup; 482 | } 483 | 484 | printf("File '%s'\n", input_file); 485 | printf("Processing time: %.2fms\n\n", 486 | (float)(end_time - start_time) / 1000.); 487 | print_info(demux); 488 | print_tracks(demux); 489 | print_metadata(demux); 490 | print_chapters(demux); 491 | if (log_frames) 492 | print_frames(demux); 493 | 494 | cleanup: 495 | if (json_file != NULL && (ret == 0 || force)) { 496 | printf("MP4 structure:\n"); 497 | struct json_object *json = NULL; 498 | err = mp4_file_to_json(input_file, true, &json); 499 | if (err < 0) 500 | ULOG_ERRNO("mp4_file_to_json", -err); 501 | if (json != NULL) { 502 | err = json_object_to_file_ext( 503 | json_file, json, pretty ? JSON_C_TO_STRING_PRETTY : 0); 504 | if (err < 0) 505 | ULOG_ERRNO("json_object_to_file_ext", -err); 506 | json_object_put(json); 507 | printf("\n"); 508 | } 509 | } 510 | if (demux != NULL) { 511 | ret = mp4_demux_close(demux); 512 | if (ret < 0) { 513 | ULOG_ERRNO("mp4_demux_close", -ret); 514 | status = EXIT_FAILURE; 515 | } 516 | } 517 | 518 | printf("%s\n", (status == EXIT_SUCCESS) ? "Done!" : "Failed!"); 519 | exit(status); 520 | } 521 | -------------------------------------------------------------------------------- /tools/mp4_mux.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018 Parrot Drones SAS 3 | * Copyright (c) 2016 Aurelien Barre 4 | * 5 | * Redistribution and use in source and binary forms, with or without 6 | * modification, are permitted provided that the following conditions are met: 7 | * * Redistributions of source code must retain the above copyright 8 | * notice, this list of conditions and the following disclaimer. 9 | * * Redistributions in binary form must reproduce the above copyright 10 | * notice, this list of conditions and the following disclaimer in the 11 | * documentation and/or other materials provided with the distribution. 12 | * * Neither the name of the copyright holders nor the names of its 13 | * contributors may be used to endorse or promote products derived from 14 | * this software without specific prior written permission. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef _FILE_OFFSET_BITS 29 | # define _FILE_OFFSET_BITS 64 30 | #endif 31 | 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | 43 | 44 | #define ULOG_TAG mp4_mux 45 | #include 46 | ULOG_DECLARE_TAG(mp4_mux); 47 | 48 | 49 | char *mdata_video_keys[] = {"com.parrot.thermal.metaversion", 50 | "com.parrot.thermal.alignment", 51 | "com.parrot.thermal.scalefactor"}; 52 | 53 | char *mdata_video_values[] = {"2", "0.000000,0.000000,0.000000", "1.836559"}; 54 | 55 | unsigned int mdata_video_count = SIZEOF_ARRAY(mdata_video_keys); 56 | 57 | static char *mdata_audio_keys[] = { 58 | "\xA9" 59 | "nam\0", 60 | "\xA9" 61 | "ART\0", 62 | "\xA9" 63 | "day\0", 64 | "\xA9" 65 | "too\0", 66 | "\xA9" 67 | "cmt\0"}; 68 | 69 | #if 0 70 | static char *mdata_audio_values[] = {"incredible machine", 71 | "3 years old scientist", 72 | "2019", 73 | "Lavf57.83.100", 74 | "just a random test video"}; 75 | #endif 76 | 77 | unsigned int mdata_audio_count = SIZEOF_ARRAY(mdata_audio_keys); 78 | 79 | 80 | int main(int argc, char *argv[]) 81 | { 82 | int ret; 83 | uint64_t now; 84 | 85 | if (argc < 3) { 86 | ULOGE("usage: %s input_file output_file", argv[0]); 87 | return 1; 88 | } 89 | 90 | const char *in = argv[1]; 91 | const char *out = argv[2]; 92 | 93 | ULOGI("demux %s and remux into %s", in, out); 94 | 95 | now = time(NULL); 96 | 97 | unsigned int sample_buffer_size = 5 * 1024 * 1024; 98 | unsigned int metadata_buffer_size = 1 * 1024 * 1024; 99 | 100 | uint8_t *sample_buffer = NULL; 101 | uint8_t *metadata_buffer = NULL; 102 | 103 | struct mp4_mux *mux = NULL; 104 | struct mp4_demux *demux = NULL; 105 | 106 | int ntracks; 107 | 108 | unsigned int meta_file_count; 109 | char **meta_file_keys; 110 | char **meta_file_vals; 111 | 112 | int videotrack = -1; 113 | int metatrack = -1; 114 | int audiotrack = -1; 115 | 116 | int current_track = -1; 117 | int has_more_audio = 0; 118 | int has_more_video = 0; 119 | 120 | int vs_count = 0; 121 | int as_count = 0; 122 | uint64_t step_ts = 0; 123 | const uint64_t increment_ts = 100000; /* 100ms */ 124 | unsigned int cover_size, j; 125 | enum mp4_metadata_cover_type cover_type; 126 | 127 | struct mp4_track_info info, video, audio; 128 | struct mp4_mux_config config = { 129 | .filename = out, 130 | .filemode = 0, 131 | .timescale = 30000, 132 | .creation_time = now, 133 | .modification_time = now, 134 | .tables_size_mbytes = MP4_MUX_DEFAULT_TABLE_SIZE_MB, 135 | .recovery.link_file = NULL, 136 | .recovery.tables_file = NULL, 137 | }; 138 | 139 | sample_buffer = malloc(sample_buffer_size); 140 | if (sample_buffer == NULL) { 141 | ULOG_ERRNO("malloc", ENOMEM); 142 | goto out; 143 | } 144 | metadata_buffer = malloc(metadata_buffer_size); 145 | if (metadata_buffer == NULL) { 146 | ULOG_ERRNO("malloc", ENOMEM); 147 | goto out; 148 | } 149 | 150 | ret = mp4_demux_open(in, &demux); 151 | if (ret < 0) { 152 | ULOG_ERRNO("mp4_demux_open", -ret); 153 | goto out; 154 | } 155 | 156 | ret = mp4_mux_open(&config, &mux); 157 | if (ret != 0) { 158 | ULOG_ERRNO("mp4_mux_open", -ret); 159 | goto out; 160 | } 161 | 162 | /* Get number of tracks in input file */ 163 | ntracks = mp4_demux_get_track_count(demux); 164 | ULOGD("%d tracks", ntracks); 165 | 166 | mp4_demux_get_metadata_strings( 167 | demux, &meta_file_count, &meta_file_keys, &meta_file_vals); 168 | for (unsigned int i = 0; i < meta_file_count; i++) { 169 | ULOGI("META: %s :: %s", meta_file_keys[i], meta_file_vals[i]); 170 | mp4_mux_add_file_metadata( 171 | mux, meta_file_keys[i], meta_file_vals[i]); 172 | } 173 | 174 | /* Find ID of first video / audio track */ 175 | for (int i = 0; i < ntracks; i++) { 176 | current_track = -1; 177 | ret = mp4_demux_get_track_info(demux, i, &info); 178 | if (ret != 0) { 179 | ULOG_ERRNO("mp4_demux_get_track_info(%d)", -ret, i); 180 | continue; 181 | } 182 | struct mp4_mux_track_params params = { 183 | .type = info.type, 184 | .name = info.name, 185 | .enabled = info.enabled, 186 | .in_movie = info.in_movie, 187 | .in_preview = info.in_preview, 188 | .timescale = info.timescale, 189 | .creation_time = info.creation_time, 190 | .modification_time = info.modification_time, 191 | }; 192 | if (info.type == MP4_TRACK_TYPE_VIDEO && videotrack == -1) { 193 | struct mp4_video_decoder_config vdc; 194 | video = info; 195 | videotrack = mp4_mux_add_track(mux, ¶ms); 196 | ret = mp4_demux_get_track_video_decoder_config( 197 | demux, info.id, &vdc); 198 | if (ret != 0) { 199 | ULOG_ERRNO( 200 | "mp4_demux_get_track" 201 | "_avc_decoder_config", 202 | -ret); 203 | goto out; 204 | } 205 | mp4_mux_track_set_video_decoder_config( 206 | mux, videotrack, &vdc); 207 | has_more_video = info.sample_count > 0; 208 | current_track = videotrack; 209 | } 210 | if (info.type == MP4_TRACK_TYPE_AUDIO && audiotrack == -1) { 211 | uint8_t *audioSpecificConfig; 212 | unsigned int asc_size; 213 | audio = info; 214 | audiotrack = mp4_mux_add_track(mux, ¶ms); 215 | ret = mp4_demux_get_track_audio_specific_config( 216 | demux, 217 | info.id, 218 | &audioSpecificConfig, 219 | &asc_size); 220 | if (ret != 0) { 221 | ULOG_ERRNO( 222 | "mp4_demux_get_track" 223 | "_audio_specific_config", 224 | -ret); 225 | goto out; 226 | } 227 | mp4_mux_track_set_audio_specific_config( 228 | mux, 229 | audiotrack, 230 | audioSpecificConfig, 231 | asc_size, 232 | info.audio_channel_count, 233 | info.audio_sample_size, 234 | info.audio_sample_rate); 235 | has_more_audio = info.sample_count > 0; 236 | current_track = audiotrack; 237 | } 238 | if (info.type == MP4_TRACK_TYPE_METADATA && metatrack == -1) { 239 | metatrack = mp4_mux_add_track(mux, ¶ms); 240 | mp4_mux_track_set_metadata_mime_type( 241 | mux, 242 | metatrack, 243 | info.content_encoding, 244 | info.mime_format); 245 | current_track = metatrack; 246 | } 247 | 248 | /* Add track metada */ 249 | if (current_track > 0) { 250 | 251 | unsigned int meta_count = 0; 252 | char **keys = NULL; 253 | char **values = NULL; 254 | 255 | ret = mp4_demux_get_track_metadata_strings( 256 | demux, info.id, &meta_count, &keys, &values); 257 | if (ret < 0) { 258 | ULOG_ERRNO( 259 | "mp4_demux_get_track_metadata_strings", 260 | -ret); 261 | continue; 262 | } 263 | 264 | #if 0 265 | /* If no metadata found for this track, we add some 266 | hardsetted ones for audio and video tracks */ 267 | /* TODO: remove once mux unit test exits */ 268 | if (meta_count == 0) { 269 | if (current_track == videotrack) { 270 | meta_count = mdata_video_count; 271 | keys = mdata_video_keys; 272 | values = mdata_video_values; 273 | } else if (current_track == audiotrack) { 274 | meta_count = mdata_audio_count; 275 | values = mdata_audio_values; 276 | keys = mdata_audio_keys; 277 | } 278 | } 279 | #endif 280 | 281 | for (j = 0; j < meta_count; j++) { 282 | if ((keys[j]) && (values[j])) { 283 | mp4_mux_add_track_metadata( 284 | mux, 285 | current_track, 286 | keys[j], 287 | values[j]); 288 | } 289 | } 290 | } 291 | } 292 | if (videotrack < 0) { 293 | ULOGE("no video track"); 294 | goto out; 295 | } 296 | 297 | /* Add track reference */ 298 | if (metatrack > 0) { 299 | ULOGI("metatrack = %d, videotrack = %d", metatrack, videotrack); 300 | ret = mp4_mux_add_ref_to_track(mux, metatrack, videotrack); 301 | if (ret != 0) { 302 | ULOG_ERRNO("mp4_mux_add_ref_to_track", -ret); 303 | goto out; 304 | } 305 | } 306 | 307 | /* Set cover, if available */ 308 | ret = mp4_demux_get_metadata_cover(demux, 309 | sample_buffer, 310 | sample_buffer_size, 311 | &cover_size, 312 | &cover_type); 313 | if (ret < 0) { 314 | ULOG_ERRNO("mp4_demux_get_metadata_cover", -ret); 315 | goto out; 316 | } 317 | if (cover_size > 0) 318 | mp4_mux_set_file_cover( 319 | mux, cover_type, sample_buffer, cover_size); 320 | 321 | /* Iterate over samples, 100ms at a time */ 322 | while (has_more_audio || has_more_video) { 323 | struct mp4_track_sample sample; 324 | struct mp4_mux_sample mux_sample; 325 | int lc_video = 0; 326 | int lc_audio = 0; 327 | step_ts += increment_ts; 328 | while (has_more_video) { 329 | ret = mp4_demux_get_track_sample(demux, 330 | video.id, 331 | 1, 332 | sample_buffer, 333 | sample_buffer_size, 334 | metadata_buffer, 335 | metadata_buffer_size, 336 | &sample); 337 | 338 | if (ret != 0 || sample.size == 0) { 339 | has_more_video = 0; 340 | break; 341 | } 342 | ULOGD("got a video sample [%d] of size %" PRIu32 343 | ", with meta of size %" PRIu32, 344 | vs_count++, 345 | sample.size, 346 | sample.metadata_size); 347 | lc_video++; 348 | 349 | mux_sample.buffer = sample_buffer; 350 | mux_sample.len = sample.size; 351 | mux_sample.sync = sample.sync; 352 | mux_sample.dts = sample.dts; 353 | mp4_mux_track_add_sample(mux, videotrack, &mux_sample); 354 | if (sample.metadata_size > 0 && metatrack != -1) { 355 | mux_sample.buffer = metadata_buffer; 356 | mux_sample.len = sample.metadata_size; 357 | mp4_mux_track_add_sample( 358 | mux, metatrack, &mux_sample); 359 | } 360 | if (mp4_sample_time_to_usec(sample.next_dts, 361 | info.timescale) > step_ts) 362 | break; 363 | } 364 | while (has_more_audio) { 365 | ret = mp4_demux_get_track_sample(demux, 366 | audio.id, 367 | 1, 368 | sample_buffer, 369 | sample_buffer_size, 370 | NULL, 371 | 0, 372 | &sample); 373 | if (ret != 0 || sample.size == 0) { 374 | has_more_audio = 0; 375 | break; 376 | } 377 | ULOGD("got an audio sample [%d] of size %" PRIu32, 378 | as_count++, 379 | sample.size); 380 | lc_audio++; 381 | 382 | mux_sample.buffer = sample_buffer; 383 | mux_sample.len = sample.size; 384 | mux_sample.sync = 0; 385 | mux_sample.dts = sample.dts; 386 | mp4_mux_track_add_sample(mux, audiotrack, &mux_sample); 387 | if (mp4_sample_time_to_usec(sample.next_dts, 388 | info.timescale) > step_ts) 389 | break; 390 | } 391 | ULOGD("added %d video samples and %d audio samples", 392 | lc_video, 393 | lc_audio); 394 | } 395 | 396 | if (as_count < 100 && vs_count < 100) 397 | mp4_mux_dump(mux); 398 | 399 | out: 400 | free(metadata_buffer); 401 | free(sample_buffer); 402 | mp4_mux_close(mux); 403 | mp4_demux_close(demux); 404 | 405 | return 0; 406 | } 407 | --------------------------------------------------------------------------------