1786 lines
67 KiB
C++
1786 lines
67 KiB
C++
//==================================================================//
|
||
/*
|
||
AtomicParsley - extracts.cpp
|
||
|
||
AtomicParsley is GPL software; you can freely distribute,
|
||
redistribute, modify & use under the terms of the GNU General
|
||
Public License; either version 2 or its successor.
|
||
|
||
AtomicParsley is distributed under the GPL "AS IS", without
|
||
any warranty; without the implied warranty of merchantability
|
||
or fitness for either an expressed or implied particular purpose.
|
||
|
||
Please see the included GNU General Public License (GPL) for
|
||
your rights and further details; see the file COPYING. If you
|
||
cannot, write to the Free Software Foundation, 59 Temple Place
|
||
Suite 330, Boston, MA 02111-1307, USA. Or www.fsf.org
|
||
|
||
Copyright (C) 2006-2007 puck_lock
|
||
with contributions from others; see the CREDITS file
|
||
*/
|
||
//==================================================================//
|
||
|
||
#include "AtomicParsley.h"
|
||
|
||
MovieInfo movie_info = {0};
|
||
iods_OD iods_info = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
// File reading routines //
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
/*----------------------
|
||
APar_skip_filler
|
||
isofile - the file to be scanned
|
||
start_position - the offset from the start of file where to commence possible
|
||
skipping
|
||
|
||
I can't remember where exactly I stumbled over what to skip, but this
|
||
touches on it:
|
||
http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt (not that
|
||
everything there is the gospel truth).
|
||
----------------------*/
|
||
uint8_t APar_skip_filler(FILE *isofile, uint32_t start_position) {
|
||
uint8_t skip_bytes = 0;
|
||
|
||
while (true) {
|
||
uint8_t eval_byte = APar_read8(isofile, start_position + skip_bytes);
|
||
|
||
if (eval_byte == 0x80 || eval_byte == 0x81 ||
|
||
eval_byte == 0xFE) { // seems sometimes QT writes 0x81
|
||
skip_bytes++;
|
||
} else {
|
||
break;
|
||
}
|
||
}
|
||
return skip_bytes;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
// string routines //
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
/*----------------------
|
||
uint32tochar4
|
||
lnum - the number to convert to a string
|
||
data - the string to hold the conversion
|
||
|
||
Returns a pointer to the originating string (used to print out a string;
|
||
different from the other function which converts returning void)
|
||
----------------------*/
|
||
char *uint32tochar4(uint32_t lnum, char *data) {
|
||
data[0] = (lnum >> 24) & 0xff;
|
||
data[1] = (lnum >> 16) & 0xff;
|
||
data[2] = (lnum >> 8) & 0xff;
|
||
data[3] = (lnum >> 0) & 0xff;
|
||
return data;
|
||
}
|
||
|
||
/*----------------------
|
||
purge_extraneous_characters
|
||
data - the string which may contain low or high ascii
|
||
|
||
Just change most non-textual characters (like a pesky new line char) to
|
||
something less objectionable - for stdout formatting only
|
||
----------------------*/
|
||
uint16_t purge_extraneous_characters(char *data) {
|
||
uint16_t purgings = 0;
|
||
uint16_t str_len = strlen(data);
|
||
for (uint16_t str_offset = 0; str_offset < str_len; str_offset++) {
|
||
if (data[str_offset] < 32 || data[str_offset] == 127) {
|
||
data[str_offset] = 19;
|
||
purgings++;
|
||
break;
|
||
}
|
||
}
|
||
return purgings;
|
||
}
|
||
|
||
void mem_append(const char *add_string, char *dest_string) {
|
||
uint8_t str_len = strlen(dest_string);
|
||
if (str_len > 0) {
|
||
memcpy(dest_string + str_len, ", ", 2);
|
||
memcpy(dest_string + str_len + 2, add_string, strlen(add_string));
|
||
} else {
|
||
memcpy(dest_string, add_string, strlen(add_string));
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
secsTOtime
|
||
seconds - duration in seconds as a floating point number
|
||
|
||
Convert decimal seconds to hh:mm:ss.milliseconds. Take the whole seconds and
|
||
manually separate out the hours, minutes and remaining seconds. For the
|
||
milliseconds, sprintf into a separate string because there doesn't seem to be a
|
||
way to print without the leading zero; so copy form that string the digits we
|
||
want then.
|
||
----------------------*/
|
||
char *secsTOtime(double seconds) {
|
||
ap_time time_duration = {0};
|
||
uint32_t whole_secs = (uint32_t)(seconds / 1);
|
||
|
||
time_duration.rem_millisecs = seconds - (double)whole_secs;
|
||
time_duration.hours = whole_secs / 3600;
|
||
whole_secs -= time_duration.hours * 3600;
|
||
time_duration.minutes = whole_secs / 60;
|
||
whole_secs -= time_duration.minutes * 60;
|
||
time_duration.seconds = whole_secs;
|
||
|
||
static char hhmmss_time[20];
|
||
memset(hhmmss_time, 0, 20);
|
||
char milli[5];
|
||
memset(milli, 0, 5);
|
||
|
||
uint8_t time_offset = 0;
|
||
if (time_duration.hours > 0) {
|
||
if (time_duration.hours < 10) {
|
||
sprintf(hhmmss_time, "0%u:", time_duration.hours);
|
||
} else {
|
||
sprintf(hhmmss_time, "%u:", time_duration.hours);
|
||
}
|
||
time_offset += 3;
|
||
}
|
||
if (time_duration.minutes > 0) {
|
||
if (time_duration.minutes < 10) {
|
||
sprintf(hhmmss_time + time_offset, "0%u:", time_duration.minutes);
|
||
} else {
|
||
sprintf(hhmmss_time + time_offset, "%u:", time_duration.minutes);
|
||
}
|
||
time_offset += 3;
|
||
} else {
|
||
memcpy(hhmmss_time + time_offset, "0:", 2);
|
||
time_offset += 2;
|
||
}
|
||
if (time_duration.seconds > 0) {
|
||
if (time_duration.seconds < 10) {
|
||
sprintf(hhmmss_time + time_offset, "0%u", time_duration.seconds);
|
||
} else {
|
||
sprintf(hhmmss_time + time_offset, "%u", time_duration.seconds);
|
||
}
|
||
time_offset += 2;
|
||
} else {
|
||
memcpy(hhmmss_time + time_offset, "0.", 2);
|
||
time_offset += 1;
|
||
}
|
||
|
||
sprintf(
|
||
milli,
|
||
"%.2lf",
|
||
time_duration.rem_millisecs); // sprintf the double float into a new
|
||
// string because I don't know if there is a
|
||
// way to print without a leading zero
|
||
memcpy(hhmmss_time + time_offset, milli + 1, 3);
|
||
|
||
return *&hhmmss_time;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
// Print Profile Info //
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
/*----------------------
|
||
APar_ShowMPEG4VisualProfileInfo
|
||
track_info - a pointer to the struct holding all the information
|
||
gathered as a single 'trak' atom was traversed
|
||
|
||
If a movie-level iods (containing profiles on a movie-level basis), prefer
|
||
that mechanism for choosing which profile, otherwise fall back to 'esds'
|
||
profiles. Much of this was garnered from ISO 14496-2:2001 - up to 'Simple
|
||
Studio'.
|
||
----------------------*/
|
||
void APar_ShowMPEG4VisualProfileInfo(TrackInfo *track_info) {
|
||
fprintf(stdout, " MPEG-4 Visual ");
|
||
uint8_t mp4v_profile = 0;
|
||
if (movie_info.contains_iods) {
|
||
mp4v_profile = iods_info.video_profile_level;
|
||
} else {
|
||
mp4v_profile = track_info->m4v_profile;
|
||
}
|
||
|
||
// unparalleled joy - Annex G table g1 - a binary listing (this from
|
||
// 14496-2:2001)
|
||
if (mp4v_profile == 0x01) {
|
||
fprintf(stdout, "Simple Profile, Level 1"); // 00000001
|
||
} else if (mp4v_profile == 0x02) {
|
||
fprintf(stdout, "Simple Profile, Level 2"); // 00000010
|
||
} else if (mp4v_profile == 0x03) {
|
||
fprintf(stdout,
|
||
"Simple Profile, Level 3"); // most files will land here //00000011
|
||
|
||
} else if (mp4v_profile == 0x08) { // Compressor can create these in 3gp files
|
||
fprintf(stdout, "Simple Profile, Level 0"); // ISO 14496-2:2004(e)
|
||
// //00001000
|
||
|
||
// Reserved 00000100 - 00000111
|
||
} else if (mp4v_profile == 0x10) {
|
||
fprintf(stdout, "Simple Scalable Profile, Level 0"); // 00010000
|
||
} else if (mp4v_profile == 0x11) {
|
||
fprintf(stdout, "Simple Scalable Profile, Level 1"); // 00010001
|
||
} else if (mp4v_profile == 0x12) {
|
||
fprintf(stdout, "Simple Scalable Profile, Level 2"); // 00010010
|
||
|
||
// Reserved 00010011 - 00100000
|
||
} else if (mp4v_profile == 0x21) {
|
||
fprintf(stdout, "Core Profile, Level 1"); // 00100001
|
||
} else if (mp4v_profile == 0x22) {
|
||
fprintf(stdout, "Core Profile, Level 2"); // 00100010
|
||
|
||
// Reserved 00100011 - 00110001
|
||
} else if (mp4v_profile == 0x32) {
|
||
fprintf(stdout, "Main Profile, Level 2"); // 00110010
|
||
} else if (mp4v_profile == 0x33) {
|
||
fprintf(stdout, "Main Profile, Level 3"); // 00110011
|
||
} else if (mp4v_profile == 0x34) {
|
||
fprintf(stdout, "Main Profile, Level 4"); // 00110100
|
||
|
||
// Reserved 00110101 - 01000001
|
||
} else if (mp4v_profile == 0x42) {
|
||
fprintf(stdout, "N-bit Profile, Level 2"); // 01000010
|
||
|
||
// Reserved 01000011 - 01010000
|
||
} else if (mp4v_profile == 0x51) {
|
||
fprintf(stdout, "Scalable Texture Profile, Level 1"); // 01010001
|
||
|
||
// Reserved 01010010 - 01100000
|
||
} else if (mp4v_profile == 0x61) {
|
||
fprintf(stdout, "Simple Face Animation, Level 1"); // 01100001
|
||
} else if (mp4v_profile == 0x62) {
|
||
fprintf(stdout, "Simple Face Animation, Level 2"); // 01100010
|
||
|
||
} else if (mp4v_profile == 0x63) {
|
||
fprintf(stdout, "Simple FBA Profile, Level 1"); // 01100011
|
||
} else if (mp4v_profile == 0x64) {
|
||
fprintf(stdout, "Simple FBA Profile, Level 2"); // 01100100
|
||
|
||
// Reserved 01100101 - 01110000
|
||
} else if (mp4v_profile == 0x71) {
|
||
fprintf(stdout, "Basic Animated Texture Profile, Level 1"); // 01110001
|
||
} else if (mp4v_profile == 0x72) {
|
||
fprintf(stdout, "Basic Animated Texture Profile, Level 2"); // 01110010
|
||
|
||
// Reserved 01110011 - 10000000
|
||
} else if (mp4v_profile == 0x81) {
|
||
fprintf(stdout, "Hybrid Profile, Level 1"); // 10000001
|
||
} else if (mp4v_profile == 0x82) {
|
||
fprintf(stdout, "Hybrid Profile, Level 2"); // 10000010
|
||
|
||
// Reserved 10000011 - 10010000
|
||
} else if (mp4v_profile == 0x91) {
|
||
fprintf(stdout, "Advanced Real Time Simple Profile, Level 1"); // 10010001
|
||
} else if (mp4v_profile == 0x92) {
|
||
fprintf(stdout, "Advanced Real Time Simple Profile, Level 2"); // 10010010
|
||
} else if (mp4v_profile == 0x93) {
|
||
fprintf(stdout, "Advanced Real Time Simple Profile, Level 3"); // 10010011
|
||
} else if (mp4v_profile == 0x94) {
|
||
fprintf(stdout, "Advanced Real Time Simple Profile, Level 4"); // 10010100
|
||
|
||
// Reserved 10010101 - 10100000
|
||
} else if (mp4v_profile == 0xA1) {
|
||
fprintf(stdout, "Core Scalable Profile, Level 1"); // 10100001
|
||
} else if (mp4v_profile == 0xA2) {
|
||
fprintf(stdout, "Core Scalable Profile, Level 2"); // 10100010
|
||
} else if (mp4v_profile == 0xA3) {
|
||
fprintf(stdout, "Core Scalable Profile, Level 3"); // 10100011
|
||
|
||
// Reserved 10100100 - 10110000
|
||
} else if (mp4v_profile == 0xB1) {
|
||
fprintf(stdout, "Advanced Coding Efficiency Profile, Level 1"); // 10110001
|
||
} else if (mp4v_profile == 0xB2) {
|
||
fprintf(stdout, "Advanced Coding Efficiency Profile, Level 2"); // 10110010
|
||
} else if (mp4v_profile == 0xB3) {
|
||
fprintf(stdout, "Advanced Coding Efficiency Profile, Level 3"); // 10110011
|
||
} else if (mp4v_profile == 0xB4) {
|
||
fprintf(stdout, "Advanced Coding Efficiency Profile, Level 4"); // 10110100
|
||
|
||
// Reserved 10110101 <20> 11000000
|
||
} else if (mp4v_profile == 0xC1) {
|
||
fprintf(stdout, "Advanced Core Profile, Level 1"); // 11000001
|
||
} else if (mp4v_profile == 0xC2) {
|
||
fprintf(stdout, "Advanced Core Profile, Level 2"); // 11000010
|
||
|
||
// Reserved 11000011 <20> 11010000
|
||
} else if (mp4v_profile == 0xD1) {
|
||
fprintf(stdout, "Advanced Scalable Texture, Level 1"); // 11010001
|
||
} else if (mp4v_profile == 0xD2) {
|
||
fprintf(stdout, "Advanced Scalable Texture, Level 2"); // 11010010
|
||
} else if (mp4v_profile == 0xD2) {
|
||
fprintf(stdout, "Advanced Scalable Texture, Level 3"); // 11010011
|
||
|
||
// from a draft document - 1999 (earlier than the 2000 above!!)
|
||
} else if (mp4v_profile == 0xE1) {
|
||
fprintf(stdout, "Simple Studio Profile, Level 1"); // 11100001
|
||
} else if (mp4v_profile == 0xE2) {
|
||
fprintf(stdout, "Simple Studio Profile, Level 2"); // 11100010
|
||
} else if (mp4v_profile == 0xE3) {
|
||
fprintf(stdout, "Simple Studio Profile, Level 3"); // 11100011
|
||
} else if (mp4v_profile == 0xE4) {
|
||
fprintf(stdout, "Simple Studio Profile, Level 4"); // 11100100
|
||
|
||
} else if (mp4v_profile == 0xE5) {
|
||
fprintf(stdout, "Core Studio Profile, Level 1"); // 11100101
|
||
} else if (mp4v_profile == 0xE6) {
|
||
fprintf(stdout, "Core Studio Profile, Level 2"); // 11100110
|
||
} else if (mp4v_profile == 0xE7) {
|
||
fprintf(stdout, "Core Studio Profile, Level 3"); // 11100111
|
||
} else if (mp4v_profile == 0xE8) {
|
||
fprintf(stdout, "Core Studio Profile, Level 4"); // 11101000
|
||
|
||
// Reserved 11101001 - 11101111
|
||
// ISO 14496-2:2004(e)
|
||
} else if (mp4v_profile == 0xF0) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 0"); // 11110000
|
||
} else if (mp4v_profile == 0xF1) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 1"); // 11110001
|
||
} else if (mp4v_profile == 0xF2) {
|
||
fprintf(
|
||
stdout,
|
||
"Advanced Simple Profile, Level 2"); // 11110010 ////3gp files that QT
|
||
// says is H.263 have esds to 0xF2
|
||
// & their ObjectType set to 0x20
|
||
// (mpeg-4 visual)
|
||
////...and its been figured out -
|
||
/// FILE EXTENSION of all things
|
||
/// determines mpeg-4 ASP or H.263
|
||
} else if (mp4v_profile == 0xF3) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 3"); // 11110011
|
||
} else if (mp4v_profile == 0xF4) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 4"); // 11110100
|
||
} else if (mp4v_profile == 0xF5) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 5"); // 11110101
|
||
|
||
// Reserved 11110110
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Advanced Simple Profile, Level 3b"); // 11110111
|
||
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 0"); // 11111000
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 1"); // 11111001
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 2"); // 11111010
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 3"); // 11111011
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 4"); // 11111100
|
||
} else if (mp4v_profile == 0xF7) {
|
||
fprintf(stdout, "Fine Granularity Scalable Profile/Level 5"); // 11111101
|
||
|
||
// Reserved 11111110
|
||
// Reserved for Escape 11111111
|
||
|
||
} else {
|
||
fprintf(stdout, "Unknown profile: 0x%X", mp4v_profile);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_ShowMPEG4AACProfileInfo
|
||
track_info - a pointer to the struct holding all the information
|
||
gathered as a single 'trak' atom was traversed
|
||
|
||
----------------------*/
|
||
void APar_ShowMPEG4AACProfileInfo(TrackInfo *track_info) {
|
||
if (track_info->descriptor_object_typeID == 1) {
|
||
fprintf(stdout, " MPEG-4 AAC Main Profile");
|
||
} else if (track_info->descriptor_object_typeID == 2) {
|
||
fprintf(
|
||
stdout,
|
||
" MPEG-4 AAC Low Complexity/LC Profile"); // most files will land here
|
||
} else if (track_info->descriptor_object_typeID == 3) {
|
||
fprintf(stdout, " MPEG-4 AAC Scaleable Sample Rate/SSR Profile");
|
||
} else if (track_info->descriptor_object_typeID == 4) {
|
||
fprintf(stdout, " MPEG-4 AAC Long Term Prediction Profile");
|
||
} else if (track_info->descriptor_object_typeID == 5) {
|
||
fprintf(stdout, " MPEG-4 AAC High Efficiency/HE Profile");
|
||
} else if (track_info->descriptor_object_typeID == 6) {
|
||
fprintf(stdout, " MPEG-4 AAC Scalable Profile");
|
||
} else if (track_info->descriptor_object_typeID == 7) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Transform domain Weighted INterleave Vector "
|
||
"Quantization/TwinVQ Profile");
|
||
} else if (track_info->descriptor_object_typeID == 8) {
|
||
fprintf(stdout, " MPEG-4 AAC Code Excited Linear Predictive/CELP Profile");
|
||
} else if (track_info->descriptor_object_typeID == 9) {
|
||
fprintf(stdout, " MPEG-4 AAC HVXC Profile");
|
||
|
||
} else if (track_info->descriptor_object_typeID == 12) {
|
||
fprintf(stdout, " MPEG-4 AAC TTSI Profile");
|
||
} else if (track_info->descriptor_object_typeID == 13) {
|
||
fprintf(stdout, " MPEG-4 AAC Main Synthesis Profile");
|
||
} else if (track_info->descriptor_object_typeID == 14) {
|
||
fprintf(stdout, " MPEG-4 AAC Wavetable Synthesis Profile");
|
||
} else if (track_info->descriptor_object_typeID == 15) {
|
||
fprintf(stdout, " MPEG-4 AAC General MIDI Profile");
|
||
} else if (track_info->descriptor_object_typeID == 16) {
|
||
fprintf(stdout, " MPEG-4 AAC Algorithmic Synthesis & Audio FX Profile");
|
||
} else if (track_info->descriptor_object_typeID == 17) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC AAC Low Complexity/LC (+error recovery) Profile");
|
||
|
||
} else if (track_info->descriptor_object_typeID == 19) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Long Term Prediction (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 20) {
|
||
fprintf(stdout, " MPEG-4 AAC Scalable (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 21) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Transform domain Weighted INterleave Vector "
|
||
"Quantization/TwinVQ (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 22) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Bit Sliced Arithmetic Coding/BSAC (+error "
|
||
"recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 23) {
|
||
fprintf(stdout, " MPEG-4 AAC Low Delay/LD (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 24) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Code Excited Linear Predictive/CELP (+error "
|
||
"recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 25) {
|
||
fprintf(stdout, " MPEG-4 AAC HXVC (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 26) {
|
||
fprintf(stdout,
|
||
" MPEG-4 AAC Harmonic and Individual Lines plus "
|
||
"Noise/HILN (+error recovery) Profile");
|
||
} else if (track_info->descriptor_object_typeID == 27) {
|
||
fprintf(stdout, " MPEG-4 AAC Parametric (+error recovery) Profile");
|
||
|
||
} else if (track_info->descriptor_object_typeID == 31) {
|
||
fprintf(
|
||
stdout,
|
||
" MPEG-4 ALS Audio Lossless Coding"); // I think that mp4alsRM18 writes
|
||
// the channels wrong after
|
||
// objectedID: 0xF880 has 0
|
||
// channels; 0xF890 is 2ch
|
||
} else {
|
||
fprintf(stdout,
|
||
" MPEG-4 Unknown profile: 0x%X",
|
||
track_info->descriptor_object_typeID);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_ShowObjectProfileInfo
|
||
track_type - broadly used to determine what types of information (like
|
||
channels or avc1 profiles) to display track_info - a pointer to the struct
|
||
holding all the information gathered as a single 'trak' atom was traversed
|
||
|
||
Based on the ObjectTypeIndication in 'esds', show the type of track. For
|
||
mpeg-4 audio & mpeg-4 visual are handled in a subroutine because there are so
|
||
many enumerations. avc1 contains 'avcC' which supports a different mechanism.
|
||
----------------------*/
|
||
void APar_ShowObjectProfileInfo(uint8_t track_type, TrackInfo *track_info) {
|
||
if (track_info->contains_esds) {
|
||
switch (track_info->ObjectTypeIndication) {
|
||
// 0x00 es Lambada/Verboten/Forbidden
|
||
case 0x01:
|
||
case 0x02: {
|
||
fprintf(stdout, " MPEG-4 Systems (BIFS/ObjDesc)");
|
||
break;
|
||
}
|
||
case 0x03: {
|
||
fprintf(stdout, " Interaction Stream");
|
||
break;
|
||
}
|
||
case 0x04: {
|
||
fprintf(stdout, " MPEG-4 Systems Extended BIFS");
|
||
break;
|
||
}
|
||
case 0x05: {
|
||
fprintf(stdout, " MPEG-4 Systems AFX");
|
||
break;
|
||
}
|
||
case 0x06: {
|
||
fprintf(stdout, " Font Data Stream");
|
||
break;
|
||
}
|
||
case 0x08: {
|
||
fprintf(stdout, " Synthesized Texture Stream");
|
||
break;
|
||
}
|
||
case 0x07: {
|
||
fprintf(stdout, " Streaming Text Stream");
|
||
break;
|
||
}
|
||
// 0x09-0x1F reserved
|
||
case 0x20: {
|
||
APar_ShowMPEG4VisualProfileInfo(track_info);
|
||
break;
|
||
}
|
||
|
||
case 0x40: { // vererable mpeg-4 aac
|
||
APar_ShowMPEG4AACProfileInfo(track_info);
|
||
break;
|
||
}
|
||
|
||
// 0x41-0x5F reserved
|
||
case 0x60: {
|
||
fprintf(stdout,
|
||
" MPEG-2 Visual Simple Profile"); //'Visual ISO/IEC 13818-2
|
||
// Simple Profile'
|
||
break;
|
||
}
|
||
case 0x61: {
|
||
fprintf(stdout, " MPEG-2 Visual Main Profile"); //'Visual ISO/IEC 13818-2
|
||
// Main Profile'
|
||
break;
|
||
}
|
||
case 0x62: {
|
||
fprintf(
|
||
stdout,
|
||
" MPEG-2 Visual SNR Profile"); //'Visual ISO/IEC 13818-2 SNR Profile'
|
||
break;
|
||
}
|
||
case 0x63: {
|
||
fprintf(stdout,
|
||
" MPEG-2 Visual Spatial Profile"); //'Visual ISO/IEC 13818-2
|
||
// Spatial Profile'
|
||
break;
|
||
}
|
||
case 0x64: {
|
||
fprintf(stdout, " MPEG-2 Visual High Profile"); //'Visual ISO/IEC 13818-2
|
||
// High Profile'
|
||
break;
|
||
}
|
||
case 0x65: {
|
||
fprintf(stdout, " MPEG-2 Visual 4:2:2 Profile"); //'Visual ISO/IEC
|
||
// 13818-2 422 Profile'
|
||
break;
|
||
}
|
||
case 0x66: {
|
||
fprintf(
|
||
stdout,
|
||
" MPEG-2 AAC Main Profile"); //'Audio ISO/IEC 13818-7 Main Profile'
|
||
break;
|
||
}
|
||
case 0x67: {
|
||
fprintf(stdout,
|
||
" MPEG-2 AAC Low Complexity Profile"); // Audio ISO/IEC 13818-7
|
||
// LowComplexity Profile
|
||
break;
|
||
}
|
||
case 0x68: {
|
||
fprintf(
|
||
stdout,
|
||
" MPEG-2 AAC Scaleable Sample Rate Profile"); //'Audio ISO/IEC
|
||
// 13818-7 Scaleable
|
||
// Sampling Rate
|
||
// Profile'
|
||
break;
|
||
}
|
||
case 0x69: {
|
||
fprintf(stdout, " MPEG-2 Audio"); //'Audio ISO/IEC 13818-3'
|
||
break;
|
||
}
|
||
case 0x6A: {
|
||
fprintf(stdout, " MPEG-1 Visual"); //'Visual ISO/IEC 11172-2'
|
||
break;
|
||
}
|
||
case 0x6B: {
|
||
fprintf(stdout, " MPEG-1 Audio"); //'Audio ISO/IEC 11172-3'
|
||
break;
|
||
}
|
||
case 0x6C: {
|
||
fprintf(stdout, " JPEG"); //'Visual ISO/IEC 10918-1'
|
||
break;
|
||
}
|
||
case 0x6D: {
|
||
fprintf(stdout, " PNG"); // http://www.mp4ra.org/object.html
|
||
break;
|
||
}
|
||
case 0x6E: {
|
||
fprintf(stdout, " JPEG2000"); //'Visual ISO/IEC 15444-1'
|
||
break;
|
||
}
|
||
case 0xA0: {
|
||
fprintf(stdout, " 3GPP2 EVRC Voice"); // http://www.mp4ra.org/object.html
|
||
break;
|
||
}
|
||
case 0xA1: {
|
||
fprintf(stdout, " 3GPP2 SMV Voice"); // http://www.mp4ra.org/object.html
|
||
break;
|
||
}
|
||
case 0xA2: {
|
||
fprintf(
|
||
stdout,
|
||
" 3GPP2 Compact Multimedia Format"); // http://www.mp4ra.org/object.html
|
||
break;
|
||
}
|
||
|
||
// 0xC0-0xE0 user private
|
||
case 0xE1: {
|
||
fprintf(stdout,
|
||
" 3GPP2 QCELP (14K Voice)"); // http://www.mp4ra.org/object.html
|
||
break;
|
||
}
|
||
// 0xE2-0xFE user private
|
||
// 0xFF no object type specified
|
||
|
||
default: {
|
||
// so many profiles, so little desire to list them all (in 14496-2 which I
|
||
// don't have)
|
||
if (movie_info.contains_iods && iods_info.audio_profile == 0xFE) {
|
||
fprintf(stdout,
|
||
" Private user object: 0x%X",
|
||
track_info->ObjectTypeIndication);
|
||
} else {
|
||
fprintf(
|
||
stdout,
|
||
" Object Type Indicator: 0x%X Description Ojbect Type ID: 0x%X\n",
|
||
track_info->ObjectTypeIndication,
|
||
track_info->descriptor_object_typeID);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
} else if (track_type == AVC1_TRACK) {
|
||
// profiles & levels are in the 14496-10 pdf (which I don't have access to),
|
||
// so... http://lists.mpegif.org/pipermail/mp4-tech/2006-January/006255.html
|
||
// http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html
|
||
// 66=baseline, 77=main, 88=extended; 100=High, 110=High 10, 122=High 4:2:2,
|
||
// 144=High 4:4:4
|
||
|
||
switch (track_info->profile) {
|
||
case 66: {
|
||
fprintf(stdout, " AVC Baseline Profile");
|
||
break;
|
||
}
|
||
case 77: {
|
||
fprintf(stdout, " AVC Main Profile");
|
||
break;
|
||
}
|
||
case 88: {
|
||
fprintf(stdout, " AVC Extended Profile");
|
||
break;
|
||
}
|
||
case 100: {
|
||
fprintf(stdout, " AVC High Profile");
|
||
break;
|
||
}
|
||
case 110: {
|
||
fprintf(stdout, " AVC High 10 Profile");
|
||
break;
|
||
}
|
||
case 122: {
|
||
fprintf(stdout, " AVC High 4:2:2 Profile");
|
||
break;
|
||
}
|
||
case 144: {
|
||
fprintf(stdout, " AVC High 4:4:4 Profile");
|
||
break;
|
||
}
|
||
default: {
|
||
fprintf(stdout, " Unknown Profile: %u", track_info->profile);
|
||
break;
|
||
}
|
||
} // end profile switch
|
||
|
||
// Don't have access to levels either, but working off of:
|
||
// http://iphome.hhi.de/suehring/tml/doc/lenc/html/configfile_8c-source.html
|
||
|
||
// and the 15 levels it says here:
|
||
// http://www.chiariglione.org/mpeg/technologies/mp04-avc/index.htm (1b in
|
||
// http://en.wikipedia.org/wiki/H.264 seems nonsensical) working backwards,
|
||
// we get... a simple 2 digit number (with '20' just drop the 0; with 21,
|
||
// put in a decimal)
|
||
if (track_info->level > 0) {
|
||
switch (track_info->level) {
|
||
case 10:
|
||
case 20:
|
||
case 30:
|
||
case 40:
|
||
case 50: {
|
||
fprintf(stdout, ", Level %u", track_info->level / 10);
|
||
break;
|
||
}
|
||
case 11:
|
||
case 12:
|
||
case 13:
|
||
case 21:
|
||
case 22:
|
||
case 31:
|
||
case 32:
|
||
case 41:
|
||
case 42:
|
||
case 51: {
|
||
fprintf(stdout,
|
||
", Level %u.%u",
|
||
track_info->level / 10,
|
||
track_info->level % 10);
|
||
break;
|
||
}
|
||
default: {
|
||
fprintf(stdout,
|
||
", Unknown level %u.%u",
|
||
track_info->level / 10,
|
||
track_info->level % 10);
|
||
}
|
||
|
||
} // end switch
|
||
} // end level if
|
||
} else if (track_type == S_AMR_TRACK) {
|
||
char *amr_modes = (char *)calloc(1, sizeof(char) * 500);
|
||
if (track_info->track_codec == 0x73616D72 ||
|
||
track_info->track_codec == 0x73617762) {
|
||
if (track_info->amr_modes & 0x0001)
|
||
mem_append("0", amr_modes);
|
||
if (track_info->amr_modes & 0x0002)
|
||
mem_append("1", amr_modes);
|
||
if (track_info->amr_modes & 0x0004)
|
||
mem_append("2", amr_modes);
|
||
if (track_info->amr_modes & 0x0008)
|
||
mem_append("3", amr_modes);
|
||
if (track_info->amr_modes & 0x0010)
|
||
mem_append("4", amr_modes);
|
||
if (track_info->amr_modes & 0x0020)
|
||
mem_append("5", amr_modes);
|
||
if (track_info->amr_modes & 0x0040)
|
||
mem_append("6", amr_modes);
|
||
if (track_info->amr_modes & 0x0080)
|
||
mem_append("7", amr_modes);
|
||
if (track_info->amr_modes & 0x0100)
|
||
mem_append("8", amr_modes);
|
||
if (strlen(amr_modes) == 0)
|
||
memcpy(amr_modes, "none", 4);
|
||
} else if (track_info->track_codec == 0x73766D72) {
|
||
if (track_info->amr_modes & 0x0001)
|
||
mem_append("VMR-WB Mode 0, ", amr_modes);
|
||
if (track_info->amr_modes & 0x0002)
|
||
mem_append("VMR-WB Mode 1, ", amr_modes);
|
||
if (track_info->amr_modes & 0x0004)
|
||
mem_append("VMR-WB Mode 2, ", amr_modes);
|
||
if (track_info->amr_modes & 0x0008)
|
||
mem_append("VMR-WB Mode 3 (AMR-WB interoperable mode), ", amr_modes);
|
||
if (track_info->amr_modes & 0x0010)
|
||
mem_append("VMR-WB Mode 4, ", amr_modes);
|
||
if (track_info->amr_modes & 0x0020)
|
||
mem_append("VMR-WB Mode 2 with maximum half-rate, ", amr_modes);
|
||
if (track_info->amr_modes & 0x0040)
|
||
mem_append("VMR-WB Mode 4 with maximum half-rate, ", amr_modes);
|
||
uint16_t amr_modes_len = strlen(amr_modes);
|
||
if (amr_modes_len > 0)
|
||
memset(amr_modes + (amr_modes_len - 1), 0, 2);
|
||
}
|
||
|
||
if (track_info->track_codec == 0x73616D72) { // samr
|
||
fprintf(stdout,
|
||
" AMR Narrow-Band. Modes: %s. Encoder vendor code: %s\n",
|
||
amr_modes,
|
||
track_info->encoder_name);
|
||
} else if (track_info->track_codec == 0x73617762) { // sawb
|
||
fprintf(stdout,
|
||
" AMR Wide-Band. Modes: %s. Encoder vendor code: %s\n",
|
||
amr_modes,
|
||
track_info->encoder_name);
|
||
} else if (track_info->track_codec == 0x73617770) { // sawp
|
||
fprintf(stdout,
|
||
" AMR Wide-Band WB+. Encoder vendor code: %s\n",
|
||
track_info->encoder_name);
|
||
} else if (track_info->track_codec == 0x73766D72) { // svmr
|
||
fprintf(stdout,
|
||
" AMR VBR Wide-Band. Encoder vendor code: %s\n",
|
||
track_info->encoder_name);
|
||
}
|
||
free(amr_modes);
|
||
amr_modes = NULL;
|
||
|
||
} else if (track_type == EVRC_TRACK) {
|
||
fprintf(stdout,
|
||
" EVRC (Enhanced Variable Rate Coder). Encoder vendor code: %s\n",
|
||
track_info->encoder_name);
|
||
|
||
} else if (track_type == QCELP_TRACK) {
|
||
fprintf(stdout,
|
||
" QCELP (Qualcomm Code Excited Linear Prediction). Encoder vendor "
|
||
"code: %s\n",
|
||
track_info->encoder_name);
|
||
|
||
} else if (track_type == S263_TRACK) {
|
||
if (track_info->profile == 0) {
|
||
fprintf(stdout,
|
||
" H.263 Baseline Profile, Level %u. Encoder vendor code: %s",
|
||
track_info->level,
|
||
track_info->encoder_name);
|
||
} else {
|
||
fprintf(stdout,
|
||
" H.263 Profile: %u, Level %u. Encoder vendor code: %s",
|
||
track_info->profile,
|
||
track_info->level,
|
||
track_info->encoder_name);
|
||
}
|
||
}
|
||
if (track_type == AUDIO_TRACK) {
|
||
if (track_info->section5_length == 0) {
|
||
fprintf(stdout, " channels: (%u)\n", track_info->channels);
|
||
} else {
|
||
fprintf(stdout, " channels: [%u]\n", track_info->channels);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
// Movie & Track Level Info //
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
/*----------------------
|
||
calcuate_sample_size
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
stsz_atom - the atom number of the stsz atom
|
||
|
||
This will get aggregate a number of the size of all chunks in the track. The
|
||
stsz atom holds a table of these sizes along with a count of how many there are.
|
||
Loop through the count, summing in the sizes. This is called
|
||
called for all tracks, but used only when a hardcoded bitrate (in esds) isn't
|
||
found (like avc1) and is displayed with the asterisk* at track-level.
|
||
----------------------*/
|
||
uint64_t
|
||
calcuate_sample_size(char *uint32_buffer, FILE *isofile, short stsz_atom) {
|
||
uint32_t sample_size = 0;
|
||
uint32_t sample_count = 0;
|
||
uint64_t total_size = 0;
|
||
|
||
sample_size = APar_read32(
|
||
uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 12);
|
||
sample_count = APar_read32(
|
||
uint32_buffer, isofile, parsedAtoms[stsz_atom].AtomicStart + 16);
|
||
|
||
if (sample_size == 0) {
|
||
for (uint64_t atom_offset = 20;
|
||
atom_offset < parsedAtoms[stsz_atom].AtomicLength;
|
||
atom_offset += 4) {
|
||
total_size +=
|
||
APar_read32(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[stsz_atom].AtomicStart + atom_offset);
|
||
}
|
||
} else {
|
||
total_size = sample_size * sample_count;
|
||
}
|
||
return total_size;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_TrackLevelInfo
|
||
track - pointer to a struct providing the track we are looking for
|
||
track_search_atom_name - the name of the atom to be found in this track
|
||
|
||
Looping through the atoms one by one, note a 'trak' atom. If we are looking
|
||
for the total amount of tracks (by setting the track_num to 0), simply return
|
||
the count of tracks back in the same struct to that later functions can loop
|
||
through each track individually, looking for a specific atom. If track's
|
||
track_num is a non-zero number, then find that atom that *matches* the atom
|
||
name. Set track's track_atom to that atom for later use
|
||
----------------------*/
|
||
void APar_TrackLevelInfo(Trackage *track, const char *track_search_atom_name) {
|
||
uint8_t track_tally = 0;
|
||
short iter = 0;
|
||
|
||
while (parsedAtoms[iter].NextAtomNumber != 0) {
|
||
|
||
if (strncmp(parsedAtoms[iter].AtomicName, "trak", 4) == 0) {
|
||
track_tally += 1;
|
||
if (track->track_num == 0) {
|
||
track->total_tracks += 1;
|
||
|
||
} else if (track->track_num == track_tally) {
|
||
|
||
short next_atom = parsedAtoms[iter].NextAtomNumber;
|
||
while (parsedAtoms[next_atom].AtomicLevel >
|
||
parsedAtoms[iter].AtomicLevel) {
|
||
|
||
if (strncmp(parsedAtoms[next_atom].AtomicName,
|
||
track_search_atom_name,
|
||
4) == 0) {
|
||
|
||
track->track_atom = parsedAtoms[next_atom].AtomicNumber;
|
||
return;
|
||
} else {
|
||
next_atom = parsedAtoms[next_atom].NextAtomNumber;
|
||
}
|
||
if (parsedAtoms[next_atom].AtomicLevel ==
|
||
parsedAtoms[iter].AtomicLevel) {
|
||
track->track_atom = 0;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
iter = parsedAtoms[iter].NextAtomNumber;
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_ExtractChannelInfo
|
||
isofile - the file to be scanned
|
||
pos - the position within the file that carries the channel info (in
|
||
esds)
|
||
|
||
The channel info in esds is bitpacked, so read it in isolation and shift the
|
||
bits around to get at it
|
||
----------------------*/
|
||
uint8_t APar_ExtractChannelInfo(FILE *isofile, uint32_t pos) {
|
||
uint8_t packed_channels = APar_read8(isofile, pos);
|
||
uint8_t unpacked_channels =
|
||
(packed_channels << 1); // just shift the first bit off the table
|
||
unpacked_channels =
|
||
(unpacked_channels >> 4); // and slide it on over back on the uint8_t
|
||
return unpacked_channels;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_Extract_iods_Info
|
||
isofile - the file to be scanned
|
||
iods_atom - a pointer to the struct that will store the profile levels
|
||
found in iods
|
||
|
||
'iods' info mostly comes from:
|
||
http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt Just as
|
||
'esds' has 'filler' bytes to skip over, so does this. Mercifully, the profiles
|
||
come one right after another. The only problem is that in many files, the iods
|
||
profiles don't match the esds profiles. This is resolved by ignoring it for
|
||
audio (mostly, unless is 0xFE user defined). For MPEG-4 Visual, it is preferred
|
||
over 'esds' (occurs in APar_ShowMPEG4VisualProfileInfo); for all other video
|
||
types it is ignored.
|
||
----------------------*/
|
||
void APar_Extract_iods_Info(FILE *isofile, AtomicInfo *iods_atom) {
|
||
uint64_t iods_offset = iods_atom->AtomicStart + 8;
|
||
if (iods_atom->AtomicVerFlags == 0 &&
|
||
APar_read8(isofile, iods_offset + 4) == 0x10) {
|
||
iods_offset += 5;
|
||
iods_offset += APar_skip_filler(isofile, iods_offset);
|
||
uint8_t iods_objdescrip_len = APar_read8(isofile, iods_offset);
|
||
iods_offset++;
|
||
if (iods_objdescrip_len >= 7) {
|
||
iods_info.od_profile_level = APar_read8(isofile, iods_offset + 2);
|
||
iods_info.scene_profile_level = APar_read8(isofile, iods_offset + 3);
|
||
iods_info.audio_profile = APar_read8(isofile, iods_offset + 4);
|
||
iods_info.video_profile_level = APar_read8(isofile, iods_offset + 5);
|
||
iods_info.graphics_profile_level = APar_read8(isofile, iods_offset + 6);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_Extract_AMR_Info
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
track_level_atom - the number of the 'esds' atom in the linked list of
|
||
parsed atoms track_info - a pointer to the struct carrying track-level info to
|
||
be filled with information
|
||
|
||
The only interesting info here is the encoding tool & the amr modes used.
|
||
ffmpeg's amr output seems to lack some compliance - no damr atom for sawb
|
||
----------------------*/
|
||
void APar_Extract_AMR_Info(char *uint32_buffer,
|
||
FILE *isofile,
|
||
short track_level_atom,
|
||
TrackInfo *track_info) {
|
||
uint32_t amr_specific_offet = 8;
|
||
APar_readX(track_info->encoder_name,
|
||
isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet,
|
||
4);
|
||
if (track_info->track_codec == 0x73616D72 ||
|
||
track_info->track_codec == 0x73617762 ||
|
||
track_info->track_codec ==
|
||
0x73766D72) { // samr,sawb & svmr contain modes only
|
||
track_info->amr_modes = APar_read16(
|
||
uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart + amr_specific_offet + 4 + 1);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_Extract_d263_Info
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
track_level_atom - the number of the 'esds' atom in the linked list of
|
||
parsed atoms track_info - a pointer to the struct carrying track-level info to
|
||
be filled with information
|
||
|
||
'd263' only holds 4 things; the 3 of interest are gathered here. Its
|
||
possible that a 'bitr' atom follows 'd263', which would hold bitrates, but isn't
|
||
parsed here
|
||
----------------------*/
|
||
void APar_Extract_d263_Info(char *uint32_buffer,
|
||
FILE *isofile,
|
||
short track_level_atom,
|
||
TrackInfo *track_info) {
|
||
uint64_t offset_into_d263 = 8;
|
||
APar_readX(track_info->encoder_name,
|
||
isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart + offset_into_d263,
|
||
4);
|
||
track_info->level = APar_read8(isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart +
|
||
offset_into_d263 + 4 + 1);
|
||
track_info->profile = APar_read8(isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart +
|
||
offset_into_d263 + 4 + 2);
|
||
// possible 'bitr' bitrate box afterwards
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_Extract_devc_Info
|
||
isofile - the file to be scanned
|
||
track_level_atom - the number of the 'esds' atom in the linked list of
|
||
parsed atoms track_info - a pointer to the struct carrying track-level info to
|
||
be filled with information
|
||
|
||
'devc' only holds 3 things: encoder vendor, decoder version & frames per
|
||
sample; only encoder vendor is gathered
|
||
----------------------*/
|
||
void APar_Extract_devc_Info(FILE *isofile,
|
||
short track_level_atom,
|
||
TrackInfo *track_info) {
|
||
uint64_t offset_into_devc = 8;
|
||
APar_readX(track_info->encoder_name,
|
||
isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart + offset_into_devc,
|
||
4);
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_Extract_esds_Info
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
track_level_atom - the number of the 'esds' atom in the linked list of
|
||
parsed atoms track_info - a pointer to the struct carrying track-level info to
|
||
be filled with information
|
||
|
||
'esds' contains a wealth of information. Memory fails where I figured out
|
||
how to parse this atom, but this seems like a decent outline in retrospect:
|
||
http://www.geocities.com/xhelmboyx/quicktime/formats/mp4-layout.txt
|
||
- but its misleading in lots of places too. For those tracks that support 'esds'
|
||
(notably not avc1 or alac), this atom comes in at most 4
|
||
sections (section 3 to section 6). Each section is numbered (3 is 0x03) followed
|
||
by an optional amount of filler (see APar_skip_filler), then the length of the
|
||
section to the end of the atom or the end of another section.
|
||
----------------------*/
|
||
void APar_Extract_esds_Info(char *uint32_buffer,
|
||
FILE *isofile,
|
||
short track_level_atom,
|
||
TrackInfo *track_info) {
|
||
uint64_t offset_into_stsd = 0;
|
||
|
||
while (offset_into_stsd < parsedAtoms[track_level_atom].AtomicLength) {
|
||
offset_into_stsd++;
|
||
if (APar_read32(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track_level_atom].AtomicStart +
|
||
offset_into_stsd) == 0x65736473) {
|
||
track_info->contains_esds = true;
|
||
|
||
uint64_t esds_start =
|
||
parsedAtoms[track_level_atom].AtomicStart + offset_into_stsd - 4;
|
||
uint64_t esds_length = APar_read32(uint32_buffer, isofile, esds_start);
|
||
uint64_t offset_into_esds =
|
||
12; // 4bytes length + 4 bytes name + 4bytes null
|
||
|
||
if (APar_read8(isofile, esds_start + offset_into_esds) == 0x03) {
|
||
offset_into_esds++;
|
||
offset_into_esds +=
|
||
APar_skip_filler(isofile, esds_start + offset_into_esds);
|
||
}
|
||
|
||
uint8_t section3_length =
|
||
APar_read8(isofile, esds_start + offset_into_esds);
|
||
if (section3_length <= esds_length && section3_length != 0) {
|
||
track_info->section3_length = section3_length;
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
// for whatever reason, when mp4box muxes in ogg into an mp4 container,
|
||
// section 3 gets a 0x9D byte (which doesn't fall inline with what AP
|
||
// considers 'filler') then again, I haven't *completely* read the ISO
|
||
// specifications, so I could just be missing it the the ->voluminous<-
|
||
// 14496-X specifications.
|
||
uint8_t test_byte =
|
||
APar_read8(isofile, esds_start + offset_into_esds + 1);
|
||
if (test_byte != 0) {
|
||
offset_into_esds++;
|
||
}
|
||
|
||
offset_into_esds += 4; // 1 bytes section 0x03 length + 2 bytes + 1 byte
|
||
|
||
if (APar_read8(isofile, esds_start + offset_into_esds) == 0x04) {
|
||
offset_into_esds++;
|
||
offset_into_esds +=
|
||
APar_skip_filler(isofile, esds_start + offset_into_esds);
|
||
}
|
||
|
||
uint8_t section4_length =
|
||
APar_read8(isofile, esds_start + offset_into_esds);
|
||
if (section4_length <= section3_length && section4_length != 0) {
|
||
track_info->section4_length = section4_length;
|
||
|
||
if (section4_length == 0x9D)
|
||
offset_into_esds++; // upper limit? when gpac puts an ogg in, section
|
||
// 3 is 9D - so is sec4 (section 4 real length
|
||
// with ogg = 0x0E86)
|
||
|
||
offset_into_esds++;
|
||
track_info->ObjectTypeIndication =
|
||
APar_read8(isofile, esds_start + offset_into_esds);
|
||
|
||
// this is just so that ogg in mp4 won't have some bizarre high bitrate
|
||
// of like 2.8megabits/sec
|
||
uint8_t a_v_flag =
|
||
APar_read8(isofile,
|
||
esds_start + offset_into_esds +
|
||
1); // mp4box with ogg will set this to DD,
|
||
// mp4a has it as 0x40, mp4v has 0x20
|
||
|
||
if (track_info->ObjectTypeIndication < 0xC0 &&
|
||
a_v_flag < 0xA0) { // 0xC0 marks user streams; but things below that
|
||
// might still be wrong (like 0x6D - png)
|
||
offset_into_esds += 5;
|
||
track_info->max_bitrate = APar_read32(
|
||
uint32_buffer, isofile, esds_start + offset_into_esds);
|
||
offset_into_esds += 4;
|
||
track_info->avg_bitrate = APar_read32(
|
||
uint32_buffer, isofile, esds_start + offset_into_esds);
|
||
offset_into_esds += 4;
|
||
}
|
||
} else {
|
||
break;
|
||
}
|
||
|
||
if (APar_read8(isofile, esds_start + offset_into_esds) == 0x05) {
|
||
offset_into_esds++;
|
||
offset_into_esds +=
|
||
APar_skip_filler(isofile, esds_start + offset_into_esds);
|
||
|
||
uint8_t section5_length =
|
||
APar_read8(isofile, esds_start + offset_into_esds);
|
||
if ((section5_length <= section4_length || section4_length == 1) &&
|
||
section5_length != 0) {
|
||
track_info->section5_length = section5_length;
|
||
offset_into_esds += 1;
|
||
|
||
if (track_info->type_of_track & AUDIO_TRACK) {
|
||
uint8_t packed_objID = APar_read8(
|
||
isofile,
|
||
esds_start + offset_into_esds); // its packed with channel, but
|
||
// channel is fetched separately
|
||
track_info->descriptor_object_typeID = packed_objID >> 3;
|
||
offset_into_esds += 1;
|
||
|
||
track_info->channels = (uint16_t)APar_ExtractChannelInfo(
|
||
isofile, esds_start + offset_into_esds);
|
||
|
||
} else if (track_info->type_of_track & VIDEO_TRACK) {
|
||
// technically, visual_object_sequence_start_code should be tested
|
||
// aginst 0x000001B0
|
||
if (APar_read16(uint32_buffer,
|
||
isofile,
|
||
esds_start + offset_into_esds + 2) == 0x01B0) {
|
||
track_info->m4v_profile =
|
||
APar_read8(isofile, esds_start + offset_into_esds + 2 + 2);
|
||
}
|
||
}
|
||
}
|
||
break; // uh, I've extracted the pertinent info
|
||
}
|
||
}
|
||
if (offset_into_stsd > parsedAtoms[track_level_atom].AtomicLength) {
|
||
break;
|
||
}
|
||
}
|
||
if ((track_info->section5_length == 0 &&
|
||
track_info->type_of_track & AUDIO_TRACK) ||
|
||
track_info->channels == 0) {
|
||
track_info->channels = APar_read16(
|
||
uint32_buffer, isofile, parsedAtoms[track_level_atom].AtomicStart + 40);
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_ExtractTrackDetails
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
track - the struct proving tracking of this 'trak' atom so we can jump
|
||
around in this track track_info - a pointer to the struct carrying track-level
|
||
info to be filled with information
|
||
|
||
This function jumps all around in a single 'trak' atom gathering information
|
||
from different child constituent atoms except 'esds' which is handled on its
|
||
own.
|
||
----------------------*/
|
||
void APar_ExtractTrackDetails(char *uint32_buffer,
|
||
FILE *isofile,
|
||
Trackage *track,
|
||
TrackInfo *track_info) {
|
||
uint64_t _offset = 0;
|
||
|
||
APar_TrackLevelInfo(track, "tkhd");
|
||
if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8) ==
|
||
0) {
|
||
if (APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 11) &
|
||
1) {
|
||
track_info->track_enabled = true;
|
||
}
|
||
track_info->creation_time =
|
||
APar_read32(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 12);
|
||
track_info->modified_time =
|
||
APar_read32(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 16);
|
||
track_info->duration =
|
||
APar_read32(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 28);
|
||
} else {
|
||
track_info->creation_time =
|
||
APar_read64(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 12);
|
||
track_info->modified_time =
|
||
APar_read64(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 20);
|
||
track_info->duration =
|
||
APar_read64(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 36);
|
||
}
|
||
|
||
// language code
|
||
APar_TrackLevelInfo(track, "mdhd");
|
||
memset(uint32_buffer, 0, 5);
|
||
uint16_t packed_language = APar_read16(
|
||
uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 28);
|
||
memset(track_info->unpacked_lang, 0, 4);
|
||
APar_UnpackLanguage(
|
||
track_info->unpacked_lang,
|
||
packed_language); // http://www.w3.org/WAI/ER/IG/ert/iso639.htm
|
||
|
||
// track handler type
|
||
APar_TrackLevelInfo(track, "hdlr");
|
||
memset(uint32_buffer, 0, 5);
|
||
track_info->track_type = APar_read32(
|
||
uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 16);
|
||
if (track_info->track_type == 0x736F756E) { // soun
|
||
track_info->type_of_track = AUDIO_TRACK;
|
||
} else if (track_info->track_type == 0x76696465) { // vide
|
||
track_info->type_of_track = VIDEO_TRACK;
|
||
}
|
||
if (parsedAtoms[track->track_atom].AtomicLength > 34) {
|
||
memset(track_info->track_hdlr_name, 0, 100);
|
||
APar_readX(track_info->track_hdlr_name,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + 32,
|
||
parsedAtoms[track->track_atom].AtomicLength - 32);
|
||
}
|
||
|
||
// codec section
|
||
APar_TrackLevelInfo(track, "stsd");
|
||
memset(uint32_buffer, 0, 5);
|
||
track_info->track_codec = APar_read32(
|
||
uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 20);
|
||
|
||
if (track_info->type_of_track & VIDEO_TRACK) { // vide
|
||
track_info->video_width =
|
||
APar_read16(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom + 1].AtomicStart + 32);
|
||
track_info->video_height =
|
||
APar_read16(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom + 1].AtomicStart + 34);
|
||
track_info->macroblocks =
|
||
(track_info->video_width / 16) * (track_info->video_height / 16);
|
||
|
||
// avc profile & level
|
||
if (track_info->track_codec == 0x61766331 ||
|
||
track_info->track_codec == 0x64726D69) { // avc1 or drmi
|
||
track_info->contains_esds = false;
|
||
APar_TrackLevelInfo(track, "avcC");
|
||
// get avc1 profile/level; atom 'avcC' is :
|
||
// byte 1 configurationVersion byte 2 AVCProfileIndication byte 3
|
||
// profile_compatibility byte 4 AVCLevelIndication
|
||
track_info->avc_version =
|
||
APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 8);
|
||
if (track_info->avc_version == 1) {
|
||
track_info->profile =
|
||
APar_read8(isofile, parsedAtoms[track->track_atom].AtomicStart + 9);
|
||
// uint8_t profile_compatibility = APar_read8(isofile,
|
||
// parsedAtoms[track.track_atom].AtomicStart + 10); /* is this reserved
|
||
// ?? */
|
||
track_info->level = APar_read8(
|
||
isofile, parsedAtoms[track->track_atom].AtomicStart + 11);
|
||
}
|
||
|
||
// avc1 doesn't have a hardcoded bitrate, so calculate it (off of stsz
|
||
// table summing) later
|
||
} else if (track_info->track_codec == 0x73323633) { // s263
|
||
APar_TrackLevelInfo(track, "d263");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "d263", 4) == 0) {
|
||
APar_Extract_d263_Info(
|
||
uint32_buffer, isofile, track->track_atom, track_info);
|
||
}
|
||
|
||
} else { // mp4v
|
||
APar_TrackLevelInfo(track, "esds");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "esds", 4) == 0) {
|
||
APar_Extract_esds_Info(
|
||
uint32_buffer,
|
||
isofile,
|
||
track->track_atom - 1,
|
||
track_info); // right, backtrack to the atom before 'esds' so we can
|
||
// offset_into_stsd++
|
||
} else if (track_info->track_codec == 0x73323633) { // s263
|
||
track_info->type_of_track = VIDEO_TRACK;
|
||
} else if (track_info->track_codec == 0x73616D72 ||
|
||
track_info->track_codec == 0x73617762 ||
|
||
track_info->track_codec == 0x73617770 ||
|
||
track_info->track_codec ==
|
||
0x73766D72) { // samr, sawb, sawp & svmr
|
||
track_info->type_of_track = AUDIO_TRACK;
|
||
} else {
|
||
track_info->type_of_track = OTHER_TRACK; // a 'jpeg' track will fall
|
||
// here
|
||
}
|
||
}
|
||
|
||
} else if (track_info->type_of_track & AUDIO_TRACK) {
|
||
if (track_info->track_codec == 0x73616D72 ||
|
||
track_info->track_codec == 0x73617762 ||
|
||
track_info->track_codec == 0x73617770 ||
|
||
track_info->track_codec ==
|
||
0x73766D72) { // samr,sawb, svmr (sawp doesn't contain modes)
|
||
APar_Extract_AMR_Info(
|
||
uint32_buffer, isofile, track->track_atom + 2, track_info);
|
||
|
||
} else if (track_info->track_codec == 0x73657663) { // sevc
|
||
APar_TrackLevelInfo(track, "devc");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "devc", 4) == 0) {
|
||
APar_Extract_devc_Info(isofile, track->track_atom, track_info);
|
||
}
|
||
|
||
} else if (track_info->track_codec == 0x73716370) { // sqcp
|
||
APar_TrackLevelInfo(track, "dqcp");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dqcp", 4) == 0) {
|
||
APar_Extract_devc_Info(isofile,
|
||
track->track_atom,
|
||
track_info); // its the same thing
|
||
}
|
||
|
||
} else if (track_info->track_codec == 0x73736D76) { // ssmv
|
||
APar_TrackLevelInfo(track, "dsmv");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "dsmv", 4) == 0) {
|
||
APar_Extract_devc_Info(isofile,
|
||
track->track_atom,
|
||
track_info); // its the same thing
|
||
}
|
||
|
||
} else {
|
||
APar_Extract_esds_Info(
|
||
uint32_buffer, isofile, track->track_atom, track_info);
|
||
}
|
||
}
|
||
|
||
// in case bitrate isn't found, manually determine it off of stsz summing
|
||
if ((track_info->type_of_track & AUDIO_TRACK ||
|
||
track_info->type_of_track & VIDEO_TRACK) &&
|
||
track_info->avg_bitrate == 0) {
|
||
if (track_info->track_codec == 0x616C6163) { // alac
|
||
track_info->channels =
|
||
APar_read16(uint32_buffer,
|
||
isofile,
|
||
parsedAtoms[track->track_atom + 1].AtomicStart + 24);
|
||
}
|
||
}
|
||
|
||
APar_TrackLevelInfo(track, "stsz");
|
||
if (memcmp(parsedAtoms[track->track_atom].AtomicName, "stsz", 4) == 0) {
|
||
track_info->sample_aggregate =
|
||
calcuate_sample_size(uint32_buffer, isofile, track->track_atom);
|
||
}
|
||
|
||
// get what exactly 'drmX' stands in for
|
||
if (track_info->track_codec >= 0x64726D00 &&
|
||
track_info->track_codec <= 0x64726DFF) {
|
||
track_info->type_of_track += DRM_PROTECTED_TRACK;
|
||
APar_TrackLevelInfo(track, "frma");
|
||
memset(uint32_buffer, 0, 5);
|
||
track_info->protected_codec = APar_read32(
|
||
uint32_buffer, isofile, parsedAtoms[track->track_atom].AtomicStart + 8);
|
||
}
|
||
|
||
// Encoder string; occasionally, it appears under stsd for a video track; it
|
||
// is typcally preceded by ' <20>' (1st char is unprintable) or 0x01B2
|
||
if (track_info->contains_esds) {
|
||
APar_TrackLevelInfo(track, "esds");
|
||
|
||
// technically, user_data_start_code should be tested aginst 0x000001B2;
|
||
// TODO: it should only be read up to section 3's length too
|
||
_offset = APar_FindValueInAtom(
|
||
uint32_buffer, isofile, track->track_atom, 24, 0x01B2);
|
||
|
||
if (_offset > 0 && _offset < parsedAtoms[track->track_atom].AtomicLength) {
|
||
_offset += 2;
|
||
memset(track_info->encoder_name,
|
||
0,
|
||
parsedAtoms[track->track_atom].AtomicLength - _offset);
|
||
APar_readX(track_info->encoder_name,
|
||
isofile,
|
||
parsedAtoms[track->track_atom].AtomicStart + _offset,
|
||
parsedAtoms[track->track_atom].AtomicLength - _offset);
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
/*----------------------
|
||
APar_ExtractMovieDetails
|
||
uint32_buffer - a buffer to read bytes in from the file
|
||
isofile - the file to be scanned
|
||
mvhd_atom - pointer to the 'mvhd' atom and where in the file it can be
|
||
found
|
||
|
||
Get information out of 'mvhd' - most important of which are timescale &
|
||
duration which get used to calcuate bitrate if needed and determine duration of
|
||
a track in seconds. A rough approximation of the overall bitrate is done off
|
||
this too using the sum of the mdat lengths.
|
||
----------------------*/
|
||
void APar_ExtractMovieDetails(char *uint32_buffer,
|
||
FILE *isofile,
|
||
AtomicInfo *mvhd_atom) {
|
||
if (mvhd_atom->AtomicVerFlags & 0x01000000) {
|
||
movie_info.creation_time =
|
||
APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 12);
|
||
movie_info.modified_time =
|
||
APar_read64(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20);
|
||
movie_info.timescale =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28);
|
||
movie_info.duration =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32);
|
||
movie_info.timescale =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 36);
|
||
movie_info.duration =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 40);
|
||
movie_info.playback_rate =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 44);
|
||
movie_info.volume =
|
||
APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 48);
|
||
} else {
|
||
movie_info.creation_time = (uint64_t)APar_read32(
|
||
uint32_buffer, isofile, mvhd_atom->AtomicStart + 12);
|
||
movie_info.modified_time = (uint64_t)APar_read32(
|
||
uint32_buffer, isofile, mvhd_atom->AtomicStart + 16);
|
||
movie_info.timescale =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 20);
|
||
movie_info.duration =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 24);
|
||
movie_info.playback_rate =
|
||
APar_read32(uint32_buffer, isofile, mvhd_atom->AtomicStart + 28);
|
||
movie_info.volume =
|
||
APar_read16(uint32_buffer, isofile, mvhd_atom->AtomicStart + 32);
|
||
}
|
||
|
||
movie_info.seconds = (float)movie_info.duration / (float)movie_info.timescale;
|
||
#if defined(_MSC_VER)
|
||
__int64 media_bits = (__int64)mdatData * 8;
|
||
#else
|
||
uint64_t media_bits = (uint64_t)mdatData * 8;
|
||
#endif
|
||
movie_info.simple_bitrate_calc =
|
||
((double)media_bits / movie_info.seconds) / 1000.0;
|
||
|
||
return;
|
||
}
|
||
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
// Get at some track-level info //
|
||
///////////////////////////////////////////////////////////////////////////////////////
|
||
|
||
void APar_Print_TrackDetails(TrackInfo *track_info) {
|
||
if (track_info->max_bitrate > 0 && track_info->avg_bitrate > 0) {
|
||
fprintf(stdout, " %.2f kbp/s", (float)track_info->avg_bitrate / 1000.0);
|
||
} else { // some ffmpeg encodings have avg_bitrate set to 0, but an inexact
|
||
// max_bitrate - actually, their esds seems a mess to me
|
||
#if defined(_MSC_VER)
|
||
fprintf(stdout,
|
||
" %.2lf* kbp/s",
|
||
((double)((__int64)track_info->sample_aggregate) /
|
||
((double)((__int64)track_info->duration) /
|
||
(double)((__int64)movie_info.timescale))) /
|
||
1000.0 * 8);
|
||
fprintf(stdout,
|
||
" %.3f sec",
|
||
(float)track_info->duration / (float)movie_info.timescale);
|
||
#else
|
||
fprintf(stdout,
|
||
" %.2lf* kbp/s",
|
||
((double)track_info->sample_aggregate /
|
||
((double)track_info->duration / (double)movie_info.timescale)) /
|
||
1000.0 * 8);
|
||
fprintf(stdout,
|
||
" %.3f sec",
|
||
(float)track_info->duration / (float)movie_info.timescale);
|
||
#endif
|
||
}
|
||
|
||
if (track_info->track_codec == 0x6D703476) { // mp4v profile
|
||
APar_ShowObjectProfileInfo(MP4V_TRACK, track_info);
|
||
} else if (track_info->track_codec == 0x6D703461 ||
|
||
track_info->protected_codec == 0x6D703461) { // mp4a profile
|
||
APar_ShowObjectProfileInfo(AUDIO_TRACK, track_info);
|
||
} else if (track_info->track_codec ==
|
||
0x616C6163) { // alac - can't figure out a hardcoded bitrate either
|
||
fprintf(
|
||
stdout, " Apple Lossless channels: [%u]\n", track_info->channels);
|
||
} else if (track_info->track_codec == 0x61766331 ||
|
||
track_info->protected_codec == 0x61766331) {
|
||
if (track_info->avc_version == 1) { // avc profile & level
|
||
APar_ShowObjectProfileInfo(AVC1_TRACK, track_info);
|
||
}
|
||
} else if (track_info->track_codec == 0x73323633) { // s263 in 3gp
|
||
APar_ShowObjectProfileInfo(S263_TRACK, track_info);
|
||
} else if (track_info->track_codec == 0x73616D72 ||
|
||
track_info->track_codec == 0x73617762 ||
|
||
track_info->track_codec == 0x73617770 ||
|
||
track_info->track_codec ==
|
||
0x73766D72) { // samr,sawb,sawp & svmr in 3gp
|
||
track_info->type_of_track = S_AMR_TRACK;
|
||
APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
|
||
} else if (track_info->track_codec == 0x73657663) { // evrc in 3gp
|
||
track_info->type_of_track = EVRC_TRACK;
|
||
APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
|
||
} else if (track_info->track_codec == 0x73716370) { // qcelp in 3gp
|
||
track_info->type_of_track = QCELP_TRACK;
|
||
APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
|
||
} else if (track_info->track_codec == 0x73736D76) { // smv in 3gp
|
||
track_info->type_of_track = SMV_TRACK;
|
||
APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
|
||
} else { // unknown everything, 0 hardcoded bitrate
|
||
APar_ShowObjectProfileInfo(track_info->type_of_track, track_info);
|
||
fprintf(stdout, "\n");
|
||
}
|
||
|
||
if (track_info->type_of_track & VIDEO_TRACK &&
|
||
((track_info->max_bitrate > 0 &&
|
||
track_info->ObjectTypeIndication == 0x20) ||
|
||
track_info->avc_version == 1 || track_info->protected_codec != 0)) {
|
||
fprintf(stdout,
|
||
" %ux%u (%" PRIu32 " macroblocks)\n",
|
||
track_info->video_width,
|
||
track_info->video_height,
|
||
track_info->macroblocks);
|
||
} else if (track_info->type_of_track & VIDEO_TRACK) {
|
||
fprintf(stdout, "\n");
|
||
}
|
||
return;
|
||
}
|
||
|
||
void APar_ExtractDetails(FILE *isofile, uint8_t optional_output) {
|
||
char *uint32_buffer = (char *)malloc(sizeof(char) * 5);
|
||
Trackage track = {0};
|
||
|
||
AtomicInfo *mvhdAtom = APar_FindAtom("moov.mvhd", false, VERSIONED_ATOM, 0);
|
||
if (mvhdAtom != NULL) {
|
||
APar_ExtractMovieDetails(uint32_buffer, isofile, mvhdAtom);
|
||
fprintf(stdout,
|
||
"Movie duration: %.3lf seconds (%s) - %.2lf* kbp/sec bitrate "
|
||
"(*=approximate)\n",
|
||
movie_info.seconds,
|
||
secsTOtime(movie_info.seconds),
|
||
movie_info.simple_bitrate_calc);
|
||
if (optional_output & SHOW_DATE_INFO) {
|
||
fprintf(stdout,
|
||
" Presentation Creation Date (UTC): %s\n",
|
||
APar_extract_UTC(movie_info.creation_time));
|
||
fprintf(stdout,
|
||
" Presentation Modification Date (UTC): %s\n",
|
||
APar_extract_UTC(movie_info.modified_time));
|
||
}
|
||
}
|
||
|
||
AtomicInfo *iodsAtom = APar_FindAtom("moov.iods", false, VERSIONED_ATOM, 0);
|
||
if (iodsAtom != NULL) {
|
||
movie_info.contains_iods = true;
|
||
APar_Extract_iods_Info(isofile, iodsAtom);
|
||
}
|
||
|
||
if (optional_output & SHOW_TRACK_INFO) {
|
||
APar_TrackLevelInfo(&track,
|
||
NULL); // With track_num set to 0, it will return the
|
||
// total trak atom into total_tracks here.
|
||
|
||
fprintf(
|
||
stdout, "Low-level details. Total tracks: %u\n", track.total_tracks);
|
||
fprintf(stdout,
|
||
"Trk Type Handler Kind Lang Bytes\n");
|
||
|
||
if (track.total_tracks > 0) {
|
||
while (track.total_tracks > track.track_num) {
|
||
track.track_num += 1;
|
||
TrackInfo track_info = {0};
|
||
|
||
// tracknum, handler type, handler name
|
||
APar_ExtractTrackDetails(uint32_buffer, isofile, &track, &track_info);
|
||
uint16_t more_whitespace =
|
||
purge_extraneous_characters(track_info.track_hdlr_name);
|
||
|
||
if (strlen(track_info.track_hdlr_name) == 0) {
|
||
memcpy(track_info.track_hdlr_name, "[none listed]", 13);
|
||
}
|
||
fprintf(stdout,
|
||
"%u %s %s",
|
||
track.track_num,
|
||
uint32tochar4(track_info.track_type, uint32_buffer),
|
||
track_info.track_hdlr_name);
|
||
|
||
uint16_t handler_len = strlen(track_info.track_hdlr_name);
|
||
if (handler_len < 25 + more_whitespace) {
|
||
for (uint16_t i = handler_len; i < 25 + more_whitespace; i++) {
|
||
fprintf(stdout, " ");
|
||
}
|
||
}
|
||
|
||
// codec, language
|
||
fprintf(stdout,
|
||
" %s %s %" PRIu64,
|
||
uint32tochar4(track_info.track_codec, uint32_buffer),
|
||
track_info.unpacked_lang,
|
||
track_info.sample_aggregate);
|
||
|
||
if (track_info.encoder_name[0] != 0 && track_info.contains_esds) {
|
||
purge_extraneous_characters(track_info.encoder_name);
|
||
fprintf(stdout, " Encoder: %s", track_info.encoder_name);
|
||
}
|
||
if (track_info.type_of_track & DRM_PROTECTED_TRACK) {
|
||
fprintf(stdout,
|
||
" (protected %s)",
|
||
uint32tochar4(track_info.protected_codec, uint32_buffer));
|
||
}
|
||
|
||
fprintf(stdout, "\n");
|
||
/*---------------------------------*/
|
||
|
||
if (track_info.type_of_track & VIDEO_TRACK ||
|
||
track_info.type_of_track & AUDIO_TRACK) {
|
||
APar_Print_TrackDetails(&track_info);
|
||
}
|
||
|
||
if (optional_output & SHOW_DATE_INFO) {
|
||
fprintf(stdout,
|
||
" Creation Date (UTC): %s\n",
|
||
APar_extract_UTC(track_info.creation_time));
|
||
fprintf(stdout,
|
||
" Modification Date (UTC): %s\n",
|
||
APar_extract_UTC(track_info.modified_time));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return;
|
||
}
|
||
|
||
// provided as a convenience function so that 3rd party utilities can know
|
||
// beforehand
|
||
void APar_ExtractBrands(char *filepath) {
|
||
FILE *a_file = APar_OpenISOBaseMediaFile(filepath, true);
|
||
char *buffer = (char *)calloc(1, sizeof(char) * 16);
|
||
uint32_t atom_length = 0;
|
||
uint8_t file_type_offset = 0;
|
||
uint32_t compatible_brand = 0;
|
||
bool cb_V2ISOBMFF = false;
|
||
|
||
APar_read32(buffer, a_file, 4);
|
||
if (memcmp(buffer, "ftyp", 4) == 0) {
|
||
atom_length = APar_read32(buffer, a_file, 0);
|
||
} else {
|
||
APar_readX(buffer, a_file, 0, 12);
|
||
if (memcmp(buffer,
|
||
"\x00\x00\x00\x0C\x6A\x50\x20\x20\x0D\x0A\x87\x0A",
|
||
12) == 0) {
|
||
APar_readX(buffer, a_file, 12, 12);
|
||
if (memcmp(buffer + 4, "ftypmjp2", 8) == 0 ||
|
||
memcmp(buffer + 4, "ftypmj2s", 8) == 0) {
|
||
atom_length = UInt32FromBigEndian(buffer);
|
||
file_type_offset = 12;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (atom_length > 0) {
|
||
memset(buffer, 0, 16);
|
||
APar_readX(buffer, a_file, 8 + file_type_offset, 4);
|
||
printBOM();
|
||
fprintf(stdout, " Major Brand: %s", buffer);
|
||
APar_IdentifyBrand(buffer);
|
||
|
||
if (memcmp(buffer, "isom", 4) == 0) {
|
||
APar_ScanAtoms(filepath); // scan_file = true;
|
||
}
|
||
|
||
uint32_t minor_version = APar_read32(buffer, a_file, 12 + file_type_offset);
|
||
fprintf(stdout, " - version %" PRIu32 "\n", minor_version);
|
||
|
||
fprintf(stdout, " Compatible Brands:");
|
||
for (uint64_t i = 16 + file_type_offset; i < atom_length; i += 4) {
|
||
APar_readX(buffer, a_file, i, 4);
|
||
compatible_brand = UInt32FromBigEndian(buffer);
|
||
if (compatible_brand != 0) {
|
||
fprintf(stdout, " %s", buffer);
|
||
if (compatible_brand == 0x6D703432 || compatible_brand == 0x69736F32) {
|
||
cb_V2ISOBMFF = true;
|
||
}
|
||
}
|
||
}
|
||
fprintf(stdout, "\n");
|
||
}
|
||
|
||
APar_OpenISOBaseMediaFile(filepath, false);
|
||
|
||
fprintf(stdout, " Tagging schemes available:\n");
|
||
switch (metadata_style) {
|
||
case ITUNES_STYLE: {
|
||
fprintf(stdout, " iTunes-style metadata allowed.\n");
|
||
break;
|
||
}
|
||
case THIRD_GEN_PARTNER:
|
||
case THIRD_GEN_PARTNER_VER1_REL6:
|
||
case THIRD_GEN_PARTNER_VER1_REL7:
|
||
case THIRD_GEN_PARTNER_VER2: {
|
||
fprintf(stdout, " 3GP-style asset metadata allowed.\n");
|
||
break;
|
||
}
|
||
case THIRD_GEN_PARTNER_VER2_REL_A: {
|
||
fprintf(stdout,
|
||
" 3GP-style asset metadata allowed [& unimplemented GAD "
|
||
"(Geographical Area Description) asset].\n");
|
||
break;
|
||
}
|
||
}
|
||
if (cb_V2ISOBMFF || metadata_style == THIRD_GEN_PARTNER_VER1_REL7) {
|
||
fprintf(stdout,
|
||
" ID3 tags on ID32 atoms @ file/movie/track level allowed.\n");
|
||
}
|
||
fprintf(stdout,
|
||
" ISO-copyright notices @ movie and/or track level "
|
||
"allowed.\n uuid private user extension tags allowed.\n");
|
||
|
||
free(buffer);
|
||
buffer = NULL;
|
||
return;
|
||
}
|