PHP: Check bitrate and samplerate of MP3 and AAC streams

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')); */ ?>