Файл: core/PEAR/Ogg/Theora.php
Строк: 164
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
// +----------------------------------------------------------------------------+
// | File_Ogg PEAR Package for Accessing Ogg Bitstreams                         |
// | Copyright (c) 2005-2007                                                    |
// | David Grant <david@grant.org.uk>                                           |
// | Tim Starling <tstarling@wikimedia.org>                                     |
// +----------------------------------------------------------------------------+
// | This library is free software; you can redistribute it and/or              |
// | modify it under the terms of the GNU Lesser General Public                 |
// | License as published by the Free Software Foundation; either               |
// | version 2.1 of the License, or (at your option) any later version.         |
// |                                                                            |
// | This library is distributed in the hope that it will be useful,            |
// | but WITHOUT ANY WARRANTY; without even the implied warranty of             |
// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU          |
// | Lesser General Public License for more details.                            |
// |                                                                            |
// | You should have received a copy of the GNU Lesser General Public           |
// | License along with this library; if not, write to the Free Software        |
// | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA |
// +----------------------------------------------------------------------------+
require_once('Bitstream.php');
define( 'OGG_THEORA_IDENTIFICATION_HEADER', 0x80 );
define( 'OGG_THEORA_COMMENTS_HEADER', 0x81 );
define( 'OGG_THEORA_IDENTIFICATION_PAGE_OFFSET', 0 );
define( 'OGG_THEORA_COMMENTS_PAGE_OFFSET', 1 );
/**
 * @author      David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @category    File
 * @copyright   David Grant <david@grant.org.uk>, Tim Starling <tstarling@wikimedia.org>
 * @license     http://www.gnu.org/copyleft/lesser.html GNU LGPL
 * @link        http://pear.php.net/package/File_Ogg
 * @link        http://www.xiph.org/theora/
 * @package     File_Ogg
 * @version     CVS: $Id: Theora.php,v 1.10 2008/01/31 04:41:44 tstarling Exp $
 */
