rlm@3: | rlm@3: // | Allan Hansen | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // | module.audio.aac_adts.php | rlm@3: // | Module for analyzing AAC files with ADTS header. | rlm@3: // | dependencies: NONE | rlm@3: // +----------------------------------------------------------------------+ rlm@3: // rlm@3: // $Id: module.audio.aac_adts.php,v 1.4 2006/11/02 10:48:01 ah Exp $ rlm@3: rlm@3: rlm@3: rlm@3: class getid3_aac_adts extends getid3_handler rlm@3: { rlm@3: rlm@3: public $option_max_frames_to_scan = 1000000; rlm@3: public $option_return_extended_info = false; rlm@3: rlm@3: rlm@3: private $decbin_cache; rlm@3: private $bitrate_cache; rlm@3: rlm@3: rlm@3: rlm@3: public function __construct(getID3 $getid3) { rlm@3: rlm@3: parent::__construct($getid3); rlm@3: rlm@3: // Populate bindec_cache rlm@3: for ($i = 0; $i < 256; $i++) { rlm@3: $this->decbin_cache[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT); rlm@3: } rlm@3: rlm@3: // Init cache rlm@3: $this->bitrate_cache = array (); rlm@3: rlm@3: // Fast scanning? rlm@3: if (!$getid3->option_accurate_results) { rlm@3: $this->option_max_frames_to_scan = 200; rlm@3: $getid3->warning('option_accurate_results set to false - bitrate and playing time are not accurate.'); rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public function Analyze() { rlm@3: rlm@3: $getid3 = $this->getid3; rlm@3: rlm@3: // based loosely on code from AACfile by Jurgen Faul rlm@3: // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html rlm@3: rlm@3: rlm@3: // http://faac.sourceforge.net/wiki/index.php?page=ADTS rlm@3: rlm@3: // * ADTS Fixed Header: these don't change from frame to frame rlm@3: // syncword 12 always: '111111111111' rlm@3: // ID 1 0: MPEG-4, 1: MPEG-2 rlm@3: // layer 2 always: '00' rlm@3: // protection_absent 1 rlm@3: // profile 2 rlm@3: // sampling_frequency_index 4 rlm@3: // private_bit 1 rlm@3: // channel_configuration 3 rlm@3: // original/copy 1 rlm@3: // home 1 rlm@3: // emphasis 2 only if ID == 0 (ie MPEG-4) rlm@3: rlm@3: // * ADTS Variable Header: these can change from frame to frame rlm@3: // copyright_identification_bit 1 rlm@3: // copyright_identification_start 1 rlm@3: // aac_frame_length 13 length of the frame including header (in bytes) rlm@3: // adts_buffer_fullness 11 0x7FF indicates VBR rlm@3: // no_raw_data_blocks_in_frame 2 rlm@3: rlm@3: // * ADTS Error check rlm@3: // crc_check 16 only if protection_absent == 0 rlm@3: rlm@3: $getid3->info['aac']['header'] = array () ; rlm@3: $info_aac = &$getid3->info['aac']; rlm@3: $info_aac_header = & $info_aac['header']; rlm@3: rlm@3: $byte_offset = $frame_number = 0; rlm@3: rlm@3: while (true) { rlm@3: rlm@3: // Breaks out when end-of-file encountered, or invalid data found, rlm@3: // or MaxFramesToScan frames have been scanned rlm@3: rlm@3: fseek($getid3->fp, $byte_offset, SEEK_SET); rlm@3: rlm@3: // First get substring rlm@3: $sub_string = fread($getid3->fp, 10); rlm@3: $sub_string_length = strlen($sub_string); rlm@3: if ($sub_string_length != 10) { rlm@3: throw new getid3_exception('Failed to read 10 bytes at offset '.(ftell($getid3->fp) - $sub_string_length).' (only read '.$sub_string_length.' bytes)'); rlm@3: } rlm@3: rlm@3: // Initialise $aac_header_bitstream rlm@3: $aac_header_bitstream = ''; rlm@3: rlm@3: // Loop thru substring chars rlm@3: for ($i = 0; $i < 10; $i++) { rlm@3: $aac_header_bitstream .= $this->decbin_cache[$sub_string[$i]]; rlm@3: } rlm@3: rlm@3: $sync_test = bindec(substr($aac_header_bitstream, 0, 12)); rlm@3: $bit_offset = 12; rlm@3: rlm@3: if ($sync_test != 0x0FFF) { rlm@3: throw new getid3_exception('Synch pattern (0x0FFF) not found at offset '.(ftell($getid3->fp) - 10).' (found 0x0'.strtoupper(dechex($sync_test)).' instead)'); rlm@3: } rlm@3: rlm@3: // Only gather info once - this takes time to do 1000 times! rlm@3: if ($frame_number > 0) { rlm@3: rlm@3: // MPEG-4: 20, // MPEG-2: 18 rlm@3: $bit_offset += $aac_header_bitstream[$bit_offset] ? 18 : 20; rlm@3: } rlm@3: rlm@3: // Gather info for first frame only - this takes time to do 1000 times! rlm@3: else { rlm@3: rlm@3: $info_aac['header_type'] = 'ADTS'; rlm@3: $info_aac_header['synch'] = $sync_test; rlm@3: $getid3->info['fileformat'] = 'aac'; rlm@3: $getid3->info['audio']['dataformat'] = 'aac'; rlm@3: rlm@3: $info_aac_header['mpeg_version'] = $aac_header_bitstream{$bit_offset++} == '0' ? 4 : 2; rlm@3: $info_aac_header['layer'] = bindec(substr($aac_header_bitstream, $bit_offset, 2)); rlm@3: $bit_offset += 2; rlm@3: rlm@3: if ($info_aac_header['layer'] != 0) { rlm@3: throw new getid3_exception('Layer error - expected 0x00, found 0x'.dechex($info_aac_header['layer']).' instead'); rlm@3: } rlm@3: rlm@3: $info_aac_header['crc_present'] = $aac_header_bitstream{$bit_offset++} == '0' ? true : false; rlm@3: rlm@3: $info_aac_header['profile_id'] = bindec(substr($aac_header_bitstream, $bit_offset, 2)); rlm@3: $bit_offset += 2; rlm@3: rlm@3: $info_aac_header['profile_text'] = getid3_aac_adts::AACprofileLookup($info_aac_header['profile_id'], $info_aac_header['mpeg_version']); rlm@3: rlm@3: $info_aac_header['sample_frequency_index'] = bindec(substr($aac_header_bitstream, $bit_offset, 4)); rlm@3: $bit_offset += 4; rlm@3: rlm@3: $info_aac_header['sample_frequency'] = getid3_aac_adts::AACsampleRateLookup($info_aac_header['sample_frequency_index']); rlm@3: rlm@3: $getid3->info['audio']['sample_rate'] = $info_aac_header['sample_frequency']; rlm@3: rlm@3: $info_aac_header['private'] = $aac_header_bitstream{$bit_offset++} == 1; rlm@3: rlm@3: $info_aac_header['channel_configuration'] = $getid3->info['audio']['channels'] = bindec(substr($aac_header_bitstream, $bit_offset, 3)); rlm@3: $bit_offset += 3; rlm@3: rlm@3: $info_aac_header['original'] = $aac_header_bitstream{$bit_offset++} == 1; rlm@3: $info_aac_header['home'] = $aac_header_bitstream{$bit_offset++} == 1; rlm@3: rlm@3: if ($info_aac_header['mpeg_version'] == 4) { rlm@3: $info_aac_header['emphasis'] = bindec(substr($aac_header_bitstream, $bit_offset, 2)); rlm@3: $bit_offset += 2; rlm@3: } rlm@3: rlm@3: if ($this->option_return_extended_info) { rlm@3: rlm@3: $info_aac[$frame_number]['copyright_id_bit'] = $aac_header_bitstream{$bit_offset++} == 1; rlm@3: $info_aac[$frame_number]['copyright_id_start'] = $aac_header_bitstream{$bit_offset++} == 1; rlm@3: rlm@3: } else { rlm@3: $bit_offset += 2; rlm@3: } rlm@3: } rlm@3: rlm@3: $frame_length = bindec(substr($aac_header_bitstream, $bit_offset, 13)); rlm@3: rlm@3: if (!isset($this->bitrate_cache[$frame_length])) { rlm@3: $this->bitrate_cache[$frame_length] = ($info_aac_header['sample_frequency'] / 1024) * $frame_length * 8; rlm@3: } rlm@3: @$info_aac['bitrate_distribution'][$this->bitrate_cache[$frame_length]]++; rlm@3: rlm@3: $info_aac[$frame_number]['aac_frame_length'] = $frame_length; rlm@3: $bit_offset += 13; rlm@3: rlm@3: $info_aac[$frame_number]['adts_buffer_fullness'] = bindec(substr($aac_header_bitstream, $bit_offset, 11)); rlm@3: $bit_offset += 11; rlm@3: rlm@3: $getid3->info['audio']['bitrate_mode'] = ($info_aac[$frame_number]['adts_buffer_fullness'] == 0x07FF) ? 'vbr' : 'cbr'; rlm@3: rlm@3: $info_aac[$frame_number]['num_raw_data_blocks'] = bindec(substr($aac_header_bitstream, $bit_offset, 2)); rlm@3: $bit_offset += 2; rlm@3: rlm@3: if ($info_aac_header['crc_present']) { rlm@3: $bit_offset += 16; rlm@3: } rlm@3: rlm@3: if (!$this->option_return_extended_info) { rlm@3: unset($info_aac[$frame_number]); rlm@3: } rlm@3: rlm@3: $byte_offset += $frame_length; rlm@3: if ((++$frame_number < $this->option_max_frames_to_scan) && (($byte_offset + 10) < $getid3->info['avdataend'])) { rlm@3: rlm@3: // keep scanning rlm@3: rlm@3: } else { rlm@3: rlm@3: $info_aac['frames'] = $frame_number; rlm@3: $getid3->info['playtime_seconds'] = ($getid3->info['avdataend'] / $byte_offset) * (($frame_number * 1024) / $info_aac_header['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds rlm@3: $getid3->info['audio']['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds']; rlm@3: ksort($info_aac['bitrate_distribution']); rlm@3: rlm@3: $getid3->info['audio']['encoder_options'] = $info_aac['header_type'].' '.$info_aac_header['profile_text']; rlm@3: rlm@3: return true; rlm@3: } rlm@3: } rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function AACsampleRateLookup($samplerate_id) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 0 => 96000, rlm@3: 1 => 88200, rlm@3: 2 => 64000, rlm@3: 3 => 48000, rlm@3: 4 => 44100, rlm@3: 5 => 32000, rlm@3: 6 => 24000, rlm@3: 7 => 22050, rlm@3: 8 => 16000, rlm@3: 9 => 12000, rlm@3: 10 => 11025, rlm@3: 11 => 8000, rlm@3: 12 => 0, rlm@3: 13 => 0, rlm@3: 14 => 0, rlm@3: 15 => 0 rlm@3: ); rlm@3: return (isset($lookup[$samplerate_id]) ? $lookup[$samplerate_id] : 'invalid'); rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: public static function AACprofileLookup($profile_id, $mpeg_version) { rlm@3: rlm@3: static $lookup = array ( rlm@3: 2 => array ( rlm@3: 0 => 'Main profile', rlm@3: 1 => 'Low Complexity profile (LC)', rlm@3: 2 => 'Scalable Sample Rate profile (SSR)', rlm@3: 3 => '(reserved)' rlm@3: ), rlm@3: 4 => array ( rlm@3: 0 => 'AAC_MAIN', rlm@3: 1 => 'AAC_LC', rlm@3: 2 => 'AAC_SSR', rlm@3: 3 => 'AAC_LTP' rlm@3: ) rlm@3: ); rlm@3: return (isset($lookup[$mpeg_version][$profile_id]) ? $lookup[$mpeg_version][$profile_id] : 'invalid'); rlm@3: } rlm@3: rlm@3: rlm@3: } rlm@3: rlm@3: rlm@3: ?>