git-svn-id: http://php-reader.googlecode.com/svn/branches/zend@172 51a70ab9-7547-0410-9469-37e369ee0574
426 lines
14 KiB
PHP
426 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Zend Framework
|
|
*
|
|
* LICENSE
|
|
*
|
|
* This source file is subject to the new BSD license that is bundled
|
|
* with this package in the file LICENSE.txt.
|
|
* It is also available through the world-wide-web at this URL:
|
|
* http://framework.zend.com/license/new-bsd
|
|
* If you did not receive a copy of the license and are unable to
|
|
* obtain it through the world-wide-web, please send an email
|
|
* to license@zend.com so we can send you a copy immediately.
|
|
*
|
|
* @category Zend
|
|
* @package Zend_Media
|
|
* @subpackage MPEG
|
|
* @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
* @version $Id$
|
|
*/
|
|
|
|
/**#@+ @ignore */
|
|
require_once 'Zend/Media/Mpeg/Abs/Object.php';
|
|
require_once 'Zend/Media/Mpeg/Abs/Frame.php';
|
|
/**#@-*/
|
|
|
|
/**
|
|
* This class represents an MPEG Audio Bit Stream as described in
|
|
* ISO/IEC 11172-3 and ISO/IEC 13818-3 standards.
|
|
*
|
|
* Non-standard VBR header extensions or namely XING, VBRI and LAME headers are
|
|
* supported.
|
|
*
|
|
* This class is optimized for fast determination of the play duration of the
|
|
* file and hence uses lazy data reading mode by default. In this mode the
|
|
* actual frames and frame data are only read when referenced directly. You may
|
|
* change this behaviour by giving an appropriate option to the constructor.
|
|
*
|
|
* @category Zend
|
|
* @package Zend_Media
|
|
* @subpackage MPEG
|
|
* @author Ryan Butterfield <buttza@gmail.com>
|
|
* @author Sven Vollbehr <sven@vollbehr.eu>
|
|
* @copyright Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
|
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
|
* @version $Id$
|
|
* @todo Implement validation routines
|
|
*/
|
|
final class Zend_Media_Mpeg_Abs extends Zend_Media_Mpeg_Abs_Object
|
|
{
|
|
/** @var integer */
|
|
private $_bytes;
|
|
|
|
/** @var Array */
|
|
private $_frames = array();
|
|
|
|
/** @var Zend_Media_Mpeg_Abs_XingHeader */
|
|
private $_xingHeader = null;
|
|
|
|
/** @var Zend_Media_Mpeg_Abs_LameHeader */
|
|
private $_lameHeader = null;
|
|
|
|
/** @var Zend_Media_Mpeg_Abs_VbriHeader */
|
|
private $_vbriHeader = null;
|
|
|
|
/** @var integer */
|
|
private $_cumulativeBitrate = 0;
|
|
|
|
/** @var integer */
|
|
private $_cumulativePlayDuration = 0;
|
|
|
|
/** @var integer */
|
|
private $_estimatedBitrate = 0;
|
|
|
|
/** @var integer */
|
|
private $_estimatedPlayDuration = 0;
|
|
|
|
/** @var integer */
|
|
private $_lastFrameOffset = false;
|
|
|
|
/**
|
|
* Constructs the Zend_Media_Mpeg_ABS class with given file and options.
|
|
*
|
|
* The following options are currently recognized:
|
|
* o readmode -- Can be one of full or lazy and determines when the read
|
|
* of frames and their data happens. When in full mode the data is read
|
|
* automatically during the instantiation of the frame and all the
|
|
* frames are read during the instantiation of this class. While this
|
|
* allows faster validation and data fetching, it is unnecessary in
|
|
* terms of determining just the play duration of the file. Defaults to
|
|
* lazy.
|
|
*
|
|
* o estimatePrecision -- Only applicaple with lazy read mode to determine
|
|
* the precision of play duration estimate. This precision is equal to
|
|
* how many frames are read before fixing the average bitrate that is
|
|
* used to calculate the play duration estimate of the whole file. Each
|
|
* frame adds about 0.1-0.2ms to the processing of the file. Defaults to
|
|
* 1000.
|
|
*
|
|
* When in lazy data reading mode it is first checked whether a VBR header
|
|
* is found in a file. If so, the play duration is calculated based no its
|
|
* data and no further frames are read from the file. If no VBR header is
|
|
* found, frames up to estimatePrecision are read to calculate an average
|
|
* bitrate.
|
|
*
|
|
* Hence, only zero or <var>estimatePrecision</var> number of frames are
|
|
* read in lazy data reading mode. The rest of the frames are read
|
|
* automatically when directly referenced, ie the data is read when it is
|
|
* needed.
|
|
*
|
|
* @param string|resource|Zend_Io_Reader $filename The path to the file,
|
|
* file descriptor of an opened file, or a {@link Zend_Io_Reader} instance.
|
|
* @param Array $options The options array.
|
|
*/
|
|
public function __construct($filename, $options = array())
|
|
{
|
|
if ($filename instanceof Zend_Io_Reader) {
|
|
$this->_reader = &$filename;
|
|
} else {
|
|
require_once 'Zend/Io/FileReader.php';
|
|
try {
|
|
$this->_reader = new Zend_Io_FileReader($filename);
|
|
} catch (Zend_Io_Exception $e) {
|
|
$this->_reader = null;
|
|
require_once 'Zend/Media/Mpeg/Exception.php';
|
|
throw new Zend_Media_Mpeg_Exception($e->getMessage());
|
|
}
|
|
}
|
|
$this->setOptions($options);
|
|
|
|
$offset = $this->_reader->getOffset();
|
|
$this->_bytes = $this->_reader->getSize();
|
|
|
|
/* Skip ID3v1 tag */
|
|
$this->_reader->setOffset(-128);
|
|
if ($this->_reader->read(3) == 'TAG') {
|
|
$this->_bytes -= 128;
|
|
}
|
|
$this->_reader->setOffset($offset);
|
|
|
|
/* Skip ID3v2 tag */
|
|
if ($this->_reader->readString8(3) == 'ID3') {
|
|
require_once 'Zend/Media/Id3/Header.php';
|
|
$header = new Zend_Media_Id3_Header($this->_reader);
|
|
$this->_reader->skip
|
|
($header->getSize() +
|
|
($header->hasFlag(Zend_Media_Id3_Header::FOOTER) ? 10 : 0));
|
|
} else {
|
|
$this->_reader->setOffset($offset);
|
|
}
|
|
|
|
/* Check for VBR headers */
|
|
$offset = $this->_reader->getOffset();
|
|
|
|
$this->_frames[] = $firstFrame =
|
|
new Zend_Media_Mpeg_Abs_Frame($this->_reader, $options);
|
|
|
|
$postoffset = $this->_reader->getOffset();
|
|
|
|
$this->_reader->setOffset
|
|
($offset + 4 + self::$sidesizes
|
|
[$firstFrame->getFrequencyType()][$firstFrame->getMode()]);
|
|
if (($xing = $this->_reader->readString8(4)) == 'Xing' ||
|
|
$xing == 'Info') {
|
|
require_once 'Zend/Media/Mpeg/Abs/XingHeader.php';
|
|
$this->_xingHeader =
|
|
new Zend_Media_Mpeg_Abs_XingHeader($this->_reader, $options);
|
|
if ($this->_reader->readString8(4) == 'LAME') {
|
|
require_once 'Zend/Media/Mpeg/Abs/LameHeader.php';
|
|
$this->_lameHeader =
|
|
new Zend_Media_Mpeg_Abs_LameHeader
|
|
($this->_reader, $options);
|
|
}
|
|
|
|
// A header frame is not counted as an audio frame
|
|
array_pop($this->_frames);
|
|
}
|
|
|
|
$this->_reader->setOffset($offset + 4 + 32);
|
|
if ($this->_reader->readString8(4) == 'VBRI') {
|
|
require_once 'Zend/Media/Mpeg/Abs/VbriHeader.php';
|
|
$this->_vbriHeader =
|
|
new Zend_Media_Mpeg_Abs_VbriHeader($this->_reader, $options);
|
|
|
|
// A header frame is not counted as an audio frame
|
|
array_pop($this->_frames);
|
|
}
|
|
|
|
$this->_reader->setOffset($postoffset);
|
|
|
|
// Ensure we always have read at least one frame
|
|
if (empty($this->_frames)) {
|
|
$this->_readFrames(1);
|
|
}
|
|
|
|
/* Read necessary frames */
|
|
if ($this->getOption('readmode', 'lazy') == 'lazy') {
|
|
if (($header = $this->_xingHeader) !== null ||
|
|
($header = $this->_vbriHeader) !== null) {
|
|
$this->_estimatedPlayDuration = $header->getFrames() *
|
|
$firstFrame->getSamples() /
|
|
$firstFrame->getSamplingFrequency();
|
|
if ($this->_lameHeader !== null) {
|
|
$this->_estimatedBitrate = $this->_lameHeader->getBitrate();
|
|
if ($this->_estimatedBitrate == 255) {
|
|
$this->_estimatedBitrate = round
|
|
(($this->_lameHeader->getMusicLength()) /
|
|
(($header->getFrames() *
|
|
$firstFrame->getSamples()) /
|
|
$firstFrame->getSamplingFrequency()) / 1000 * 8);
|
|
}
|
|
} else {
|
|
$this->_estimatedBitrate = ($this->_bytes - $offset) /
|
|
$this->_estimatedPlayDuration / 1000 * 8;
|
|
}
|
|
} else {
|
|
$this->_readFrames($this->getOption('estimatePrecision', 1000));
|
|
|
|
$this->_estimatedBitrate =
|
|
$this->_cumulativeBitrate / count($this->_frames);
|
|
$this->_estimatedPlayDuration =
|
|
($this->_bytes - $offset) /
|
|
($this->_estimatedBitrate * 1000 / 8);
|
|
}
|
|
} else {
|
|
$this->_readFrames();
|
|
|
|
$this->_estimatedBitrate =
|
|
$this->_cumulativeBitrate / count($this->_frames);
|
|
$this->_estimatedPlayDuration = $this->_cumulativePlayDuration;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns <var>true</var> if the audio bitstream contains the Xing VBR
|
|
* header, or <var>false</var> otherwise.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasXingHeader()
|
|
{
|
|
return $this->_xingHeader === null;
|
|
}
|
|
|
|
/**
|
|
* Returns the Xing VBR header, or <var>null</var> if not found in the audio
|
|
* bitstream.
|
|
*
|
|
* @return Zend_Media_Mpeg_Abs_XingHeader
|
|
*/
|
|
public function getXingHeader()
|
|
{
|
|
return $this->_xingHeader;
|
|
}
|
|
|
|
/**
|
|
* Returns <var>true</var> if the audio bitstream contains the LAME VBR
|
|
* header, or <var>false</var> otherwise.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasLameHeader()
|
|
{
|
|
return $this->_lameHeader === null;
|
|
}
|
|
|
|
/**
|
|
* Returns the LAME VBR header, or <var>null</var> if not found in the audio
|
|
* bitstream.
|
|
*
|
|
* @return Zend_Media_Mpeg_Abs_LameHeader
|
|
*/
|
|
public function getLameHeader()
|
|
{
|
|
return $this->_lameHeader;
|
|
}
|
|
|
|
/**
|
|
* Returns <var>true</var> if the audio bitstream contains the Fraunhofer IIS
|
|
* VBR header, or <var>false</var> otherwise.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function hasVbriHeader()
|
|
{
|
|
return $this->_vbriHeader === null;
|
|
}
|
|
|
|
/**
|
|
* Returns the Fraunhofer IIS VBR header, or <var>null</var> if not found in
|
|
* the audio bitstream.
|
|
*
|
|
* @return Zend_Media_Mpeg_Abs_VbriHeader
|
|
*/
|
|
public function getVbriHeader()
|
|
{
|
|
return $this->_vbriHeader;
|
|
}
|
|
|
|
/**
|
|
* Returns the bitrate estimate. This value is either fetched from one of the
|
|
* headers or calculated based on the read frames.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getBitrateEstimate()
|
|
{
|
|
return $this->_estimatedBitrate;
|
|
}
|
|
|
|
/**
|
|
* For variable bitrate files this method returns the exact average bitrate of
|
|
* the whole file.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getBitrate()
|
|
{
|
|
if ($this->getOption('readmode', 'lazy') == 'lazy') {
|
|
$this->_readFrames();
|
|
}
|
|
return $this->_cumulativeBitrate / count($this->_frames);
|
|
}
|
|
|
|
/**
|
|
* Returns the playtime estimate, in seconds.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getLengthEstimate()
|
|
{
|
|
return $this->_estimatedPlayDuration;
|
|
}
|
|
|
|
/**
|
|
* Returns the exact playtime in seconds. In lazy reading mode the frames
|
|
* are read from the file the first time you call this method to get the
|
|
* exact playtime of the file.
|
|
*
|
|
* @return integer
|
|
*/
|
|
public function getLength()
|
|
{
|
|
if ($this->getOption('readmode', 'lazy') == 'lazy') {
|
|
$this->_readFrames();
|
|
}
|
|
return $this->_cumulativePlayDuration;
|
|
}
|
|
|
|
/**
|
|
* Returns the playtime estimate as a string in the form of
|
|
* [hours:]minutes:seconds.milliseconds.
|
|
*
|
|
* @param integer $seconds The playtime in seconds.
|
|
* @return string
|
|
*/
|
|
public function getFormattedLengthEstimate()
|
|
{
|
|
return $this->formatTime($this->getLengthEstimate());
|
|
}
|
|
|
|
/**
|
|
* Returns the exact playtime given in seconds as a string in the form of
|
|
* [hours:]minutes:seconds.milliseconds. In lazy reading mode the frames are
|
|
* read from the file the first time you call this method to get the exact
|
|
* playtime of the file.
|
|
*
|
|
* @param integer $seconds The playtime in seconds.
|
|
* @return string
|
|
*/
|
|
public function getFormattedLength()
|
|
{
|
|
return $this->formatTime($this->getLength());
|
|
}
|
|
|
|
/**
|
|
* Returns all the frames of the audio bitstream as an array. In lazy
|
|
* reading mode the frames are read from the file the first time you call
|
|
* this method.
|
|
*
|
|
* @return Array
|
|
*/
|
|
public function getFrames()
|
|
{
|
|
if ($this->getOption('readmode', 'lazy') == 'lazy') {
|
|
$this->_readFrames();
|
|
}
|
|
return $this->_frames;
|
|
}
|
|
|
|
/**
|
|
* Reads frames up to given limit. If called subsequently the method
|
|
* continues after the last frame read in the last call, again to read up
|
|
* to the limit or just the rest of the frames.
|
|
*
|
|
* @param integer $limit The maximum number of frames read from the
|
|
* bitstream
|
|
*/
|
|
private function _readFrames($limit = null)
|
|
{
|
|
if ($this->_lastFrameOffset !== false) {
|
|
$this->_reader->setOffset($this->_lastFrameOffset);
|
|
}
|
|
|
|
for ($i = 0; $this->_reader->getOffset() < $this->_bytes; $i++) {
|
|
$options = $this->getOptions();
|
|
$frame = new Zend_Media_Mpeg_Abs_Frame($this->_reader, $options);
|
|
|
|
$this->_cumulativePlayDuration +=
|
|
(double)($frame->getLength() /
|
|
($frame->getBitrate() * 1000 / 8));
|
|
$this->_cumulativeBitrate += $frame->getBitrate();
|
|
$this->_frames[] = $frame;
|
|
|
|
if ($limit === null) {
|
|
$this->_lastFrameOffset = $this->_reader->getOffset();
|
|
}
|
|
if ($limit !== null && ($i + 1) == $limit) {
|
|
$this->_lastFrameOffset = $this->_reader->getOffset();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|