/** * @staticvar type $MPEGaudioVersionLookup * @staticvar type $MPEGaudioLayerLookup * @staticvar type $MPEGaudioBitrateLookup * * @param type $avdataoffset * @param bool $BitrateHistogram * * @return bool */ public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram = false) { // looks for synch, decodes MPEG audio header $info =& $this->getGetId3()->info; static $MPEGaudioVersionLookup; static $MPEGaudioLayerLookup; static $MPEGaudioBitrateLookup; if (empty($MPEGaudioVersionLookup)) { $MPEGaudioVersionLookup = self::MPEGaudioVersionArray(); $MPEGaudioLayerLookup = self::MPEGaudioLayerArray(); $MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray(); } fseek($this->getGetId3()->fp, $avdataoffset, SEEK_SET); $sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset); if ($sync_seek_buffer_size <= 0) { $info['error'][] = 'Invalid $sync_seek_buffer_size at offset ' . $avdataoffset; return false; } $header = fread($this->getGetId3()->fp, $sync_seek_buffer_size); $sync_seek_buffer_size = strlen($header); $SynchSeekOffset = 0; while ($SynchSeekOffset < $sync_seek_buffer_size) { if ($avdataoffset + $SynchSeekOffset < $info['avdataend'] && !feof($this->getGetId3()->fp)) { if ($SynchSeekOffset > $sync_seek_buffer_size) { // if a synch's not found within the first 128k bytes, then give up $info['error'][] = 'Could not find valid MPEG audio synch within the first ' . round($sync_seek_buffer_size / 1024) . 'kB'; if (isset($info['audio']['bitrate'])) { unset($info['audio']['bitrate']); } if (isset($info['mpeg']['audio'])) { unset($info['mpeg']['audio']); } if (empty($info['mpeg'])) { unset($info['mpeg']); } return false; } elseif (feof($this->getGetId3()->fp)) { $info['error'][] = 'Could not find valid MPEG audio synch before end of file'; if (isset($info['audio']['bitrate'])) { unset($info['audio']['bitrate']); } if (isset($info['mpeg']['audio'])) { unset($info['mpeg']['audio']); } if (isset($info['mpeg']) && (!is_array($info['mpeg']) || count($info['mpeg']) == 0)) { unset($info['mpeg']); } return false; } } if ($SynchSeekOffset + 1 >= strlen($header)) { $info['error'][] = 'Could not find valid MPEG synch before end of file'; return false; } if ($header[$SynchSeekOffset] == "ÿ" && $header[$SynchSeekOffset + 1] > "à") { // synch detected if (!isset($FirstFrameThisfileInfo) && !isset($info['mpeg']['audio'])) { $FirstFrameThisfileInfo = $info; $FirstFrameAVDataOffset = $avdataoffset + $SynchSeekOffset; if (!$this->decodeMPEGaudioHeader($FirstFrameAVDataOffset, $FirstFrameThisfileInfo, false)) { // if this is the first valid MPEG-audio frame, save it in case it's a VBR header frame and there's // garbage between this frame and a valid sequence of MPEG-audio frames, to be restored below unset($FirstFrameThisfileInfo); } } $dummy = $info; // only overwrite real data if valid header found if ($this->decodeMPEGaudioHeader($avdataoffset + $SynchSeekOffset, $dummy, true)) { $info = $dummy; $info['avdataoffset'] = $avdataoffset + $SynchSeekOffset; switch (isset($info['fileformat']) ? $info['fileformat'] : '') { case '': case 'id3': case 'ape': case 'mp3': $info['fileformat'] = 'mp3'; $info['audio']['dataformat'] = 'mp3'; break; } if (isset($FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode']) && $FirstFrameThisfileInfo['mpeg']['audio']['bitrate_mode'] == 'vbr') { if (!(abs($info['audio']['bitrate'] - $FirstFrameThisfileInfo['audio']['bitrate']) <= 1)) { // If there is garbage data between a valid VBR header frame and a sequence // of valid MPEG-audio frames the VBR data is no longer discarded. $info = $FirstFrameThisfileInfo; $info['avdataoffset'] = $FirstFrameAVDataOffset; $info['fileformat'] = 'mp3'; $info['audio']['dataformat'] = 'mp3'; $dummy = $info; unset($dummy['mpeg']['audio']); $GarbageOffsetStart = $FirstFrameAVDataOffset + $FirstFrameThisfileInfo['mpeg']['audio']['framelength']; $GarbageOffsetEnd = $avdataoffset + $SynchSeekOffset; if ($this->decodeMPEGaudioHeader($GarbageOffsetEnd, $dummy, true, true)) { $info = $dummy; $info['avdataoffset'] = $GarbageOffsetEnd; $info['warning'][] = 'apparently-valid VBR header not used because could not find ' . self::GETID3_MP3_VALID_CHECK_FRAMES . ' consecutive MPEG-audio frames immediately after VBR header (garbage data for ' . ($GarbageOffsetEnd - $GarbageOffsetStart) . ' bytes between ' . $GarbageOffsetStart . ' and ' . $GarbageOffsetEnd . '), but did find valid CBR stream starting at ' . $GarbageOffsetEnd; } else { $info['warning'][] = 'using data from VBR header even though could not find ' . self::GETID3_MP3_VALID_CHECK_FRAMES . ' consecutive MPEG-audio frames immediately after VBR header (garbage data for ' . ($GarbageOffsetEnd - $GarbageOffsetStart) . ' bytes between ' . $GarbageOffsetStart . ' and ' . $GarbageOffsetEnd . ')'; } } } if (isset($info['mpeg']['audio']['bitrate_mode']) && $info['mpeg']['audio']['bitrate_mode'] == 'vbr' && !isset($info['mpeg']['audio']['VBR_method'])) { // VBR file with no VBR header $BitrateHistogram = true; } if ($BitrateHistogram) { $info['mpeg']['audio']['stereo_distribution'] = array('stereo' => 0, 'joint stereo' => 0, 'dual channel' => 0, 'mono' => 0); $info['mpeg']['audio']['version_distribution'] = array('1' => 0, '2' => 0, '2.5' => 0); if ($info['mpeg']['audio']['version'] == '1') { if ($info['mpeg']['audio']['layer'] == 3) { $info['mpeg']['audio']['bitrate_distribution'] = array('free' => 0, 32000 => 0, 40000 => 0, 48000 => 0, 56000 => 0, 64000 => 0, 80000 => 0, 96000 => 0, 112000 => 0, 128000 => 0, 160000 => 0, 192000 => 0, 224000 => 0, 256000 => 0, 320000 => 0); } elseif ($info['mpeg']['audio']['layer'] == 2) { $info['mpeg']['audio']['bitrate_distribution'] = array('free' => 0, 32000 => 0, 48000 => 0, 56000 => 0, 64000 => 0, 80000 => 0, 96000 => 0, 112000 => 0, 128000 => 0, 160000 => 0, 192000 => 0, 224000 => 0, 256000 => 0, 320000 => 0, 384000 => 0); } elseif ($info['mpeg']['audio']['layer'] == 1) { $info['mpeg']['audio']['bitrate_distribution'] = array('free' => 0, 32000 => 0, 64000 => 0, 96000 => 0, 128000 => 0, 160000 => 0, 192000 => 0, 224000 => 0, 256000 => 0, 288000 => 0, 320000 => 0, 352000 => 0, 384000 => 0, 416000 => 0, 448000 => 0); } } elseif ($info['mpeg']['audio']['layer'] == 1) { $info['mpeg']['audio']['bitrate_distribution'] = array('free' => 0, 32000 => 0, 48000 => 0, 56000 => 0, 64000 => 0, 80000 => 0, 96000 => 0, 112000 => 0, 128000 => 0, 144000 => 0, 160000 => 0, 176000 => 0, 192000 => 0, 224000 => 0, 256000 => 0); } else { $info['mpeg']['audio']['bitrate_distribution'] = array('free' => 0, 8000 => 0, 16000 => 0, 24000 => 0, 32000 => 0, 40000 => 0, 48000 => 0, 56000 => 0, 64000 => 0, 80000 => 0, 96000 => 0, 112000 => 0, 128000 => 0, 144000 => 0, 160000 => 0); } $dummy = array('error' => $info['error'], 'warning' => $info['warning'], 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']); $synchstartoffset = $info['avdataoffset']; fseek($this->getGetId3()->fp, $info['avdataoffset'], SEEK_SET); // you can play with these numbers: $max_frames_scan = 50000; $max_scan_segments = 10; // don't play with these numbers: $FastMode = false; $SynchErrorsFound = 0; $frames_scanned = 0; $this_scan_segment = 0; $frames_scan_per_segment = ceil($max_frames_scan / $max_scan_segments); $pct_data_scanned = 0; for ($current_segment = 0; $current_segment < $max_scan_segments; ++$current_segment) { $frames_scanned_this_segment = 0; if (ftell($this->getGetId3()->fp) >= $info['avdataend']) { break; } $scan_start_offset[$current_segment] = max(ftell($this->getGetId3()->fp), $info['avdataoffset'] + round($current_segment * (($info['avdataend'] - $info['avdataoffset']) / $max_scan_segments))); if ($current_segment > 0) { fseek($this->getGetId3()->fp, $scan_start_offset[$current_segment], SEEK_SET); $buffer_4k = fread($this->getGetId3()->fp, 4096); for ($j = 0; $j < strlen($buffer_4k) - 4; ++$j) { if ($buffer_4k[$j] == "ÿ" && $buffer_4k[$j + 1] > "à") { // synch detected if ($this->decodeMPEGaudioHeader($scan_start_offset[$current_segment] + $j, $dummy, false, false, $FastMode)) { $calculated_next_offset = $scan_start_offset[$current_segment] + $j + $dummy['mpeg']['audio']['framelength']; if ($this->decodeMPEGaudioHeader($calculated_next_offset, $dummy, false, false, $FastMode)) { $scan_start_offset[$current_segment] += $j; break; } } } } } $synchstartoffset = $scan_start_offset[$current_segment]; while ($this->decodeMPEGaudioHeader($synchstartoffset, $dummy, false, false, $FastMode)) { $FastMode = true; $thisframebitrate = $MPEGaudioBitrateLookup[$MPEGaudioVersionLookup[$dummy['mpeg']['audio']['raw']['version']]][$MPEGaudioLayerLookup[$dummy['mpeg']['audio']['raw']['layer']]][$dummy['mpeg']['audio']['raw']['bitrate']]; if (empty($dummy['mpeg']['audio']['framelength'])) { ++$SynchErrorsFound; ++$synchstartoffset; } else { Helper::safe_inc($info['mpeg']['audio']['bitrate_distribution'][$thisframebitrate]); Helper::safe_inc($info['mpeg']['audio']['stereo_distribution'][$dummy['mpeg']['audio']['channelmode']]); Helper::safe_inc($info['mpeg']['audio']['version_distribution'][$dummy['mpeg']['audio']['version']]); $synchstartoffset += $dummy['mpeg']['audio']['framelength']; } ++$frames_scanned; if ($frames_scan_per_segment && ++$frames_scanned_this_segment >= $frames_scan_per_segment) { $this_pct_scanned = (ftell($this->getGetId3()->fp) - $scan_start_offset[$current_segment]) / ($info['avdataend'] - $info['avdataoffset']); if ($current_segment == 0 && $this_pct_scanned * $max_scan_segments >= 1) { // file likely contains < $max_frames_scan, just scan as one segment $max_scan_segments = 1; $frames_scan_per_segment = $max_frames_scan; } else { $pct_data_scanned += $this_pct_scanned; break; } } } } if ($pct_data_scanned > 0) { $info['warning'][] = 'too many MPEG audio frames to scan, only scanned ' . $frames_scanned . ' frames in ' . $max_scan_segments . ' segments (' . number_format($pct_data_scanned * 100, 1) . '% of file) and extrapolated distribution, playtime and bitrate may be incorrect.'; foreach ($info['mpeg']['audio'] as $key1 => $value1) { if (!preg_match('#_distribution$#i', $key1)) { continue; } foreach ($value1 as $key2 => $value2) { $info['mpeg']['audio'][$key1][$key2] = round($value2 / $pct_data_scanned); } } } if ($SynchErrorsFound > 0) { $info['warning'][] = 'Found ' . $SynchErrorsFound . ' synch errors in histogram analysis'; //return false; } $bittotal = 0; $framecounter = 0; foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitratevalue => $bitratecount) { $framecounter += $bitratecount; if ($bitratevalue != 'free') { $bittotal += $bitratevalue * $bitratecount; } } if ($framecounter == 0) { $info['error'][] = 'Corrupt MP3 file: framecounter == zero'; return false; } $info['mpeg']['audio']['frame_count'] = Helper::CastAsInt($framecounter); $info['mpeg']['audio']['bitrate'] = $bittotal / $framecounter; $info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate']; // Definitively set VBR vs CBR, even if the Xing/LAME/VBRI header says differently $distinct_bitrates = 0; foreach ($info['mpeg']['audio']['bitrate_distribution'] as $bitrate_value => $bitrate_count) { if ($bitrate_count > 0) { ++$distinct_bitrates; } } if ($distinct_bitrates > 1) { $info['mpeg']['audio']['bitrate_mode'] = 'vbr'; } else { $info['mpeg']['audio']['bitrate_mode'] = 'cbr'; } $info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode']; } break; // exit while() } } ++$SynchSeekOffset; if ($avdataoffset + $SynchSeekOffset >= $info['avdataend']) { // end of file/data if (empty($info['mpeg']['audio'])) { $info['error'][] = 'could not find valid MPEG synch before end of file'; if (isset($info['audio']['bitrate'])) { unset($info['audio']['bitrate']); } if (isset($info['mpeg']['audio'])) { unset($info['mpeg']['audio']); } if (isset($info['mpeg']) && (!is_array($info['mpeg']) || empty($info['mpeg']))) { unset($info['mpeg']); } return false; } break; } } $info['audio']['channels'] = $info['mpeg']['audio']['channels']; $info['audio']['channelmode'] = $info['mpeg']['audio']['channelmode']; $info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate']; return true; }
/** * @param type $BlockData * * @return bool */ private function parseSEEKTABLE($BlockData) { $info =& $this->getid3->info; $offset = 0; $BlockLength = strlen($BlockData); $placeholderpattern = str_repeat("ÿ", 8); while ($offset < $BlockLength) { $SampleNumberString = substr($BlockData, $offset, 8); $offset += 8; if ($SampleNumberString == $placeholderpattern) { // placeholder point Helper::safe_inc($info['flac']['SEEKTABLE']['placeholders'], 1); $offset += 10; } else { $SampleNumber = Helper::BigEndian2Int($SampleNumberString); $info['flac']['SEEKTABLE'][$SampleNumber]['offset'] = Helper::BigEndian2Int(substr($BlockData, $offset, 8)); $offset += 8; $info['flac']['SEEKTABLE'][$SampleNumber]['samples'] = Helper::BigEndian2Int(substr($BlockData, $offset, 2)); $offset += 2; } } return true; }
/** * @return bool */ public function analyze() { $info =& $this->getid3->info; $OriginalAVdataOffset = $info['avdataoffset']; fseek($this->getid3->fp, $info['avdataoffset'], SEEK_SET); $VOCheader = fread($this->getid3->fp, 26); $magic = 'Creative Voice File'; if (substr($VOCheader, 0, 19) != $magic) { $info['error'][] = 'Expecting "' . Helper::PrintHexBytes($magic) . '" at offset ' . $info['avdataoffset'] . ', found "' . Helper::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'] = Helper::LittleEndian2Int(substr($VOCheader, 20, 2)); $thisfile_voc['header']['minor_version'] = Helper::LittleEndian2Int(substr($VOCheader, 22, 1)); $thisfile_voc['header']['major_version'] = Helper::LittleEndian2Int(substr($VOCheader, 23, 1)); do { $BlockOffset = ftell($this->getid3->fp); $BlockData = fread($this->getid3->fp, 4); $BlockType = ord($BlockData[0]); $BlockSize = Helper::LittleEndian2Int(substr($BlockData, 1, 3)); $ThisBlock = array(); Helper::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 .= fread($this->getid3->fp, 2); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { $info['avdataoffset'] = ftell($this->getid3->fp); } fseek($this->getid3->fp, $BlockSize - 2, SEEK_CUR); $ThisBlock['sample_rate_id'] = Helper::LittleEndian2Int(substr($BlockData, 4, 1)); $ThisBlock['compression_type'] = Helper::LittleEndian2Int(substr($BlockData, 5, 1)); $ThisBlock['compression_name'] = $this->VOCcompressionTypeLookup($ThisBlock['compression_type']); if ($ThisBlock['compression_type'] <= 3) { $thisfile_voc['compressed_bits_per_sample'] = Helper::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'] = Helper::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 fseek($this->getid3->fp, $BlockSize, SEEK_CUR); break; case 8: // Extended $BlockData .= fread($this->getid3->fp, 4); //00-01 Time Constant: // Mono: 65536 - (256000000 / sample_rate) // Stereo: 65536 - (256000000 / (sample_rate * 2)) $ThisBlock['time_constant'] = Helper::LittleEndian2Int(substr($BlockData, 4, 2)); $ThisBlock['pack_method'] = Helper::LittleEndian2Int(substr($BlockData, 6, 1)); $ThisBlock['stereo'] = (bool) Helper::LittleEndian2Int(substr($BlockData, 7, 1)); $thisfile_audio['channels'] = $ThisBlock['stereo'] ? 2 : 1; $thisfile_audio['sample_rate'] = Helper::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 .= fread($this->getid3->fp, 12); if ($info['avdataoffset'] <= $OriginalAVdataOffset) { $info['avdataoffset'] = ftell($this->getid3->fp); } fseek($this->getid3->fp, $BlockSize - 12, SEEK_CUR); $ThisBlock['sample_rate'] = Helper::LittleEndian2Int(substr($BlockData, 4, 4)); $ThisBlock['bits_per_sample'] = Helper::LittleEndian2Int(substr($BlockData, 8, 1)); $ThisBlock['channels'] = Helper::LittleEndian2Int(substr($BlockData, 9, 1)); $ThisBlock['wFormat'] = Helper::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; fseek($this->getid3->fp, $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 fseek($this->getid3->fp, -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; }
/** * @staticvar array $decbin * * @param type $MaxFramesToScan * @param type $ReturnExtendedInfo * * @return bool */ 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 (!Helper::intValueSupported($byteoffset)) { $info['warning'][] = 'Unable to parse AAC file beyond ' . ftell($this->getid3->fp) . ' (PHP does not support file operations beyond ' . round(PHP_INT_MAX / 1073741824) . 'GB)'; return false; } fseek($this->getid3->fp, $byteoffset, SEEK_SET); // First get substring $substring = fread($this->getid3->fp, 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 ' . (ftell($this->getid3->fp) - $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 = Helper::BigEndian2Int(substr($substring, 0, 2)); $header2 = Helper::BigEndian2Int(substr($substring, 2, 4)); $header3 = Helper::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 ' . (ftell($this->getid3->fp) - $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; } Helper::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'] = GetId3_lib::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; getid3_lib::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. }