wip on MP3
This commit is contained in:
parent
e8177c3d2d
commit
194b0fc284
1 changed files with 207 additions and 5 deletions
|
@ -394,10 +394,203 @@ function extractFragmentedMP4( $filename ) {
|
||||||
return $segments;
|
return $segments;
|
||||||
}
|
}
|
||||||
|
|
||||||
function consolidate( $target, $segments ) {
|
// http://www.mp3-tech.org/programmer/frame_header.html
|
||||||
$out = [
|
|
||||||
'init' => $segments['init'],
|
class MP3FrameHeader {
|
||||||
|
private $header;
|
||||||
|
|
||||||
|
public $valid = false;
|
||||||
|
public $size = 0;
|
||||||
|
|
||||||
|
public $sync;
|
||||||
|
public $mpeg;
|
||||||
|
public $layer;
|
||||||
|
public $protection;
|
||||||
|
public $bitrate;
|
||||||
|
public $sampleRate;
|
||||||
|
public $padding;
|
||||||
|
|
||||||
|
private static $bits = [
|
||||||
|
'sync' => [ 21, 11 ],
|
||||||
|
'mpeg' => [ 19, 2 ],
|
||||||
|
'layer' => [ 17, 2 ],
|
||||||
|
'protection' => [ 16, 1 ],
|
||||||
|
'bitrate' => [ 12, 4 ],
|
||||||
|
'sampleRate' => [ 10, 2 ],
|
||||||
|
'padding' => [ 9, 1 ],
|
||||||
|
'private' => [ 8, 1 ], // not needed below this
|
||||||
|
'channelMode' => [ 6, 2 ],
|
||||||
|
'modeExt' => [ 4, 2 ],
|
||||||
|
'copyright' => [ 3, 1 ],
|
||||||
|
'original' => [ 2, 1 ],
|
||||||
|
'emphasis' => [ 0, 2 ],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
private const SYNC_MASK = 0x7ff;
|
||||||
|
|
||||||
|
// 1s used for reserved slots to avoid exploding
|
||||||
|
// in case of invalid input
|
||||||
|
private static $sampleRates = [
|
||||||
|
// MPEG-2.5
|
||||||
|
[ 11025, 12000, 8000, 1 ],
|
||||||
|
// Reserved
|
||||||
|
[ 1, 1, 1, 1 ],
|
||||||
|
// MPEG-2
|
||||||
|
[ 22050, 24000, 16000, 1 ],
|
||||||
|
// MPEG-1
|
||||||
|
[ 44100, 48000, 32000, 1 ],
|
||||||
|
];
|
||||||
|
|
||||||
|
private static $bitrates = [
|
||||||
|
[
|
||||||
|
// MPEG-2
|
||||||
|
// invalid
|
||||||
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ],
|
||||||
|
// layer 3
|
||||||
|
[ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 ],
|
||||||
|
// layer 2
|
||||||
|
[ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 ],
|
||||||
|
// layer 1
|
||||||
|
[ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 ],
|
||||||
|
],
|
||||||
|
[
|
||||||
|
// MPEG-1
|
||||||
|
// invalid
|
||||||
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ],
|
||||||
|
// layer 3
|
||||||
|
[ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320, 0 ],
|
||||||
|
// layer 2
|
||||||
|
[ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 ],
|
||||||
|
// layer 1
|
||||||
|
[ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 316, 448, 0 ],
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
private function field( $name ) {
|
||||||
|
[ $shift, $bits ] = self::$bits[$name];
|
||||||
|
$mask = ( 1 << $bits ) - 1;
|
||||||
|
return ( $this->header >> $shift ) & $mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function __construct( $header ) {
|
||||||
|
$this->header = $header;
|
||||||
|
|
||||||
|
$this->sync = $this->field( 'sync' ) == self::SYNC_MASK;
|
||||||
|
if ( $this->sync ) {
|
||||||
|
$this->mpeg = $this->field( 'mpeg' );
|
||||||
|
$this->layer = $this->field( 'layer' );
|
||||||
|
$this->protection = $this->field( 'protection' );
|
||||||
|
$br = $this->field( 'bitrate' );
|
||||||
|
$this->bitrate = 1000 * self::$bitrates[$this->mpeg & 1][$this->layer][$br];
|
||||||
|
$sr = $this->field( 'sampleRate' );
|
||||||
|
$this->sampleRate = self::$sampleRates[$this->mpeg][$sr];
|
||||||
|
$this->padding = $this->field( 'padding' );
|
||||||
|
|
||||||
|
$this->valid = $this->sync;
|
||||||
|
if ( $this->bitrate == 0 ) {
|
||||||
|
var_dump( $this );
|
||||||
|
echo "br: $br\n";
|
||||||
|
//throw new Exception( "Invalid bitrate" );
|
||||||
|
$this->valid = false;
|
||||||
|
}
|
||||||
|
if ( $this->sampleRate == 1 ) {
|
||||||
|
var_dump( $this );
|
||||||
|
echo "sr: $sr\n";
|
||||||
|
//throw new Exception( "Invalid sample rate" );
|
||||||
|
$this->valid = false;
|
||||||
|
}
|
||||||
|
$this->size = intval( 144.0 * $this->bitrate / $this->sampleRate );
|
||||||
|
if ( $this->protection ) {
|
||||||
|
$this->size += 2;
|
||||||
|
}
|
||||||
|
if ( $this->padding ) {
|
||||||
|
$this->size++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MP3Reader {
|
||||||
|
|
||||||
|
private $file;
|
||||||
|
|
||||||
|
public function __construct( $file ) {
|
||||||
|
$this->file = $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pos() {
|
||||||
|
return ftell( $this->file );
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readFrame() {
|
||||||
|
while ( true ) {
|
||||||
|
// Look for the sync pattern
|
||||||
|
$start = $this->pos();
|
||||||
|
$bytes = fread( $this->file, 4 );
|
||||||
|
if ( $bytes === false || strlen( $bytes ) < 4 ) {
|
||||||
|
// end of file
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = unpack( "Nval", $bytes );
|
||||||
|
$header = new MP3FrameHeader( $data['val'] );
|
||||||
|
if ( $header->valid ) {
|
||||||
|
// Note we don't need the data at this time.
|
||||||
|
//fseek( $this->file, $start + $header->size, SEEK_SET );
|
||||||
|
var_dump ( $header );
|
||||||
|
fread( $this->file, $header->size - 4 );
|
||||||
|
return $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
$x = hexdump( $bytes );
|
||||||
|
print "BACKUP $x\n";
|
||||||
|
|
||||||
|
if ( $header->sync && !$header->valid ) {
|
||||||
|
$x = hexdump( $bytes );
|
||||||
|
die('xxx ' . $x);
|
||||||
|
}
|
||||||
|
|
||||||
|
// back up 3 bytes and try again
|
||||||
|
fseek( $this->file, $start + 1, SEEK_SET );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMP3( $filename ) {
|
||||||
|
$file = fopen( $filename, 'rb' );
|
||||||
|
if ( !$file ) {
|
||||||
|
throw new Exception( 'Error opening MP3 file' );
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
$reader = new MP3Reader( $file );
|
||||||
|
$segments = [];
|
||||||
|
$timestamp = 0.0;
|
||||||
|
while ( true ) {
|
||||||
|
$start = $reader->pos();
|
||||||
|
$frame = $reader->readFrame();
|
||||||
|
if ( !$frame ) {
|
||||||
|
return $segments;
|
||||||
|
}
|
||||||
|
$duration = 144.0 / $frame->sampleRate;
|
||||||
|
$segments[] = [
|
||||||
|
'start' => $start,
|
||||||
|
'size' => $frame->size,
|
||||||
|
'timestamp' => $timestamp,
|
||||||
|
'duration' => $duration,
|
||||||
|
];
|
||||||
|
$timestamp += $duration;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
fclose( $file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function consolidate( $target, $segments ) {
|
||||||
|
$out = [];
|
||||||
|
if ( isset( $segments['init'] ) ) {
|
||||||
|
$out['init'] = $segments['init'];
|
||||||
|
}
|
||||||
|
|
||||||
$n = count( $segments ) - 1;
|
$n = count( $segments ) - 1;
|
||||||
$start = $segments[0]['start'];
|
$start = $segments[0]['start'];
|
||||||
$size = $segments[0]['size'];
|
$size = $segments[0]['size'];
|
||||||
|
@ -492,8 +685,17 @@ function playlist( $filename, $segments ) {
|
||||||
$argv = $_SERVER['argv'];
|
$argv = $_SERVER['argv'];
|
||||||
$self = array_shift( $argv );
|
$self = array_shift( $argv );
|
||||||
$filename = array_shift( $argv );
|
$filename = array_shift( $argv );
|
||||||
$segments = extractFragmentedMP4( $filename );
|
$target = 10;
|
||||||
$segments = consolidate( 10, $segments );
|
|
||||||
|
$ext = substr( $filename, strrpos( $filename, '.' ) );
|
||||||
|
if ( $ext === '.mp3' ) {
|
||||||
|
$segments = extractMP3( $filename );
|
||||||
|
} elseif ( $ext === '.mp4' ) {
|
||||||
|
$segments = extractFragmentedMP4( $filename );
|
||||||
|
} else {
|
||||||
|
die( "Unexpected file extension $ext\n" );
|
||||||
|
}
|
||||||
|
$segments = consolidate( $target, $segments );
|
||||||
|
|
||||||
/*
|
/*
|
||||||
foreach ( $segments as $key => $segment ) {
|
foreach ( $segments as $key => $segment ) {
|
||||||
|
|
Loading…
Reference in a new issue