public function Analyze() { $info =& $this->getid3->info; $OriginalAVdataOffset = $info['avdataoffset']; $this->fseek($info['avdataoffset']); $VOCheader = $this->fread(26); $magic = 'Creative Voice File'; if (substr($VOCheader, 0, 19) != $magic) { $info['error'][] = 'Expecting "' . Utils::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Utils::PrintHexBytes(substr($VOCheader, 0, 19)) . '"'; return false; } // shortcuts $thisfile_audio =& $info['audio']; $info['voc'] = array(); $thisfile_voc =& $info['voc']; $info['fileformat'] = 'voc'; $thisfile_audio['dataformat'] = 'voc'; $thisfile_audio['bitrate_mode'] = 'cbr'; $thisfile_audio['lossless'] = true; $thisfile_audio['channels'] = 1; // might be overriden below $thisfile_audio['bits_per_sample'] = 8; // might be overriden below // byte # Description // ------ ------------------------------------------ // 00-12 'Creative Voice File' // 13 1A (eof to abort printing of file) // 14-15 Offset of first datablock in .voc file (std 1A 00 in Intel Notation) // 16-17 Version number (minor,major) (VOC-HDR puts 0A 01) // 18-19 2's Comp of Ver. # + 1234h (VOC-HDR puts 29 11) $thisfile_voc['header']['datablock_offset'] = Utils::LittleEndian2Int(substr($VOCheader, 20, 2)); $thisfile_voc['header']['minor_version'] = Utils::LittleEndian2Int(substr($VOCheader, 22, 1)); $thisfile_voc['header']['major_version'] = Utils::LittleEndian2Int(substr($VOCheader, 23, 1)); do { $BlockOffset = $this->ftell(); $BlockData = $this->fread(4); $BlockType = ord($BlockData[0]); $BlockSize = Utils::LittleEndian2Int(substr($BlockData, 1, 3)); $ThisBlock = array(); Utils::safe_inc($thisfile_voc['blocktypes'][$BlockType], 1); switch ($BlockType) { case 0: // Terminator // do nothing, we'll break out of the loop down below break; case 1: // Sound data $BlockData .= $this->fread(2); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { $info['avdataoffset'] = $this->ftell(); } $this->fseek($BlockSize - 2, SEEK_CUR); $ThisBlock['sample_rate_id'] = Utils::LittleEndian2Int(substr($BlockData, 4, 1)); $ThisBlock['compression_type'] = Utils::LittleEndian2Int(substr($BlockData, 5, 1)); $ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']); if ($ThisBlock['compression_type'] <= 3) { $thisfile_voc['compressed_bits_per_sample'] = Utils::CastAsInt(str_replace('-bit', '', $ThisBlock['compression_name'])); } // Less accurate sample_rate calculation than the Extended block (#8) data (but better than nothing if Extended Block is not available) if (empty($thisfile_audio['sample_rate'])) { // SR byte = 256 - (1000000 / sample_rate) $thisfile_audio['sample_rate'] = Utils::trunc(1000000 / (256 - $ThisBlock['sample_rate_id']) / $thisfile_audio['channels']); } break; case 2: // Sound continue // Sound continue case 3: // Silence // Silence case 4: // Marker // Marker case 6: // Repeat // Repeat case 7: // End repeat // nothing useful, just skip $this->fseek($BlockSize, SEEK_CUR); break; case 8: // Extended $BlockData .= $this->fread(4); //00-01 Time Constant: // Mono: 65536 - (256000000 / sample_rate) // Stereo: 65536 - (256000000 / (sample_rate * 2)) $ThisBlock['time_constant'] = Utils::LittleEndian2Int(substr($BlockData, 4, 2)); $ThisBlock['pack_method'] = Utils::LittleEndian2Int(substr($BlockData, 6, 1)); $ThisBlock['stereo'] = (bool) Utils::LittleEndian2Int(substr($BlockData, 7, 1)); $thisfile_audio['channels'] = $ThisBlock['stereo'] ? 2 : 1; $thisfile_audio['sample_rate'] = Utils::trunc(256000000 / (65536 - $ThisBlock['time_constant']) / $thisfile_audio['channels']); break; case 9: // data block that supersedes blocks 1 and 8. Used for stereo, 16 bit $BlockData .= $this->fread(12); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { $info['avdataoffset'] = $this->ftell(); } $this->fseek($BlockSize - 12, SEEK_CUR); $ThisBlock['sample_rate'] = Utils::LittleEndian2Int(substr($BlockData, 4, 4)); $ThisBlock['bits_per_sample'] = Utils::LittleEndian2Int(substr($BlockData, 8, 1)); $ThisBlock['channels'] = Utils::LittleEndian2Int(substr($BlockData, 9, 1)); $ThisBlock['wFormat'] = Utils::LittleEndian2Int(substr($BlockData, 10, 2)); $ThisBlock['compression_name'] = $this->VOCwFormatLookup($ThisBlock['wFormat']); if ($this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat'])) { $thisfile_voc['compressed_bits_per_sample'] = $this->VOCwFormatActualBitsPerSampleLookup($ThisBlock['wFormat']); } $thisfile_audio['sample_rate'] = $ThisBlock['sample_rate']; $thisfile_audio['bits_per_sample'] = $ThisBlock['bits_per_sample']; $thisfile_audio['channels'] = $ThisBlock['channels']; break; default: $info['warning'][] = 'Unhandled block type "' . $BlockType . '" at offset ' . $BlockOffset; $this->fseek($BlockSize, SEEK_CUR); break; } if (!empty($ThisBlock)) { $ThisBlock['block_offset'] = $BlockOffset; $ThisBlock['block_size'] = $BlockSize; $ThisBlock['block_type_id'] = $BlockType; $thisfile_voc['blocks'][] = $ThisBlock; } } while (!feof($this->getid3->fp) && $BlockType != 0); // Terminator block doesn't have size field, so seek back 3 spaces $this->fseek(-3, SEEK_CUR); ksort($thisfile_voc['blocktypes']); if (!empty($thisfile_voc['compressed_bits_per_sample'])) { $info['playtime_seconds'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / ($thisfile_voc['compressed_bits_per_sample'] * $thisfile_audio['channels'] * $thisfile_audio['sample_rate']); $thisfile_audio['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; } return true; }
public function getAACADTSheaderFilepointer($MaxFramesToScan = 1000000, $ReturnExtendedInfo = false) { $info =& $this->getid3->info; // based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de> // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html // http://faac.sourceforge.net/wiki/index.php?page=ADTS // dead link // http://wiki.multimedia.cx/index.php?title=ADTS // * ADTS Fixed Header: these don't change from frame to frame // syncword 12 always: '111111111111' // ID 1 0: MPEG-4, 1: MPEG-2 // MPEG layer 2 If you send AAC in MPEG-TS, set to 0 // protection_absent 1 0: CRC present; 1: no CRC // profile 2 0: AAC Main; 1: AAC LC (Low Complexity); 2: AAC SSR (Scalable Sample Rate); 3: AAC LTP (Long Term Prediction) // sampling_frequency_index 4 15 not allowed // private_bit 1 usually 0 // channel_configuration 3 // original/copy 1 0: original; 1: copy // home 1 usually 0 // emphasis 2 only if ID == 0 (ie MPEG-4) // not present in some documentation? // * ADTS Variable Header: these can change from frame to frame // copyright_identification_bit 1 // copyright_identification_start 1 // aac_frame_length 13 length of the frame including header (in bytes) // adts_buffer_fullness 11 0x7FF indicates VBR // no_raw_data_blocks_in_frame 2 // * ADTS Error check // crc_check 16 only if protection_absent == 0 $byteoffset = $info['avdataoffset']; $framenumber = 0; // Init bit pattern array static $decbin = array(); // Populate $bindec for ($i = 0; $i < 256; $i++) { $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT); } // used to calculate bitrate below $BitrateCache = array(); while (true) { // breaks out when end-of-file encountered, or invalid data found, // or MaxFramesToScan frames have been scanned if (!Utils::intValueSupported($byteoffset)) { $info['warning'][] = 'Unable to parse AAC file beyond ' . $this->ftell() . ' (PHP does not support file operations beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB)'; return false; } $this->fseek($byteoffset); // First get substring $substring = $this->fread(9); // header is 7 bytes (or 9 if CRC is present) $substringlength = strlen($substring); if ($substringlength != 9) { $info['error'][] = 'Failed to read 7 bytes at offset ' . ($this->ftell() - $substringlength) . ' (only read ' . $substringlength . ' bytes)'; return false; } // this would be easier with 64-bit math, but split it up to allow for 32-bit: $header1 = Utils::BigEndian2Int(substr($substring, 0, 2)); $header2 = Utils::BigEndian2Int(substr($substring, 2, 4)); $header3 = Utils::BigEndian2Int(substr($substring, 6, 1)); $info['aac']['header']['raw']['syncword'] = ($header1 & 0xfff0) >> 4; if ($info['aac']['header']['raw']['syncword'] != 0xfff) { $info['error'][] = 'Synch pattern (0x0FFF) not found at offset ' . ($this->ftell() - $substringlength) . ' (found 0x0' . strtoupper(dechex($info['aac']['header']['raw']['syncword'])) . ' instead)'; //if ($info['fileformat'] == 'aac') { // return true; //} unset($info['aac']); return false; } // Gather info for first frame only - this takes time to do 1000 times! if ($framenumber == 0) { $info['aac']['header_type'] = 'ADTS'; $info['fileformat'] = 'aac'; $info['audio']['dataformat'] = 'aac'; $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x8) >> 3; $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x6) >> 1; $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x1) >> 0; $info['aac']['header']['raw']['profile_code'] = ($header2 & 0xc0000000) >> 30; $info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3c000000) >> 26; $info['aac']['header']['raw']['private_stream'] = ($header2 & 0x2000000) >> 25; $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x1c00000) >> 22; $info['aac']['header']['raw']['original'] = ($header2 & 0x200000) >> 21; $info['aac']['header']['raw']['home'] = ($header2 & 0x100000) >> 20; $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x80000) >> 19; $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x40000) >> 18; $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x3ffe0) >> 5; $info['aac']['header']['mpeg_version'] = $info['aac']['header']['raw']['mpeg_version'] ? 2 : 4; $info['aac']['header']['crc_present'] = $info['aac']['header']['raw']['protection_absent'] ? false : true; $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']); $info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']); $info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream']; $info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original']; $info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home']; $info['aac']['header']['channels'] = $info['aac']['header']['raw']['channels_code'] == 7 ? 8 : $info['aac']['header']['raw']['channels_code']; if ($ReturnExtendedInfo) { $info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream']; $info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start']; } if ($info['aac']['header']['raw']['mpeg_layer'] != 0) { $info['warning'][] = 'Layer error - expected "0", found "' . $info['aac']['header']['raw']['mpeg_layer'] . '" instead'; } if ($info['aac']['header']['sample_frequency'] == 0) { $info['error'][] = 'Corrupt AAC file: sample_frequency == zero'; return false; } $info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency']; $info['audio']['channels'] = $info['aac']['header']['channels']; } $FrameLength = ($header2 & 0x3ffe0) >> 5; if (!isset($BitrateCache[$FrameLength])) { $BitrateCache[$FrameLength] = $info['aac']['header']['sample_frequency'] / 1024 * $FrameLength * 8; } Utils::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1); $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength; $info['aac'][$framenumber]['adts_buffer_fullness'] = ($header2 & 0x1f) << 6 & ($header3 & 0xfc) >> 2; if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x7ff) { $info['audio']['bitrate_mode'] = 'vbr'; } else { $info['audio']['bitrate_mode'] = 'cbr'; } $info['aac'][$framenumber]['num_raw_data_blocks'] = ($header3 & 0x3) >> 0; if ($info['aac']['header']['crc_present']) { //$info['aac'][$framenumber]['crc'] = Utils::BigEndian2Int(substr($substring, 7, 2); } if (!$ReturnExtendedInfo) { unset($info['aac'][$framenumber]); } /* $rounded_precision = 5000; $info['aac']['bitrate_distribution_rounded'] = array(); foreach ($info['aac']['bitrate_distribution'] as $bitrate => $count) { $rounded_bitrate = round($bitrate / $rounded_precision) * $rounded_precision; Utils::safe_inc($info['aac']['bitrate_distribution_rounded'][$rounded_bitrate], $count); } ksort($info['aac']['bitrate_distribution_rounded']); */ $byteoffset += $FrameLength; if (++$framenumber < $MaxFramesToScan && $byteoffset + 10 < $info['avdataend']) { // keep scanning } else { $info['aac']['frames'] = $framenumber; $info['playtime_seconds'] = $info['avdataend'] / $byteoffset * ($framenumber * 1024 / $info['aac']['header']['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds if ($info['playtime_seconds'] == 0) { $info['error'][] = 'Corrupt AAC file: playtime_seconds == zero'; return false; } $info['audio']['bitrate'] = ($info['avdataend'] - $info['avdataoffset']) * 8 / $info['playtime_seconds']; ksort($info['aac']['bitrate_distribution']); $info['audio']['encoder_options'] = $info['aac']['header_type'] . ' ' . $info['aac']['header']['profile']; return true; } } // should never get here. }