For more info see: Analyzing Audio Streams
<?php
// GPL by Peer-Axel Kroeske peeraxel@aol.com
// see more on fmstream.org/analyzing-audio-streams.htm
// mp3 check modified from https://gist.github.com/fastest963/2357002
// todo:
// - AAC check works only correct for LC. If SBR is added (HE-AAC) sample rate must be doubled. If PS parametric stereo (HE-AACv2) is added, output shows mono but should be stereo. SBR/PS cannot be detected yet.
// no idea yet how to detect the he_aac_super_frame_header with sbr_flag, sbc_channel_mode and ps_flag as described in https://www.etsi.org/deliver/etsi_ts/101700_101799/101756/02.02.01_60/ts_101756v020201p.pdf (is that only valid for DAB+ radio?)
// - MP3 VBR (variable bit rate) is used if multiple probes lead to different results
function decbin8($s) {
$s=decbin($s);
while (strlen($s)<8) {$s='0'.$s;}
return $s;}
function audioCodecInfo($filename,$descr='') { // if descr given the probe will be stored as sample-(descr)
$descr=str_replace('/','-',$descr);
$size=4096;
$bit_rates = array(
array(0,0,0,0,0),
array(32,32,32,32,8),
array(64,48,40,48,16),
array(96,56,48,56,24),
array(128,64,56,64,32),
array(160,80,64,80,40),
array(192,96,80,96,48),
array(224,112,96,112,56),
array(256,128,112,128,64),
array(288,160,128,144,80),
array(320,192,160,160,96),
array(352,224,192,176,112),
array(384,256,224,192,128),
array(416,320,256,224,144),
array(448,384,320,256,160),
array(-1,-1,-1,-1,-1),
);
$sample_rates = array( //index 3 speculative
array(11025,12000,8000,12000), //mpeg 2.5
array(0,0,0),
array(22050,24000,16000,24000), //mpeg 2
array(44100,48000,32000,48000), //mpeg 1
);
$adtsSr=array(96000,88200,64000,48000,44100,32000,24000,22050,16000,12000,11025,8000,7350);
$version=array('MPEG2.5','none','MPEG2','MPEG1');
$aacType=array('Main','LC','SSR','LTP');
$codecName=array('','mp3','mp2','mp3','aac'); // [1] can be anything
$aacSt=array(0,1,2,3,4,5,6,8);
$mp3St=array(2,2,2,1);
$mp3StMode=array('Stereo','Joint stereo','Dual channel','Mono');
$fileData = array('bit_rate' => 0, 'sample_rate' => 0);
$fp = @fopen($filename, 'r');
if (!$fp) {
return false;
}
$data = fread($fp,$size);
if ($descr) {
$f2=fopen('sample-'.$descr,'w');
fwrite($f2,$data);
fclose($f2);}
$b = unpack('C*', $data);
for ($o = 1; $o < count($b) - 4; $o++) {
//AAAAAAAA AAABBCCD EEEEFFGH IIJJKLMM from http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html
if (($b[$o] & 255) == 255 && ($b[$o+1] & 224) == 224) {
$vIdx = ($b[$o+1] & 24) >> 3; //get BB
$r = array();
if ($vIdx!=1) {
//print "<b>".decbin8($b[$o]).' '.decbin8($b[$o+1]).' '.decbin8($b[$o+2]).' '.decbin8($b[$o+3])."</b> $descr ";
$r = array();
$r['version']=$version[$vIdx];
$r['layer'] = abs((($b[$o+1] & 6) >> 1) - 4); //get CC (1 -> 3), then invert
$r['codec_name']=$codecName[$r['layer']];
$r['padding'] = ($b[$o+2] & 2) >> 1; //get G
if ($r['layer']==4) {
//AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP QQQQQQQQ QQQQQQQQ from https://wiki.multimedia.cx/index.php?title=ADTS
$r['channels']=$aacSt[($b[$o+2]&1)*4+($b[$o+3]&192)>>6];
$sr=$adtsSr[($b[$o+2]&60)>>2]; //get FFFF sample rate per channel
$r['sample_rate']=$sr*$r['channels'];
$frames=0;
//count full frames within probe, AAC always has 1024 samples per frame, bit_rate=$sr/1024*$bytesperframe*8;
for($x=1;$x<count($b)-3;$x++) {if ($b[$x]==$b[$o] and $b[$x+1]==$b[$o+1] and $b[$x+2]==$b[$o+2] and $b[$x+3]==$b[$o+3]) {$frames++;$last=$x;}}
$r['bit_rate']=($frames>1?round($sr*($last-$o)/($frames-1)/128):round($sr*count($b)/128));
$r['frames']=$frames;
$r['profile']=$aacType[($b[$o+2]&192)>>6];}
else {
$chIdx=($b[$o+3]&192)>>6;
$r['channels']=$mp3St[$chIdx];
$r['profile']=$mp3StMode[$chIdx];
$srIndex = ($b[$o+2] & 12) >> 2; //get FF (0 -> 3)
$brRow = ($b[$o+2] & 240) >> 4; //get EEEE (0 -> 15)
$r['sample_rate'] = $sample_rates[$vIdx][$srIndex];
if ($vIdx & 1 == 1) {$r['bit_rate'] = $bit_rates[$brRow][$r['layer']-1];}
else {$r['bit_rate'] = $bit_rates[$brRow][($r['layer'] & 2 >> 1)+3]*1000;}} //v2 and l1 or l2/l3 (3 is the offset in the arrays)
return $r;}}
}}
if (isset($_GET['u'])) {
header('Content-type:application/json');
print json_encode(audioCodecInfo($_GET['u']));}
/*
some checks, in brackets the results from ffprobe for comparison
print'<br>';var_dump(audioCodecInfo('http://sc8.radiocaroline.net:8010/;','aac48/44/2')); //HE-AAC
print'<br>';var_dump(audioCodecInfo('http://ic2.christiannetcast.com/wdeo-fm','aac32/32/2')); //HE-AACv2
print'<br>';var_dump(audioCodecInfo('http://njpr.wnyc.org/wnycam-tunein.aac','aac64/44/2')); //AAC-LC
print'<br>';var_dump(audioCodecInfo('http://paineldj6.com.br:11750/live','aac128/44/2')); //the rest works fine
print'<br>';var_dump(audioCodecInfo('http://01.solumedia.com.ar:8092/;','aac36/44/2'));
print'<br>';var_dump(audioCodecInfo('http://s5.viastreaming.net:7100/;','aac20/24/1'));
print'<br>';var_dump(audioCodecInfo('http://sc8.radiocaroline.net:8000/;','mp64/24/2'));
print'<br>';var_dump(audioCodecInfo('http://addrad.io/4WRKdZ','mp128/48/2'));
*/
?>