class File_Ogg_Theora extends File_Ogg_Media
{
    /**
     * @access  private
     */
    function File_Ogg_Theora($streamSerial, $streamData, $filePointer)
    {
        File_Ogg_Media::File_Ogg_Media($streamSerial, $streamData, $filePointer);
        $this->_decodeIdentificationHeader();
        $this->_decodeCommentsHeader();
        // Calculate length
        // First make some "numeric strings"
        // These might not fit into PHP's integer type, but they will fit into 
        // the 53-bit mantissa of a double-precision number
        $topWord = floatval( '0x' . substr( $this->_lastGranulePos, 0, 8 ) );
        $bottomWord = floatval( '0x' . substr( $this->_lastGranulePos, 8, 8 ) );
        // Calculate the keyframe position by shifting right by KFGSHIFT
        // We don't use PHP's shift operators because they're terribly broken
        // This is made slightly simpler by the fact that KFGSHIFT < 32
        $keyFramePos = $topWord / pow(2, $this->_kfgShift - 32) + 
            floor( $bottomWord / pow(2, $this->_kfgShift) );
        // Calculate the frame offset by masking off the top 64-KFGSHIFT bits
        // This requires a bit of floating point trickery
        $offset = fmod( $bottomWord, pow(2, $this->_kfgShift) );
        // They didn't teach you that one at school did they?
        // Now put it together with the frame rate to calculate length in seconds
        $this->_streamLength = ( $keyFramePos + $offset ) / $this->_frameRate;
        $this->_avgBitrate = $this->_streamLength ? ($this->_streamSize * 8) / $this->_streamLength : 0;
    }
    /**
     * Get the 6-byte identification string expected in the common header
     */
    function getIdentificationString()
    {
        return OGG_STREAM_CAPTURE_THEORA;
    }
    /**
     * Parse the identification header in a Theora stream.
     * @access  private
     */
    function _decodeIdentificationHeader()
    {
        $this->_decodeCommonHeader(OGG_THEORA_IDENTIFICATION_HEADER, OGG_THEORA_IDENTIFICATION_PAGE_OFFSET);
        $h = File_Ogg::_readBigEndian( $this->_filePointer, array(
            'VMAJ' => 8,
            'VMIN' => 8,
            'VREV' => 8,
            'FMBW' => 16,
            'FMBH' => 16,
            'PICW' => 24,
            'PICH' => 24,
            'PICX' => 8,
            'PICY' => 8,
            'FRN'  => 32,
            'FRD'  => 32,
            'PARN' => 24,
            'PARD' => 24,
            'CS'   => 8,
            'NOMBR' => 24,
            'QUAL' => 6,
            'KFGSHIFT' => 5,
            'PF' => 2));
        if ( !$h ) {
            throw new PEAR_Exception("Stream is undecodable due to a truncated header.", OGG_ERROR_UNDECODABLE);
        }
        // Theora version
        // Seems overly strict but this is what the spec says
        // VREV is for backwards-compatible changes, apparently
        if ( $h['VMAJ'] != 3 || $h['VMIN'] != 2 ) {
            throw new PEAR_Exception("Stream is undecodable due to an invalid theora version.", OGG_ERROR_UNDECODABLE);
        }
        $this->_theoraVersion = "{$h['VMAJ']}.{$h['VMIN']}.{$h['VREV']}";
        // Frame height/width
        if ( !$h['FMBW'] || !$h['FMBH'] ) {
            throw new PEAR_Exception("Stream is undecodable because it has frame size of zero.", OGG_ERROR_UNDECODABLE);
        }
        $this->_frameWidth = $h['FMBW'] * 16;
        $this->_frameHeight = $h['FMBH'] * 16;
        
        // Picture height/width
        if ( $h['PICW'] > $this->_frameWidth || $h['PICH'] > $this->_frameHeight ) {
            throw new PEAR_Exception("Stream is undecodable because the picture width is greater than the frame width.", OGG_ERROR_UNDECODABLE);
        }
        $this->_pictureWidth = $h['PICW'];
        $this->_pictureHeight = $h['PICH'];
        // Picture offset
        $this->_offsetX = $h['PICX'];
        $this->_offsetY = $h['PICY'];
        // Frame rate
        $this->_frameRate = $h['FRD'] == 0 ? 0 : $h['FRN'] / $h['FRD'];
        // Physical aspect ratio
        if ( !$h['PARN'] || !$h['PARD'] ) {
            $this->_physicalAspectRatio = 1;
        } else {
            $this->_physicalAspectRatio = $h['PARN'] / $h['PARD'];
        }
        // Color space
        $colorSpaces = array(
            0 => 'Undefined',
            1 => 'Rec. 470M',
            2 => 'Rec. 470BG',
        );
        if ( isset( $colorSpaces[$h['CS']] ) ) {
            $this->_colorSpace = $colorSpaces[$h['CS']];
        } else {
            $this->_colorSpace = 'Unknown (reserved)';
        }
        $this->_nomBitrate = $h['NOMBR'];
        $this->_quality = $h['QUAL'];
        $this->_kfgShift = $h['KFGSHIFT'];
        
        $pixelFormats = array(
            0 => '4:2:0',
            1 => 'Unknown (reserved)',
            2 => '4:2:2',
            3 => '4:4:4',
        );
        $this->_pixelFormat = $pixelFormats[$h['PF']];
        switch ( $h['PF'] ) {
            case 0: 
                $h['NSBS'] = 
                    floor( ($h['FMBW'] + 1) / 2 ) * 
                    floor( ($h['FMBH'] + 1) / 2 ) + 2 * 
                    floor( ($h['FMBW'] + 3) / 4 ) * 
                    floor( ($h['FMBH'] + 3) / 4 );
                $h['NBS'] = 6 * $h['FMBW'] * $h['FMBH'];
                break;
            case 2:
                $h['NSBS'] = 
                    floor( ($h['FMBW'] + 1) / 2 ) * 
                    floor( ($h['FMBH'] + 1) / 2 ) + 2 * 
                    floor( ($h['FMBW'] + 3) / 4 ) * 
                    floor( ($h['FMBH'] + 1) / 2 );
                $h['NBS'] = 8 * $h['FMBW'] * $h['FMBH'];
                break;
            case 3:
                $h['NSBS'] = 
                    3 * floor( ($h['FMBW'] + 1) / 2 ) * 
                        floor( ($h['FMBH'] + 1) / 2 );
                $h['NBS'] = 12 * $h['FMBW'] * $h['FMBH'];
                break;
            default:
                $h['NSBS'] = $h['NBS'] = 0;
        }
        $h['NMBS'] = $h['FMBW'] * $h['FMBH'];
        $this->_idHeader = $h;
    }
    /**
     * Get an associative array containing header information about the stream
     * @access  public
     * @return  array
     */
    function getHeader() {
        return $this->_idHeader;
    }
    /**
     * Get a short string describing the type of the stream
     * @return string
     */
    function getType() {
        return 'Theora';
    }
    /**
     * Decode the comments header
     * @access private
     */
    function _decodeCommentsHeader()
    {
        $this->_decodeCommonHeader(OGG_THEORA_COMMENTS_HEADER, OGG_THEORA_COMMENTS_PAGE_OFFSET);
        $this->_decodeBareCommentsHeader();
    }
}
?>