File indexing completed on 2024-05-12 05:58:11

0001 <?php
0002 /////////////////////////////////////////////////////////////////
0003 /// getID3() by James Heinrich <info@getid3.org>               //
0004 //  available at http://getid3.sourceforge.net                 //
0005 //            or http://www.getid3.org                         //
0006 //          also https://github.com/JamesHeinrich/getID3       //
0007 //                                                             //
0008 //  FLV module by Seth Kaufman <sethØwhirl-i-gig*com>          //
0009 //                                                             //
0010 //  * version 0.1 (26 June 2005)                               //
0011 //                                                             //
0012 //                                                             //
0013 //  * version 0.1.1 (15 July 2005)                             //
0014 //  minor modifications by James Heinrich <info@getid3.org>    //
0015 //                                                             //
0016 //  * version 0.2 (22 February 2006)                           //
0017 //  Support for On2 VP6 codec and meta information             //
0018 //    by Steve Webster <steve.websterØfeaturecreep*com>        //
0019 //                                                             //
0020 //  * version 0.3 (15 June 2006)                               //
0021 //  Modified to not read entire file into memory               //
0022 //    by James Heinrich <info@getid3.org>                      //
0023 //                                                             //
0024 //  * version 0.4 (07 December 2007)                           //
0025 //  Bugfixes for incorrectly parsed FLV dimensions             //
0026 //    and incorrect parsing of onMetaTag                       //
0027 //    by Evgeny Moysevich <moysevichØgmail*com>                //
0028 //                                                             //
0029 //  * version 0.5 (21 May 2009)                                //
0030 //  Fixed parsing of audio tags and added additional codec     //
0031 //    details. The duration is now read from onMetaTag (if     //
0032 //    exists), rather than parsing whole file                  //
0033 //    by Nigel Barnes <ngbarnesØhotmail*com>                   //
0034 //                                                             //
0035 //  * version 0.6 (24 May 2009)                                //
0036 //  Better parsing of files with h264 video                    //
0037 //    by Evgeny Moysevich <moysevichØgmail*com>                //
0038 //                                                             //
0039 //  * version 0.6.1 (30 May 2011)                              //
0040 //    prevent infinite loops in expGolombUe()                  //
0041 //                                                             //
0042 //  * version 0.7.0 (16 Jul 2013)                              //
0043 //  handle GETID3_FLV_VIDEO_VP6FLV_ALPHA                       //
0044 //  improved AVCSequenceParameterSetReader::readData()         //
0045 //    by Xander Schouwerwou <schouwerwouØgmail*com>            //
0046 //                                                             //
0047 /////////////////////////////////////////////////////////////////
0048 //                                                             //
0049 // module.audio-video.flv.php                                  //
0050 // module for analyzing Shockwave Flash Video files            //
0051 // dependencies: NONE                                          //
0052 //                                                            ///
0053 /////////////////////////////////////////////////////////////////
0054 
0055 define('GETID3_FLV_TAG_AUDIO',          8);
0056 define('GETID3_FLV_TAG_VIDEO',          9);
0057 define('GETID3_FLV_TAG_META',          18);
0058 
0059 define('GETID3_FLV_VIDEO_H263',         2);
0060 define('GETID3_FLV_VIDEO_SCREEN',       3);
0061 define('GETID3_FLV_VIDEO_VP6FLV',       4);
0062 define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
0063 define('GETID3_FLV_VIDEO_SCREENV2',     6);
0064 define('GETID3_FLV_VIDEO_H264',         7);
0065 
0066 define('H264_AVC_SEQUENCE_HEADER',          0);
0067 define('H264_PROFILE_BASELINE',            66);
0068 define('H264_PROFILE_MAIN',                77);
0069 define('H264_PROFILE_EXTENDED',            88);
0070 define('H264_PROFILE_HIGH',               100);
0071 define('H264_PROFILE_HIGH10',             110);
0072 define('H264_PROFILE_HIGH422',            122);
0073 define('H264_PROFILE_HIGH444',            144);
0074 define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
0075 
0076 class getid3_flv extends getid3_handler {
0077 
0078   const magic = 'FLV';
0079 
0080   public $max_frames = 100000; // break out of the loop if too many frames have been scanned; only scan this many if meta frame does not contain useful duration
0081 
0082   public function Analyze() {
0083     $info = &$this->getid3->info;
0084 
0085     $this->fseek($info['avdataoffset']);
0086 
0087     $FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
0088     $FLVheader = $this->fread(5);
0089 
0090     $info['fileformat'] = 'flv';
0091     $info['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
0092     $info['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
0093     $TypeFlags                          = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
0094 
0095     if ($info['flv']['header']['signature'] != self::magic) {
0096       $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
0097       unset($info['flv'], $info['fileformat']);
0098       return false;
0099     }
0100 
0101     $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
0102     $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
0103 
0104     $FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
0105     $FLVheaderFrameLength = 9;
0106     if ($FrameSizeDataLength > $FLVheaderFrameLength) {
0107       $this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
0108     }
0109     $Duration = 0;
0110     $found_video = false;
0111     $found_audio = false;
0112     $found_meta  = false;
0113     $found_valid_meta_playtime = false;
0114     $tagParseCount = 0;
0115     $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
0116     $flv_framecount = &$info['flv']['framecount'];
0117     while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime))  {
0118       $ThisTagHeader = $this->fread(16);
0119 
0120       $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
0121       $TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
0122       $DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
0123       $Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
0124       $LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
0125       $NextOffset = $this->ftell() - 1 + $DataLength;
0126       if ($Timestamp > $Duration) {
0127         $Duration = $Timestamp;
0128       }
0129 
0130       $flv_framecount['total']++;
0131       switch ($TagType) {
0132         case GETID3_FLV_TAG_AUDIO:
0133           $flv_framecount['audio']++;
0134           if (!$found_audio) {
0135             $found_audio = true;
0136             $info['flv']['audio']['audioFormat']     = ($LastHeaderByte >> 4) & 0x0F;
0137             $info['flv']['audio']['audioRate']       = ($LastHeaderByte >> 2) & 0x03;
0138             $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
0139             $info['flv']['audio']['audioType']       =  $LastHeaderByte       & 0x01;
0140           }
0141           break;
0142 
0143         case GETID3_FLV_TAG_VIDEO:
0144           $flv_framecount['video']++;
0145           if (!$found_video) {
0146             $found_video = true;
0147             $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
0148 
0149             $FLVvideoHeader = $this->fread(11);
0150 
0151             if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
0152               // this code block contributed by: moysevichØgmail*com
0153 
0154               $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
0155               if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
0156                 //  read AVCDecoderConfigurationRecord
0157                 $configurationVersion       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  4, 1));
0158                 $AVCProfileIndication       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  5, 1));
0159                 $profile_compatibility      = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  6, 1));
0160                 $lengthSizeMinusOne         = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  7, 1));
0161                 $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  8, 1));
0162 
0163                 if (($numOfSequenceParameterSets & 0x1F) != 0) {
0164                   //  there is at least one SequenceParameterSet
0165                   //  read size of the first SequenceParameterSet
0166                   //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
0167                   $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
0168                   //  read the first SequenceParameterSet
0169                   $sps = $this->fread($spsSize);
0170                   if (strlen($sps) == $spsSize) { //  make sure that whole SequenceParameterSet was red
0171                     $spsReader = new AVCSequenceParameterSetReader($sps);
0172                     $spsReader->readData();
0173                     $info['video']['resolution_x'] = $spsReader->getWidth();
0174                     $info['video']['resolution_y'] = $spsReader->getHeight();
0175                   }
0176                 }
0177               }
0178               // end: moysevichØgmail*com
0179 
0180             } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
0181 
0182               $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
0183               $PictureSizeType = $PictureSizeType & 0x0007;
0184               $info['flv']['header']['videoSizeType'] = $PictureSizeType;
0185               switch ($PictureSizeType) {
0186                 case 0:
0187                   //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
0188                   //$PictureSizeEnc <<= 1;
0189                   //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
0190                   //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
0191                   //$PictureSizeEnc <<= 1;
0192                   //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
0193 
0194                   $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
0195                   $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
0196                   $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
0197                   $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
0198                   break;
0199 
0200                 case 1:
0201                   $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
0202                   $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
0203                   $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
0204                   $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
0205                   break;
0206 
0207                 case 2:
0208                   $info['video']['resolution_x'] = 352;
0209                   $info['video']['resolution_y'] = 288;
0210                   break;
0211 
0212                 case 3:
0213                   $info['video']['resolution_x'] = 176;
0214                   $info['video']['resolution_y'] = 144;
0215                   break;
0216 
0217                 case 4:
0218                   $info['video']['resolution_x'] = 128;
0219                   $info['video']['resolution_y'] = 96;
0220                   break;
0221 
0222                 case 5:
0223                   $info['video']['resolution_x'] = 320;
0224                   $info['video']['resolution_y'] = 240;
0225                   break;
0226 
0227                 case 6:
0228                   $info['video']['resolution_x'] = 160;
0229                   $info['video']['resolution_y'] = 120;
0230                   break;
0231 
0232                 default:
0233                   $info['video']['resolution_x'] = 0;
0234                   $info['video']['resolution_y'] = 0;
0235                   break;
0236 
0237               }
0238 
0239             } elseif ($info['flv']['video']['videoCodec'] ==  GETID3_FLV_VIDEO_VP6FLV_ALPHA) {
0240 
0241               /* contributed by schouwerwouØgmail*com */
0242               if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
0243                 $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
0244                 $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
0245                 $info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
0246                 $info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
0247               }
0248               /* end schouwerwouØgmail*com */
0249 
0250             }
0251             if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
0252               $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
0253             }
0254           }
0255           break;
0256 
0257         // Meta tag
0258         case GETID3_FLV_TAG_META:
0259           if (!$found_meta) {
0260             $found_meta = true;
0261             $this->fseek(-1, SEEK_CUR);
0262             $datachunk = $this->fread($DataLength);
0263             $AMFstream = new AMFStream($datachunk);
0264             $reader = new AMFReader($AMFstream);
0265             $eventName = $reader->readData();
0266             $info['flv']['meta'][$eventName] = $reader->readData();
0267             unset($reader);
0268 
0269             $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
0270             foreach ($copykeys as $sourcekey => $destkey) {
0271               if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
0272                 switch ($sourcekey) {
0273                   case 'width':
0274                   case 'height':
0275                     $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
0276                     break;
0277                   case 'audiodatarate':
0278                     $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
0279                     break;
0280                   case 'videodatarate':
0281                   case 'frame_rate':
0282                   default:
0283                     $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
0284                     break;
0285                 }
0286               }
0287             }
0288             if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
0289               $found_valid_meta_playtime = true;
0290             }
0291           }
0292           break;
0293 
0294         default:
0295           // noop
0296           break;
0297       }
0298       $this->fseek($NextOffset);
0299     }
0300 
0301     $info['playtime_seconds'] = $Duration / 1000;
0302     if ($info['playtime_seconds'] > 0) {
0303       $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
0304     }
0305 
0306     if ($info['flv']['header']['hasAudio']) {
0307       $info['audio']['codec']           =   self::audioFormatLookup($info['flv']['audio']['audioFormat']);
0308       $info['audio']['sample_rate']     =     self::audioRateLookup($info['flv']['audio']['audioRate']);
0309       $info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);
0310 
0311       $info['audio']['channels']   =  $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
0312       $info['audio']['lossless']   = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
0313       $info['audio']['dataformat'] = 'flv';
0314     }
0315     if (!empty($info['flv']['header']['hasVideo'])) {
0316       $info['video']['codec']      = self::videoCodecLookup($info['flv']['video']['videoCodec']);
0317       $info['video']['dataformat'] = 'flv';
0318       $info['video']['lossless']   = false;
0319     }
0320 
0321     // Set information from meta
0322     if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
0323       $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
0324       $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
0325     }
0326     if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
0327       $info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
0328     }
0329     if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
0330       $info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
0331     }
0332     return true;
0333   }
0334 
0335 
0336   public static function audioFormatLookup($id) {
0337     static $lookup = array(
0338       0  => 'Linear PCM, platform endian',
0339       1  => 'ADPCM',
0340       2  => 'mp3',
0341       3  => 'Linear PCM, little endian',
0342       4  => 'Nellymoser 16kHz mono',
0343       5  => 'Nellymoser 8kHz mono',
0344       6  => 'Nellymoser',
0345       7  => 'G.711A-law logarithmic PCM',
0346       8  => 'G.711 mu-law logarithmic PCM',
0347       9  => 'reserved',
0348       10 => 'AAC',
0349       11 => 'Speex',
0350       12 => false, // unknown?
0351       13 => false, // unknown?
0352       14 => 'mp3 8kHz',
0353       15 => 'Device-specific sound',
0354     );
0355     return (isset($lookup[$id]) ? $lookup[$id] : false);
0356   }
0357 
0358   public static function audioRateLookup($id) {
0359     static $lookup = array(
0360       0 =>  5500,
0361       1 => 11025,
0362       2 => 22050,
0363       3 => 44100,
0364     );
0365     return (isset($lookup[$id]) ? $lookup[$id] : false);
0366   }
0367 
0368   public static function audioBitDepthLookup($id) {
0369     static $lookup = array(
0370       0 =>  8,
0371       1 => 16,
0372     );
0373     return (isset($lookup[$id]) ? $lookup[$id] : false);
0374   }
0375 
0376   public static function videoCodecLookup($id) {
0377     static $lookup = array(
0378       GETID3_FLV_VIDEO_H263         => 'Sorenson H.263',
0379       GETID3_FLV_VIDEO_SCREEN       => 'Screen video',
0380       GETID3_FLV_VIDEO_VP6FLV       => 'On2 VP6',
0381       GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
0382       GETID3_FLV_VIDEO_SCREENV2     => 'Screen video v2',
0383       GETID3_FLV_VIDEO_H264         => 'Sorenson H.264',
0384     );
0385     return (isset($lookup[$id]) ? $lookup[$id] : false);
0386   }
0387 }
0388 
0389 class AMFStream {
0390   public $bytes;
0391   public $pos;
0392 
0393   public function __construct(&$bytes) {
0394     $this->bytes =& $bytes;
0395     $this->pos = 0;
0396   }
0397 
0398   public function readByte() {
0399     return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
0400   }
0401 
0402   public function readInt() {
0403     return ($this->readByte() << 8) + $this->readByte();
0404   }
0405 
0406   public function readLong() {
0407     return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
0408   }
0409 
0410   public function readDouble() {
0411     return getid3_lib::BigEndian2Float($this->read(8));
0412   }
0413 
0414   public function readUTF() {
0415     $length = $this->readInt();
0416     return $this->read($length);
0417   }
0418 
0419   public function readLongUTF() {
0420     $length = $this->readLong();
0421     return $this->read($length);
0422   }
0423 
0424   public function read($length) {
0425     $val = substr($this->bytes, $this->pos, $length);
0426     $this->pos += $length;
0427     return $val;
0428   }
0429 
0430   public function peekByte() {
0431     $pos = $this->pos;
0432     $val = $this->readByte();
0433     $this->pos = $pos;
0434     return $val;
0435   }
0436 
0437   public function peekInt() {
0438     $pos = $this->pos;
0439     $val = $this->readInt();
0440     $this->pos = $pos;
0441     return $val;
0442   }
0443 
0444   public function peekLong() {
0445     $pos = $this->pos;
0446     $val = $this->readLong();
0447     $this->pos = $pos;
0448     return $val;
0449   }
0450 
0451   public function peekDouble() {
0452     $pos = $this->pos;
0453     $val = $this->readDouble();
0454     $this->pos = $pos;
0455     return $val;
0456   }
0457 
0458   public function peekUTF() {
0459     $pos = $this->pos;
0460     $val = $this->readUTF();
0461     $this->pos = $pos;
0462     return $val;
0463   }
0464 
0465   public function peekLongUTF() {
0466     $pos = $this->pos;
0467     $val = $this->readLongUTF();
0468     $this->pos = $pos;
0469     return $val;
0470   }
0471 }
0472 
0473 class AMFReader {
0474   public $stream;
0475 
0476   public function __construct(&$stream) {
0477     $this->stream =& $stream;
0478   }
0479 
0480   public function readData() {
0481     $value = null;
0482 
0483     $type = $this->stream->readByte();
0484     switch ($type) {
0485 
0486       // Double
0487       case 0:
0488         $value = $this->readDouble();
0489       break;
0490 
0491       // Boolean
0492       case 1:
0493         $value = $this->readBoolean();
0494         break;
0495 
0496       // String
0497       case 2:
0498         $value = $this->readString();
0499         break;
0500 
0501       // Object
0502       case 3:
0503         $value = $this->readObject();
0504         break;
0505 
0506       // null
0507       case 6:
0508         return null;
0509         break;
0510 
0511       // Mixed array
0512       case 8:
0513         $value = $this->readMixedArray();
0514         break;
0515 
0516       // Array
0517       case 10:
0518         $value = $this->readArray();
0519         break;
0520 
0521       // Date
0522       case 11:
0523         $value = $this->readDate();
0524         break;
0525 
0526       // Long string
0527       case 13:
0528         $value = $this->readLongString();
0529         break;
0530 
0531       // XML (handled as string)
0532       case 15:
0533         $value = $this->readXML();
0534         break;
0535 
0536       // Typed object (handled as object)
0537       case 16:
0538         $value = $this->readTypedObject();
0539         break;
0540 
0541       // Long string
0542       default:
0543         $value = '(unknown or unsupported data type)';
0544       break;
0545     }
0546 
0547     return $value;
0548   }
0549 
0550   public function readDouble() {
0551     return $this->stream->readDouble();
0552   }
0553 
0554   public function readBoolean() {
0555     return $this->stream->readByte() == 1;
0556   }
0557 
0558   public function readString() {
0559     return $this->stream->readUTF();
0560   }
0561 
0562   public function readObject() {
0563     // Get highest numerical index - ignored
0564 //    $highestIndex = $this->stream->readLong();
0565 
0566     $data = array();
0567 
0568     while ($key = $this->stream->readUTF()) {
0569       $data[$key] = $this->readData();
0570     }
0571     // Mixed array record ends with empty string (0x00 0x00) and 0x09
0572     if (($key == '') && ($this->stream->peekByte() == 0x09)) {
0573       // Consume byte
0574       $this->stream->readByte();
0575     }
0576     return $data;
0577   }
0578 
0579   public function readMixedArray() {
0580     // Get highest numerical index - ignored
0581     $highestIndex = $this->stream->readLong();
0582 
0583     $data = array();
0584 
0585     while ($key = $this->stream->readUTF()) {
0586       if (is_numeric($key)) {
0587         $key = (float) $key;
0588       }
0589       $data[$key] = $this->readData();
0590     }
0591     // Mixed array record ends with empty string (0x00 0x00) and 0x09
0592     if (($key == '') && ($this->stream->peekByte() == 0x09)) {
0593       // Consume byte
0594       $this->stream->readByte();
0595     }
0596 
0597     return $data;
0598   }
0599 
0600   public function readArray() {
0601     $length = $this->stream->readLong();
0602     $data = array();
0603 
0604     for ($i = 0; $i < $length; $i++) {
0605       $data[] = $this->readData();
0606     }
0607     return $data;
0608   }
0609 
0610   public function readDate() {
0611     $timestamp = $this->stream->readDouble();
0612     $timezone = $this->stream->readInt();
0613     return $timestamp;
0614   }
0615 
0616   public function readLongString() {
0617     return $this->stream->readLongUTF();
0618   }
0619 
0620   public function readXML() {
0621     return $this->stream->readLongUTF();
0622   }
0623 
0624   public function readTypedObject() {
0625     $className = $this->stream->readUTF();
0626     return $this->readObject();
0627   }
0628 }
0629 
0630 class AVCSequenceParameterSetReader {
0631   public $sps;
0632   public $start = 0;
0633   public $currentBytes = 0;
0634   public $currentBits = 0;
0635   public $width;
0636   public $height;
0637 
0638   public function __construct($sps) {
0639     $this->sps = $sps;
0640   }
0641 
0642   public function readData() {
0643     $this->skipBits(8);
0644     $this->skipBits(8);
0645     $profile = $this->getBits(8);                               // read profile
0646     if ($profile > 0) {
0647       $this->skipBits(8);
0648       $level_idc = $this->getBits(8);                         // level_idc
0649       $this->expGolombUe();                                   // seq_parameter_set_id // sps
0650       $this->expGolombUe();                                   // log2_max_frame_num_minus4
0651       $picOrderType = $this->expGolombUe();                   // pic_order_cnt_type
0652       if ($picOrderType == 0) {
0653         $this->expGolombUe();                               // log2_max_pic_order_cnt_lsb_minus4
0654       } elseif ($picOrderType == 1) {
0655         $this->skipBits(1);                                 // delta_pic_order_always_zero_flag
0656         $this->expGolombSe();                               // offset_for_non_ref_pic
0657         $this->expGolombSe();                               // offset_for_top_to_bottom_field
0658         $num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
0659         for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
0660           $this->expGolombSe();                           // offset_for_ref_frame[ i ]
0661         }
0662       }
0663       $this->expGolombUe();                                   // num_ref_frames
0664       $this->skipBits(1);                                     // gaps_in_frame_num_value_allowed_flag
0665       $pic_width_in_mbs_minus1 = $this->expGolombUe();        // pic_width_in_mbs_minus1
0666       $pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1
0667 
0668       $frame_mbs_only_flag = $this->getBits(1);               // frame_mbs_only_flag
0669       if ($frame_mbs_only_flag == 0) {
0670         $this->skipBits(1);                                 // mb_adaptive_frame_field_flag
0671       }
0672       $this->skipBits(1);                                     // direct_8x8_inference_flag
0673       $frame_cropping_flag = $this->getBits(1);               // frame_cropping_flag
0674 
0675       $frame_crop_left_offset   = 0;
0676       $frame_crop_right_offset  = 0;
0677       $frame_crop_top_offset    = 0;
0678       $frame_crop_bottom_offset = 0;
0679 
0680       if ($frame_cropping_flag) {
0681         $frame_crop_left_offset   = $this->expGolombUe();   // frame_crop_left_offset
0682         $frame_crop_right_offset  = $this->expGolombUe();   // frame_crop_right_offset
0683         $frame_crop_top_offset    = $this->expGolombUe();   // frame_crop_top_offset
0684         $frame_crop_bottom_offset = $this->expGolombUe();   // frame_crop_bottom_offset
0685       }
0686       $this->skipBits(1);                                     // vui_parameters_present_flag
0687       // etc
0688 
0689       $this->width  = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
0690       $this->height = ((2 - $frame_mbs_only_flag) * ($pic_height_in_map_units_minus1 + 1) * 16) - ($frame_crop_top_offset * 2) - ($frame_crop_bottom_offset * 2);
0691     }
0692   }
0693 
0694   public function skipBits($bits) {
0695     $newBits = $this->currentBits + $bits;
0696     $this->currentBytes += (int)floor($newBits / 8);
0697     $this->currentBits = $newBits % 8;
0698   }
0699 
0700   public function getBit() {
0701     $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
0702     $this->skipBits(1);
0703     return $result;
0704   }
0705 
0706   public function getBits($bits) {
0707     $result = 0;
0708     for ($i = 0; $i < $bits; $i++) {
0709       $result = ($result << 1) + $this->getBit();
0710     }
0711     return $result;
0712   }
0713 
0714   public function expGolombUe() {
0715     $significantBits = 0;
0716     $bit = $this->getBit();
0717     while ($bit == 0) {
0718       $significantBits++;
0719       $bit = $this->getBit();
0720 
0721       if ($significantBits > 31) {
0722         // something is broken, this is an emergency escape to prevent infinite loops
0723         return 0;
0724       }
0725     }
0726     return (1 << $significantBits) + $this->getBits($significantBits) - 1;
0727   }
0728 
0729   public function expGolombSe() {
0730     $result = $this->expGolombUe();
0731     if (($result & 0x01) == 0) {
0732       return -($result >> 1);
0733     } else {
0734       return ($result + 1) >> 1;
0735     }
0736   }
0737 
0738   public function getWidth() {
0739     return $this->width;
0740   }
0741 
0742   public function getHeight() {
0743     return $this->height;
0744   }
0745 }