: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
if (isset($thisfile_mpeg_audio['VBR_frames']) ? $thisfile_mpeg_audio['VBR_frames'] : '') {
switch ($thisfile_mpeg_audio['bitrate_mode']) {
if (($thisfile_mpeg_audio['version'] == '1') && ($thisfile_mpeg_audio['layer'] == 1)) {
} elseif ((($thisfile_mpeg_audio['version'] == '2') || ($thisfile_mpeg_audio['version'] == '2.5')) && ($thisfile_mpeg_audio['layer'] == 3)) {
$thisfile_mpeg_audio['VBR_bitrate'] = (isset($thisfile_mpeg_audio['VBR_bytes']) ? (($thisfile_mpeg_audio['VBR_bytes'] / $thisfile_mpeg_audio['VBR_frames']) * 8) * ($info['audio']['sample_rate'] / $bytes_per_frame) : 0);
if ($thisfile_mpeg_audio['VBR_bitrate'] > 0) {
$info['audio']['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate'];
$thisfile_mpeg_audio['bitrate'] = $thisfile_mpeg_audio['VBR_bitrate']; // to avoid confusion
// End variable-bitrate headers
////////////////////////////////////////////////////////////////////////////////////
if (!$this->RecursiveFrameScanning($offset, $nextframetestoffset, $ScanAsCBR)) {
if (!empty($this->getid3->info['mp3_validity_check_bitrates']) && !empty($thisfile_mpeg_audio['bitrate_mode']) && ($thisfile_mpeg_audio['bitrate_mode'] == 'vbr') && !empty($thisfile_mpeg_audio['VBR_bitrate'])) {
// https://github.com/JamesHeinrich/getID3/issues/287
if (count(array_keys($this->getid3->info['mp3_validity_check_bitrates'])) == 1) {
list($cbr_bitrate_in_short_scan) = array_keys($this->getid3->info['mp3_validity_check_bitrates']);
$deviation_cbr_from_header_bitrate = abs($thisfile_mpeg_audio['VBR_bitrate'] - $cbr_bitrate_in_short_scan) / $cbr_bitrate_in_short_scan;
if ($deviation_cbr_from_header_bitrate < 0.01) {
// VBR header bitrate may differ slightly from true bitrate of frames, perhaps accounting for overhead of VBR header frame itself?
// If measured CBR bitrate is within 1% of specified bitrate in VBR header then assume that file is truly CBR
$thisfile_mpeg_audio['bitrate_mode'] = 'cbr';
//$this->warning('VBR header ignored, assuming CBR '.round($cbr_bitrate_in_short_scan / 1000).'kbps based on scan of '.$this->mp3_valid_check_frames.' frames');
if (isset($this->getid3->info['mp3_validity_check_bitrates'])) {
unset($this->getid3->info['mp3_validity_check_bitrates']);
// // experimental side info parsing section - not returning anything useful yet
// $SideInfoBitstream = getid3_lib::BigEndian2Bin($SideInfoData);
// if ($thisfile_mpeg_audio['version'] == '1') {
// if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// // MPEG-1 (stereo, joint-stereo, dual-channel)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// if ($thisfile_mpeg_audio['channelmode'] == 'mono') {
// // MPEG-2, MPEG-2.5 (mono)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// // MPEG-2, MPEG-2.5 (stereo, joint-stereo, dual-channel)
// $thisfile_mpeg_audio['side_info']['main_data_begin'] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// if ($thisfile_mpeg_audio['version'] == '1') {
// for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
// for ($scfsi_band = 0; $scfsi_band < 4; $scfsi_band++) {
// $thisfile_mpeg_audio['scfsi'][$channel][$scfsi_band] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// for ($granule = 0; $granule < (($thisfile_mpeg_audio['version'] == '1') ? 2 : 1); $granule++) {
// for ($channel = 0; $channel < $info['audio']['channels']; $channel++) {
// $thisfile_mpeg_audio['part2_3_length'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 12);
// $SideInfoOffset += 12;
// $thisfile_mpeg_audio['big_values'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $thisfile_mpeg_audio['global_gain'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 8);
// if ($thisfile_mpeg_audio['version'] == '1') {
// $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
// $thisfile_mpeg_audio['scalefac_compress'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 9);
// $thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// if ($thisfile_mpeg_audio['window_switching_flag'][$granule][$channel] == '1') {
// $thisfile_mpeg_audio['block_type'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 2);
// $thisfile_mpeg_audio['mixed_block_flag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// for ($region = 0; $region < 2; $region++) {
// $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
// $thisfile_mpeg_audio['table_select'][$granule][$channel][2] = 0;
// for ($window = 0; $window < 3; $window++) {
// $thisfile_mpeg_audio['subblock_gain'][$granule][$channel][$window] = substr($SideInfoBitstream, $SideInfoOffset, 3);
// for ($region = 0; $region < 3; $region++) {
// $thisfile_mpeg_audio['table_select'][$granule][$channel][$region] = substr($SideInfoBitstream, $SideInfoOffset, 5);
// $thisfile_mpeg_audio['region0_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 4);
// $thisfile_mpeg_audio['region1_count'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 3);
// $thisfile_mpeg_audio['block_type'][$granule][$channel] = 0;
// if ($thisfile_mpeg_audio['version'] == '1') {
// $thisfile_mpeg_audio['preflag'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $thisfile_mpeg_audio['scalefac_scale'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
// $thisfile_mpeg_audio['count1table_select'][$granule][$channel] = substr($SideInfoBitstream, $SideInfoOffset, 1);
* @param int $nextframetestoffset
public function RecursiveFrameScanning(&$offset, &$nextframetestoffset, $ScanAsCBR) {
$info = &$this->getid3->info;
$firstframetestarray = array('error' => array(), 'warning'=> array(), 'avdataend' => $info['avdataend'], 'avdataoffset' => $info['avdataoffset']);
$this->decodeMPEGaudioHeader($offset, $firstframetestarray, false);
$info['mp3_validity_check_bitrates'] = array();
for ($i = 0; $i < $this->mp3_valid_check_frames; $i++) {
// check next (default: 50) frames for validity, to make sure we haven't run across a false synch
if (($nextframetestoffset + 4) >= $info['avdataend']) {
$nextframetestarray = array('error' => array(), 'warning' => array(), 'avdataend' => $info['avdataend'], 'avdataoffset'=>$info['avdataoffset']);
if ($this->decodeMPEGaudioHeader($nextframetestoffset, $nextframetestarray, false)) {
/** @phpstan-ignore-next-line */
getid3_lib::safe_inc($info['mp3_validity_check_bitrates'][$nextframetestarray['mpeg']['audio']['bitrate']]);
// force CBR mode, used for trying to pick out invalid audio streams with valid(?) VBR headers, or VBR streams with no VBR header
if (!isset($nextframetestarray['mpeg']['audio']['bitrate']) || !isset($firstframetestarray['mpeg']['audio']['bitrate']) || ($nextframetestarray['mpeg']['audio']['bitrate'] != $firstframetestarray['mpeg']['audio']['bitrate'])) {
// next frame is OK, get ready to check the one after that
if (isset($nextframetestarray['mpeg']['audio']['framelength']) && ($nextframetestarray['mpeg']['audio']['framelength'] > 0)) {
$nextframetestoffset += $nextframetestarray['mpeg']['audio']['framelength'];
$this->error('Frame at offset ('.$offset.') is has an invalid frame length.');
} elseif (!empty($firstframetestarray['mpeg']['audio']['framelength']) && (($nextframetestoffset + $firstframetestarray['mpeg']['audio']['framelength']) > $info['avdataend'])) {
// it's not the end of the file, but there's not enough data left for another frame, so assume it's garbage/padding and return OK
// next frame is not valid, note the error and fail, so scanning can contiue for a valid frame sequence
$this->warning('Frame at offset ('.$offset.') is valid, but the next one at ('.$nextframetestoffset.') is not.');
public function FreeFormatFrameLength($offset, $deepscan=false) {
$info = &$this->getid3->info;
$MPEGaudioData = $this->fread(32768);
$SyncPattern1 = substr($MPEGaudioData, 0, 4);
// may be different pattern due to padding
$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) | 0x02).$SyncPattern1[3];
if ($SyncPattern2 === $SyncPattern1) {
$SyncPattern2 = $SyncPattern1[0].$SyncPattern1[1].chr(ord($SyncPattern1[2]) & 0xFD).$SyncPattern1[3];
$framelength1 = strpos($MPEGaudioData, $SyncPattern1, 4);
$framelength2 = strpos($MPEGaudioData, $SyncPattern2, 4);
$framelength = $framelength1;
if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
$framelength = $framelength2;
// LAME 3.88 has a different value for modeextension on the first frame vs the rest
$framelength1 = strpos($MPEGaudioData, substr($SyncPattern1, 0, 3), 4);
$framelength2 = strpos($MPEGaudioData, substr($SyncPattern2, 0, 3), 4);
$framelength = $framelength1;
if (($framelength2 > 4) && ($framelength2 < $framelength1)) {
$framelength = $framelength2;
$this->error('Cannot find next free-format synch pattern ('.getid3_lib::PrintHexBytes($SyncPattern1).' or '.getid3_lib::PrintHexBytes($SyncPattern2).') after offset '.$offset);
$this->warning('ModeExtension varies between first frame and other frames (known free-format issue in LAME 3.88)');
$info['audio']['codec'] = 'LAME';
$info['audio']['encoder'] = 'LAME3.88';
$SyncPattern1 = substr($SyncPattern1, 0, 3);
$SyncPattern2 = substr($SyncPattern2, 0, 3);
$ActualFrameLengthValues = array();
$nextoffset = $offset + $framelength;
while ($nextoffset < ($info['avdataend'] - 6)) {
$this->fseek($nextoffset - 1);
$NextSyncPattern = $this->fread(6);
if ((substr($NextSyncPattern, 1, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 1, strlen($SyncPattern2)) == $SyncPattern2)) {
// good - found where expected
$ActualFrameLengthValues[] = $framelength;
} elseif ((substr($NextSyncPattern, 0, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 0, strlen($SyncPattern2)) == $SyncPattern2)) {
// ok - found one byte earlier than expected (last frame wasn't padded, first frame was)
$ActualFrameLengthValues[] = ($framelength - 1);
} elseif ((substr($NextSyncPattern, 2, strlen($SyncPattern1)) == $SyncPattern1) || (substr($NextSyncPattern, 2, strlen($SyncPattern2)) == $SyncPattern2)) {
// ok - found one byte later than expected (last frame was padded, first frame wasn't)
$ActualFrameLengthValues[] = ($framelength + 1);
$this->error('Did not find expected free-format sync pattern at offset '.$nextoffset);
$nextoffset += $framelength;
if (count($ActualFrameLengthValues) > 0) {
$framelength = intval(round(array_sum($ActualFrameLengthValues) / count($ActualFrameLengthValues)));
public function getOnlyMPEGaudioInfoBruteForce() {
$MPEGaudioHeaderDecodeCache = array();
$MPEGaudioHeaderValidCache = array();
$MPEGaudioHeaderLengthCache = array();
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
$MPEGaudioFrequencyLookup = self::MPEGaudioFrequencyArray();
$MPEGaudioChannelModeLookup = self::MPEGaudioChannelModeArray();
$MPEGaudioModeExtensionLookup = self::MPEGaudioModeExtensionArray();
$MPEGaudioEmphasisLookup = self::MPEGaudioEmphasisArray();
$LongMPEGversionLookup = array();
$LongMPEGlayerLookup = array();
$LongMPEGbitrateLookup = array();
$LongMPEGpaddingLookup = array();
$LongMPEGfrequencyLookup = array();
$Distribution['bitrate'] = array();
$Distribution['frequency'] = array();
$Distribution['layer'] = array();
$Distribution['version'] = array();
$Distribution['padding'] = array();
$info = &$this->getid3->info;
$this->fseek($info['avdataoffset']);
$previousvalidframe = $info['avdataoffset'];
while ($this->ftell() < $info['avdataend']) {
$head4 = $this->fread(4);
if (strlen($head4) < 4) {
if ($head4[0] != "\xFF") {
for ($i = 1; $i < 4; $i++) {
if ($head4[$i] == "\xFF") {
$this->fseek($i - 4, SEEK_CUR);
if (!isset($MPEGaudioHeaderDecodeCache[$head4])) {
$MPEGaudioHeaderDecodeCache[$head4] = self::MPEGaudioHeaderDecode($head4);
if (!isset($MPEGaudioHeaderValidCache[$head4])) {
$MPEGaudioHeaderValidCache[$head4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$head4], false, false);
if ($MPEGaudioHeaderValidCache[$head4]) {
if (!isset($MPEGaudioHeaderLengthCache[$head4])) {
$LongMPEGversionLookup[$head4] = $MPEGaudioVersionLookup[$MPEGaudioHeaderDecodeCache[$head4]['version']];
$LongMPEGlayerLookup[$head4] = $MPEGaudioLayerLookup[$MPEGaudioHeaderDecodeCache[$head4]['layer']];
$LongMPEGbitrateLookup[$head4] = $MPEGaudioBitrateLookup[$LongMPEGversionLookup[$head4]][$LongMPEGlayerLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['bitrate']];
$LongMPEGpaddingLookup[$head4] = (bool) $MPEGaudioHeaderDecodeCache[$head4]['padding'];
$LongMPEGfrequencyLookup[$head4] = $MPEGaudioFrequencyLookup[$LongMPEGversionLookup[$head4]][$MPEGaudioHeaderDecodeCache[$head4]['sample_rate']];
$MPEGaudioHeaderLengthCache[$head4] = self::MPEGaudioFrameLength(
$LongMPEGbitrateLookup[$head4],
$LongMPEGversionLookup[$head4],
$LongMPEGlayerLookup[$head4],
$LongMPEGpaddingLookup[$head4],
$LongMPEGfrequencyLookup[$head4]);
if ($MPEGaudioHeaderLengthCache[$head4] > 4) {
$WhereWeWere = $this->ftell();
$this->fseek($MPEGaudioHeaderLengthCache[$head4] - 4, SEEK_CUR);
$next4 = $this->fread(4);
if ($next4[0] == "\xFF") {
if (!isset($MPEGaudioHeaderDecodeCache[$next4])) {
$MPEGaudioHeaderDecodeCache[$next4] = self::MPEGaudioHeaderDecode($next4);
if (!isset($MPEGaudioHeaderValidCache[$next4])) {
$MPEGaudioHeaderValidCache[$next4] = self::MPEGaudioHeaderValid($MPEGaudioHeaderDecodeCache[$next4], false, false);
if ($MPEGaudioHeaderValidCache[$next4]) {
$this->fseek(-4, SEEK_CUR);
$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] = isset($Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]]) ? ++$Distribution['bitrate'][$LongMPEGbitrateLookup[$head4]] : 1;
$Distribution['layer'][$LongMPEGlayerLookup[$head4]] = isset($Distribution['layer'][$LongMPEGlayerLookup[$head4]]) ? ++$Distribution['layer'][$LongMPEGlayerLookup[$head4]] : 1;
$Distribution['version'][$LongMPEGversionLookup[$head4]] = isset($Distribution['version'][$LongMPEGversionLookup[$head4]]) ? ++$Distribution['version'][$LongMPEGversionLookup[$head4]] : 1;
$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] = isset($Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])]) ? ++$Distribution['padding'][intval($LongMPEGpaddingLookup[$head4])] : 1;
$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] = isset($Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]]) ? ++$Distribution['frequency'][$LongMPEGfrequencyLookup[$head4]] : 1;
if (++$frames_scanned >= $max_frames_scan) {
$pct_data_scanned = getid3_lib::SafeDiv($this->ftell() - $info['avdataoffset'], $info['avdataend'] - $info['avdataoffset']);
$this->warning('too many MPEG audio frames to scan, only scanned first '.$max_frames_scan.' frames ('.number_format($pct_data_scanned * 100, 1).'% of file) and extrapolated distribution, playtime and bitrate may be incorrect.');
foreach ($Distribution as $key1 => $value1) {
foreach ($value1 as $key2 => $value2) {
$Distribution[$key1][$key2] = $pct_data_scanned ? round($value2 / $pct_data_scanned) : 1;
$this->fseek($WhereWeWere - 3);
foreach ($Distribution as $key => $value) {
ksort($Distribution[$key], SORT_NUMERIC);
ksort($Distribution['version'], SORT_STRING);
$info['mpeg']['audio']['bitrate_distribution'] = $Distribution['bitrate'];
$info['mpeg']['audio']['frequency_distribution'] = $Distribution['frequency'];
$info['mpeg']['audio']['layer_distribution'] = $Distribution['layer'];
$info['mpeg']['audio']['version_distribution'] = $Distribution['version'];
$info['mpeg']['audio']['padding_distribution'] = $Distribution['padding'];
if (count($Distribution['version']) > 1) {
$this->error('Corrupt file - more than one MPEG version detected');
if (count($Distribution['layer']) > 1) {
$this->error('Corrupt file - more than one MPEG layer detected');
if (count($Distribution['frequency']) > 1) {
$this->error('Corrupt file - more than one MPEG sample rate detected');
foreach ($Distribution['bitrate'] as $bitratevalue => $bitratecount) {
if ($bitratevalue != 'free') {
$bittotal += ($bitratevalue * $bitratecount);
$info['mpeg']['audio']['frame_count'] = array_sum($Distribution['bitrate']);
if ($info['mpeg']['audio']['frame_count'] == 0) {
$this->error('no MPEG audio frames found');
$info['mpeg']['audio']['bitrate'] = ($bittotal / $info['mpeg']['audio']['frame_count']);
$info['mpeg']['audio']['bitrate_mode'] = ((count($Distribution['bitrate']) > 0) ? 'vbr' : 'cbr');
$info['mpeg']['audio']['sample_rate'] = getid3_lib::array_max($Distribution['frequency'], true);
$info['audio']['bitrate'] = $info['mpeg']['audio']['bitrate'];
$info['audio']['bitrate_mode'] = $info['mpeg']['audio']['bitrate_mode'];
$info['audio']['sample_rate'] = $info['mpeg']['audio']['sample_rate'];
$info['audio']['dataformat'] = 'mp'.getid3_lib::array_max($Distribution['layer'], true);
$info['fileformat'] = $info['audio']['dataformat'];
* @param int $avdataoffset
* @param bool $BitrateHistogram
public function getOnlyMPEGaudioInfo($avdataoffset, $BitrateHistogram=false) {
// looks for synch, decodes MPEG audio header
$info = &$this->getid3->info;
static $MPEGaudioVersionLookup;
static $MPEGaudioLayerLookup;
static $MPEGaudioBitrateLookup;
if (empty($MPEGaudioVersionLookup)) {
$MPEGaudioVersionLookup = self::MPEGaudioVersionArray();
$MPEGaudioLayerLookup = self::MPEGaudioLayerArray();
$MPEGaudioBitrateLookup = self::MPEGaudioBitrateArray();
$this->fseek($avdataoffset);
$sync_seek_buffer_size = min(128 * 1024, $info['avdataend'] - $avdataoffset);
if ($sync_seek_buffer_size <= 0) {
$this->error('Invalid $sync_seek_buffer_size at offset '.$avdataoffset);
$header = $this->fread($sync_seek_buffer_size);
$sync_seek_buffer_size = strlen($header);
$SyncSeekAttemptsMax = 1000;
$FirstFrameThisfileInfo = null;
while ($SynchSeekOffset < $sync_seek_buffer_size) {
if ((($avdataoffset + $SynchSeekOffset) < $info['avdataend']) && !$this->feof()) {
if ($SynchSeekOffset > $sync_seek_buffer_size) {
// if a synch's not found within the first 128k bytes, then give up
$this->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'])) {
if (($SynchSeekOffset + 1) >= strlen($header)) {
$this->error('Could not find valid MPEG synch before end of file');