diff --git a/src/Zend/Media/Ogg/Exception.php b/src/Zend/Media/Ogg/Exception.php new file mode 100644 index 0000000..eea7607 --- /dev/null +++ b/src/Zend/Media/Ogg/Exception.php @@ -0,0 +1,39 @@ + + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +class Zend_Media_Ogg_Exception extends Zend_Media_Exception +{} diff --git a/src/Zend/Media/Ogg/Page.php b/src/Zend/Media/Ogg/Page.php new file mode 100644 index 0000000..cdde6bc --- /dev/null +++ b/src/Zend/Media/Ogg/Page.php @@ -0,0 +1,235 @@ + + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +final class Zend_Media_Ogg_Page +{ + /** + * The reader object. + * + * @var Zend_Io_Reader + */ + private $_reader; + + /** @var string */ + private $_capturePattern; + + /** @var integer */ + private $_streamStructureVersion; + + /** @var integer */ + private $_headerTypeFlag; + + /** @var integer */ + private $_granulePosition; + + /** @var integer */ + private $_bitstreamSerialNumber; + + /** @var integer */ + private $_pageSequenceNumber; + + /** @var integer */ + private $_crcChecksum; + + /** @var integer */ + private $_numberPageSegments; + + /** @var Array */ + private $_segmentTable = array(); + + /** @var integer */ + private $_size; + + /** @var integer */ + private $_headerSize; + + /** @var integer */ + private $_pageSize; + + /** + * Constructs the class with given parameters and reads object related data + * from the Ogg bitstream. + * + * @param Zend_Io_Reader $reader The reader object. + */ + public function __construct($reader) + { + $this->_reader = $reader; + + $this->_capturePattern = $this->_reader->read(4); + if ($this->_capturePattern != 'OggS') { + require_once 'Zend/Media/Ogg/Exception.php'; + throw new Zend_Media_Ogg_Exception('Not a valid Ogg bitstream'); + } + $this->_streamStructureVersion = $this->_reader->readUInt8(); + if ($this->_streamStructureVersion != 0) { + require_once 'Zend/Media/Ogg/Exception.php'; + throw new Zend_Media_Ogg_Exception('Unsupported Ogg stream structure version'); + } + $this->_headerTypeFlag = $this->_reader->readUInt8(); + $this->_granulePosition = $this->_reader->readInt64LE(); + $this->_bitstreamSerialNumber = $this->_reader->readUInt32LE(); + $this->_pageSequenceNumber = $this->_reader->readUInt32LE(); + $this->_crcChecksum = $this->_reader->readUInt32LE(); + $this->_numberPageSegments = $this->_reader->readUInt8(); + $this->_segmentTable = array(); + for ($i = 0; $i < $this->_numberPageSegments; $i++) { + $this->_segmentTable[] = $this->_reader->readUInt8(); + } + $this->_headerSize = $this->_numberPageSegments + 27; + $this->_pageSize = array_sum($this->_segmentTable); + $this->_size = $this->_headerSize + $this->_pageSize; + } + + /** + * Returns this page's context identifier in the bitstream. + * + * @return integer + */ + public final function getHeaderTypeFlag() + { + return $this->_headerTypeFlag; + } + + /** + * Returns total samples encoded after including all packets finished on this page (packets begun on this page but + * continuing on to the next page do not count). + * + * The rationale here is that the position specified in the frame header of the last page tells how long the data + * coded by the bitstream is. A truncated stream will still return the proper number of samples that can be decoded + * fully. + * + * A special value of '-1' (in two's complement) indicates that no packets finish on this page. + * + * @return integer + */ + public final function getGranulePosition() + { + return $this->_granulePosition; + } + + /** + * Returns the logical bitstream serial number. + * + * Ogg allows for separate logical bitstreams to be mixed at page granularity in a physical bitstream. The most + * common case would be sequential arrangement, but it is possible to interleave pages for two separate bitstreams + * to be decoded concurrently. The serial number is the means by which pages physical pages are associated with a + * particular logical stream. Each logical stream must have a unique serial number within a physical stream. + * + * @return integer + */ + public final function getBitstreamSerialNumber() + { + return $this->_bitstreamSerialNumber; + } + + /** + * Returns the page counter; lets us know if a page is lost (useful where packets span page boundaries). + * + * @return integer + */ + public final function getPageSequenceNumber() + { + return $this->_pageSequenceNumber; + } + + /** + * Returns the 32 bit CRC value (direct algorithm, initial val and final XOR = 0, generator polynomial=0x04c11db7). + * The value is computed over the entire header (with the CRC field in the header set to zero) and then continued + * over the page. The CRC field is then filled with the computed value. + * + * @return integer + */ + public final function getCrcChecksum() + { + return $this->_crcChecksum; + } + + /** + * Returns the number of segment entries to appear in the segment table. The maximum number of 255 segments (255 + * bytes each) sets the maximum possible physical page size at 65307 bytes or just under 64kB (thus we know that a + * header corrupted so as destroy sizing/alignment information will not cause a runaway bitstream. We'll read in the + * page according to the corrupted size information that's guaranteed to be a reasonable size regardless, notice the + * checksum mismatch, drop sync and then look for recapture). + * + * @return integer + */ + public final function getNumberPageSegments() + { + return $this->_numberPageSegments; + } + + /** + * Returns the lacing values for each packet segment physically appearing in this page are listed in contiguous + * order. + * + * @return integer + */ + public final function getSegmentTable() + { + return $this->_segmentTable; + } + + /** + * Returns the total page size with the header in bytes. + * + * @return integer + */ + public final function getSize() + { + return $this->_size; + } + + /** + * Returns the total header size in bytes. + * + * @return integer + */ + public final function getHeaderSize() + { + return $this->_headerSize; + } + + /** + * Returns the total page size without the header in bytes. The page size is calculated directly from the known + * lacing values in the segment table. + * + * @return integer + */ + public final function getPageSize() + { + return $this->_pageSize; + } +} diff --git a/src/Zend/Media/Ogg/Reader.php b/src/Zend/Media/Ogg/Reader.php new file mode 100644 index 0000000..e1bf5ed --- /dev/null +++ b/src/Zend/Media/Ogg/Reader.php @@ -0,0 +1,169 @@ + + * @copyright Copyright (c) 2005-2011 Zend Technologies USA Inc. (http://www.zend.com) + * @license http://framework.zend.com/license/new-bsd New BSD License + * @version $Id$ + */ +final class Zend_Media_Ogg_Reader extends Zend_Io_Reader +{ + /** @var Array */ + private $_pages = array(); + + /** @var integer */ + private $_currentPage = 0; + + /** @var integer */ + private $_currentPagePosition = 0; + + /** @var integer */ + private $_streamSize = 0; + + /** + * Constructs the Ogg class with given file. + * + * @param string $filename The path to the file. + * @throws Zend_Io_Exception if an error occur in stream handling. + * @throws Zend_Media_Ogg_Exception if an error occurs in Ogg bitstream reading. + */ + public function __construct($filename) + { + $reader = new Zend_Io_FileReader($filename); + $fileSize = $reader->getSize(); + while ($reader->getOffset() < $fileSize) { + $this->_pages[] = array( + 'page' => $page = new Zend_Media_Ogg_Page($reader), + 'offset' => $reader->getOffset() + ); + $this->_streamSize += $page->getPageSize(); + $reader->skip($page->getPageSize()); + } + $reader->setOffset($this->_pages[$this->_currentPage]['offset']); + $this->_fd = $reader->getFileDescriptor(); + } + + /** + * Overwrite the method to return the current point of operation within the Ogg bitstream. + * + * @return integer + * @throws Zend_Io_Exception if an I/O error occurs + */ + public function getOffset() + { + $offset = 0; + for ($i = 0; $i < $this->_currentPage; $i++) { + $offset += $this->_pages[$i]['page']->getPageSize(); + } + return $offset += $this->_currentPagePosition; + } + + /** + * Overwrite the method to set the point of operation within the Ogg bitstream. + * + * @param integer $offset The new point of operation. + * @return void + * @throws Zend_Io_Exception if an I/O error occurs + */ + public function setOffset($offset) + { + $streamSize = 0; + for ($i = 0, $pageCount = count($this->_pages); $i < $pageCount; $i++) { + if (($streamSize + $this->_pages[$i]['page']->getPageSize()) >= $offset) { + $this->_currentPage = $i; + $this->_currentPagePosition = $offset - $streamSize; + parent::setOffset($this->_pages[$i]['offset'] + $this->_currentPagePosition); + break; + } + $streamSize += $this->_pages[$i]['page']->getPageSize(); + } + } + + /** + * Overwrite the method to return the Ogg bitstream size in bytes. + * + * @return integer + */ + public function getSize() + { + echo "getSize\n"; + return $this->_streamSize; + } + + /** + * Overwrite the method to jump size amount of bytes in the Ogg bitstream. + * + * @param integer $size The amount of bytes to jump within the Ogg bitstream. + * @return void + * @throws Zend_Io_Exception if an I/O error occurs + */ + public function skip($size) + { + $currentPageSize = $this->_pages[$this->_currentPage]['page']->getPageSize(); + if (($this->_currentPagePosition + $size) >= $currentPageSize) { + parent::skip + (($currentPageSize - $this->_currentPagePosition) + + $this->_pages[++$this->_currentPage]['page']->getHeaderSize() + + ($this->_currentPagePosition = ($size - $currentPageSize - $this->_currentPagePosition))); + } else { + $this->_currentPagePosition += $size; + parent::skip($size); + } + } + + /** + * Overwrite the method to read bytes within the Ogg bitstream. + * + * @param integer $length The amount of bytes to read within the Ogg bitstream. + * @return string + * @throws Zend_Io_Exception if an I/O error occurs + */ + public function read($length) + { + $currentPageSize = $this->_pages[$this->_currentPage]['page']->getPageSize(); + if (($this->_currentPagePosition + $length) >= $currentPageSize) { + $buffer = parent::read($currentPageSize - $this->_currentPagePosition); + parent::skip($this->_pages[++$this->_currentPage]['page']->getHeaderSize()); + return $buffer . parent::read + ($this->_currentPagePosition = ($length - ($currentPageSize - $this->_currentPagePosition))); + } else { + $buffer = parent::read($length); + $this->_currentPagePosition += $length; + return $buffer; + } + } +}