//==================================================================// /* 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 Ð 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 Ð 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 ' ²' (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; }