angelovcom.net

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

module.audio-video.flv.php (27099B)


      1 <?php
      2 /////////////////////////////////////////////////////////////////
      3 /// getID3() by James Heinrich <info@getid3.org>               //
      4 //  available at https://github.com/JamesHeinrich/getID3       //
      5 //            or https://www.getid3.org                        //
      6 //            or http://getid3.sourceforge.net                 //
      7 //  see readme.txt for more details                            //
      8 /////////////////////////////////////////////////////////////////
      9 //                                                             //
     10 // module.audio-video.flv.php                                  //
     11 // module for analyzing Shockwave Flash Video files            //
     12 // dependencies: NONE                                          //
     13 //                                                             //
     14 /////////////////////////////////////////////////////////////////
     15 //                                                             //
     16 //  FLV module by Seth Kaufman <sethØwhirl-i-gig*com>          //
     17 //                                                             //
     18 //  * version 0.1 (26 June 2005)                               //
     19 //                                                             //
     20 //  * version 0.1.1 (15 July 2005)                             //
     21 //  minor modifications by James Heinrich <info@getid3.org>    //
     22 //                                                             //
     23 //  * version 0.2 (22 February 2006)                           //
     24 //  Support for On2 VP6 codec and meta information             //
     25 //    by Steve Webster <steve.websterØfeaturecreep*com>        //
     26 //                                                             //
     27 //  * version 0.3 (15 June 2006)                               //
     28 //  Modified to not read entire file into memory               //
     29 //    by James Heinrich <info@getid3.org>                      //
     30 //                                                             //
     31 //  * version 0.4 (07 December 2007)                           //
     32 //  Bugfixes for incorrectly parsed FLV dimensions             //
     33 //    and incorrect parsing of onMetaTag                       //
     34 //    by Evgeny Moysevich <moysevichØgmail*com>                //
     35 //                                                             //
     36 //  * version 0.5 (21 May 2009)                                //
     37 //  Fixed parsing of audio tags and added additional codec     //
     38 //    details. The duration is now read from onMetaTag (if     //
     39 //    exists), rather than parsing whole file                  //
     40 //    by Nigel Barnes <ngbarnesØhotmail*com>                   //
     41 //                                                             //
     42 //  * version 0.6 (24 May 2009)                                //
     43 //  Better parsing of files with h264 video                    //
     44 //    by Evgeny Moysevich <moysevichØgmail*com>                //
     45 //                                                             //
     46 //  * version 0.6.1 (30 May 2011)                              //
     47 //    prevent infinite loops in expGolombUe()                  //
     48 //                                                             //
     49 //  * version 0.7.0 (16 Jul 2013)                              //
     50 //  handle GETID3_FLV_VIDEO_VP6FLV_ALPHA                       //
     51 //  improved AVCSequenceParameterSetReader::readData()         //
     52 //    by Xander Schouwerwou <schouwerwouØgmail*com>            //
     53 //                                                            ///
     54 /////////////////////////////////////////////////////////////////
     55 
     56 if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers
     57 	exit;
     58 }
     59 
     60 define('GETID3_FLV_TAG_AUDIO',          8);
     61 define('GETID3_FLV_TAG_VIDEO',          9);
     62 define('GETID3_FLV_TAG_META',          18);
     63 
     64 define('GETID3_FLV_VIDEO_H263',         2);
     65 define('GETID3_FLV_VIDEO_SCREEN',       3);
     66 define('GETID3_FLV_VIDEO_VP6FLV',       4);
     67 define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
     68 define('GETID3_FLV_VIDEO_SCREENV2',     6);
     69 define('GETID3_FLV_VIDEO_H264',         7);
     70 
     71 define('H264_AVC_SEQUENCE_HEADER',          0);
     72 define('H264_PROFILE_BASELINE',            66);
     73 define('H264_PROFILE_MAIN',                77);
     74 define('H264_PROFILE_EXTENDED',            88);
     75 define('H264_PROFILE_HIGH',               100);
     76 define('H264_PROFILE_HIGH10',             110);
     77 define('H264_PROFILE_HIGH422',            122);
     78 define('H264_PROFILE_HIGH444',            144);
     79 define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
     80 
     81 class getid3_flv extends getid3_handler
     82 {
     83 	const magic = 'FLV';
     84 
     85 	/**
     86 	 * Break out of the loop if too many frames have been scanned; only scan this
     87 	 * many if meta frame does not contain useful duration.
     88 	 *
     89 	 * @var int
     90 	 */
     91 	public $max_frames = 100000;
     92 
     93 	/**
     94 	 * @return bool
     95 	 */
     96 	public function Analyze() {
     97 		$info = &$this->getid3->info;
     98 
     99 		$this->fseek($info['avdataoffset']);
    100 
    101 		$FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
    102 		$FLVheader = $this->fread(5);
    103 
    104 		$info['fileformat'] = 'flv';
    105 		$info['flv']['header']['signature'] =                           substr($FLVheader, 0, 3);
    106 		$info['flv']['header']['version']   = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
    107 		$TypeFlags                          = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
    108 
    109 		if ($info['flv']['header']['signature'] != self::magic) {
    110 			$this->error('Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"');
    111 			unset($info['flv'], $info['fileformat']);
    112 			return false;
    113 		}
    114 
    115 		$info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
    116 		$info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
    117 
    118 		$FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
    119 		$FLVheaderFrameLength = 9;
    120 		if ($FrameSizeDataLength > $FLVheaderFrameLength) {
    121 			$this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
    122 		}
    123 		$Duration = 0;
    124 		$found_video = false;
    125 		$found_audio = false;
    126 		$found_meta  = false;
    127 		$found_valid_meta_playtime = false;
    128 		$tagParseCount = 0;
    129 		$info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
    130 		$flv_framecount = &$info['flv']['framecount'];
    131 		while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime))  {
    132 			$ThisTagHeader = $this->fread(16);
    133 
    134 			$PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  0, 4));
    135 			$TagType           = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  4, 1));
    136 			$DataLength        = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  5, 3));
    137 			$Timestamp         = getid3_lib::BigEndian2Int(substr($ThisTagHeader,  8, 3));
    138 			$LastHeaderByte    = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
    139 			$NextOffset = $this->ftell() - 1 + $DataLength;
    140 			if ($Timestamp > $Duration) {
    141 				$Duration = $Timestamp;
    142 			}
    143 
    144 			$flv_framecount['total']++;
    145 			switch ($TagType) {
    146 				case GETID3_FLV_TAG_AUDIO:
    147 					$flv_framecount['audio']++;
    148 					if (!$found_audio) {
    149 						$found_audio = true;
    150 						$info['flv']['audio']['audioFormat']     = ($LastHeaderByte >> 4) & 0x0F;
    151 						$info['flv']['audio']['audioRate']       = ($LastHeaderByte >> 2) & 0x03;
    152 						$info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
    153 						$info['flv']['audio']['audioType']       =  $LastHeaderByte       & 0x01;
    154 					}
    155 					break;
    156 
    157 				case GETID3_FLV_TAG_VIDEO:
    158 					$flv_framecount['video']++;
    159 					if (!$found_video) {
    160 						$found_video = true;
    161 						$info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
    162 
    163 						$FLVvideoHeader = $this->fread(11);
    164 
    165 						if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
    166 							// this code block contributed by: moysevichØgmail*com
    167 
    168 							$AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
    169 							if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
    170 								//	read AVCDecoderConfigurationRecord
    171 								$configurationVersion       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  4, 1));
    172 								$AVCProfileIndication       = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  5, 1));
    173 								$profile_compatibility      = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  6, 1));
    174 								$lengthSizeMinusOne         = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  7, 1));
    175 								$numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader,  8, 1));
    176 
    177 								if (($numOfSequenceParameterSets & 0x1F) != 0) {
    178 									//	there is at least one SequenceParameterSet
    179 									//	read size of the first SequenceParameterSet
    180 									//$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
    181 									$spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
    182 									//	read the first SequenceParameterSet
    183 									$sps = $this->fread($spsSize);
    184 									if (strlen($sps) == $spsSize) {	//	make sure that whole SequenceParameterSet was red
    185 										$spsReader = new AVCSequenceParameterSetReader($sps);
    186 										$spsReader->readData();
    187 										$info['video']['resolution_x'] = $spsReader->getWidth();
    188 										$info['video']['resolution_y'] = $spsReader->getHeight();
    189 									}
    190 								}
    191 							}
    192 							// end: moysevichØgmail*com
    193 
    194 						} elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
    195 
    196 							$PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
    197 							$PictureSizeType = $PictureSizeType & 0x0007;
    198 							$info['flv']['header']['videoSizeType'] = $PictureSizeType;
    199 							switch ($PictureSizeType) {
    200 								case 0:
    201 									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
    202 									//$PictureSizeEnc <<= 1;
    203 									//$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
    204 									//$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
    205 									//$PictureSizeEnc <<= 1;
    206 									//$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
    207 
    208 									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
    209 									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
    210 									$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
    211 									$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
    212 									break;
    213 
    214 								case 1:
    215 									$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
    216 									$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
    217 									$info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
    218 									$info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
    219 									break;
    220 
    221 								case 2:
    222 									$info['video']['resolution_x'] = 352;
    223 									$info['video']['resolution_y'] = 288;
    224 									break;
    225 
    226 								case 3:
    227 									$info['video']['resolution_x'] = 176;
    228 									$info['video']['resolution_y'] = 144;
    229 									break;
    230 
    231 								case 4:
    232 									$info['video']['resolution_x'] = 128;
    233 									$info['video']['resolution_y'] = 96;
    234 									break;
    235 
    236 								case 5:
    237 									$info['video']['resolution_x'] = 320;
    238 									$info['video']['resolution_y'] = 240;
    239 									break;
    240 
    241 								case 6:
    242 									$info['video']['resolution_x'] = 160;
    243 									$info['video']['resolution_y'] = 120;
    244 									break;
    245 
    246 								default:
    247 									$info['video']['resolution_x'] = 0;
    248 									$info['video']['resolution_y'] = 0;
    249 									break;
    250 
    251 							}
    252 
    253 						} elseif ($info['flv']['video']['videoCodec'] ==  GETID3_FLV_VIDEO_VP6FLV_ALPHA) {
    254 
    255 							/* contributed by schouwerwouØgmail*com */
    256 							if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
    257 								$PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
    258 								$PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
    259 								$info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
    260 								$info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
    261 							}
    262 							/* end schouwerwouØgmail*com */
    263 
    264 						}
    265 						if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
    266 							$info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
    267 						}
    268 					}
    269 					break;
    270 
    271 				// Meta tag
    272 				case GETID3_FLV_TAG_META:
    273 					if (!$found_meta) {
    274 						$found_meta = true;
    275 						$this->fseek(-1, SEEK_CUR);
    276 						$datachunk = $this->fread($DataLength);
    277 						$AMFstream = new AMFStream($datachunk);
    278 						$reader = new AMFReader($AMFstream);
    279 						$eventName = $reader->readData();
    280 						$info['flv']['meta'][$eventName] = $reader->readData();
    281 						unset($reader);
    282 
    283 						$copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
    284 						foreach ($copykeys as $sourcekey => $destkey) {
    285 							if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
    286 								switch ($sourcekey) {
    287 									case 'width':
    288 									case 'height':
    289 										$info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
    290 										break;
    291 									case 'audiodatarate':
    292 										$info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
    293 										break;
    294 									case 'videodatarate':
    295 									case 'frame_rate':
    296 									default:
    297 										$info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
    298 										break;
    299 								}
    300 							}
    301 						}
    302 						if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
    303 							$found_valid_meta_playtime = true;
    304 						}
    305 					}
    306 					break;
    307 
    308 				default:
    309 					// noop
    310 					break;
    311 			}
    312 			$this->fseek($NextOffset);
    313 		}
    314 
    315 		$info['playtime_seconds'] = $Duration / 1000;
    316 		if ($info['playtime_seconds'] > 0) {
    317 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
    318 		}
    319 
    320 		if ($info['flv']['header']['hasAudio']) {
    321 			$info['audio']['codec']           =   self::audioFormatLookup($info['flv']['audio']['audioFormat']);
    322 			$info['audio']['sample_rate']     =     self::audioRateLookup($info['flv']['audio']['audioRate']);
    323 			$info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);
    324 
    325 			$info['audio']['channels']   =  $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
    326 			$info['audio']['lossless']   = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
    327 			$info['audio']['dataformat'] = 'flv';
    328 		}
    329 		if (!empty($info['flv']['header']['hasVideo'])) {
    330 			$info['video']['codec']      = self::videoCodecLookup($info['flv']['video']['videoCodec']);
    331 			$info['video']['dataformat'] = 'flv';
    332 			$info['video']['lossless']   = false;
    333 		}
    334 
    335 		// Set information from meta
    336 		if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
    337 			$info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
    338 			$info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
    339 		}
    340 		if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
    341 			$info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
    342 		}
    343 		if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
    344 			$info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
    345 		}
    346 		return true;
    347 	}
    348 
    349 	/**
    350 	 * @param int $id
    351 	 *
    352 	 * @return string|false
    353 	 */
    354 	public static function audioFormatLookup($id) {
    355 		static $lookup = array(
    356 			0  => 'Linear PCM, platform endian',
    357 			1  => 'ADPCM',
    358 			2  => 'mp3',
    359 			3  => 'Linear PCM, little endian',
    360 			4  => 'Nellymoser 16kHz mono',
    361 			5  => 'Nellymoser 8kHz mono',
    362 			6  => 'Nellymoser',
    363 			7  => 'G.711A-law logarithmic PCM',
    364 			8  => 'G.711 mu-law logarithmic PCM',
    365 			9  => 'reserved',
    366 			10 => 'AAC',
    367 			11 => 'Speex',
    368 			12 => false, // unknown?
    369 			13 => false, // unknown?
    370 			14 => 'mp3 8kHz',
    371 			15 => 'Device-specific sound',
    372 		);
    373 		return (isset($lookup[$id]) ? $lookup[$id] : false);
    374 	}
    375 
    376 	/**
    377 	 * @param int $id
    378 	 *
    379 	 * @return int|false
    380 	 */
    381 	public static function audioRateLookup($id) {
    382 		static $lookup = array(
    383 			0 =>  5500,
    384 			1 => 11025,
    385 			2 => 22050,
    386 			3 => 44100,
    387 		);
    388 		return (isset($lookup[$id]) ? $lookup[$id] : false);
    389 	}
    390 
    391 	/**
    392 	 * @param int $id
    393 	 *
    394 	 * @return int|false
    395 	 */
    396 	public static function audioBitDepthLookup($id) {
    397 		static $lookup = array(
    398 			0 =>  8,
    399 			1 => 16,
    400 		);
    401 		return (isset($lookup[$id]) ? $lookup[$id] : false);
    402 	}
    403 
    404 	/**
    405 	 * @param int $id
    406 	 *
    407 	 * @return string|false
    408 	 */
    409 	public static function videoCodecLookup($id) {
    410 		static $lookup = array(
    411 			GETID3_FLV_VIDEO_H263         => 'Sorenson H.263',
    412 			GETID3_FLV_VIDEO_SCREEN       => 'Screen video',
    413 			GETID3_FLV_VIDEO_VP6FLV       => 'On2 VP6',
    414 			GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
    415 			GETID3_FLV_VIDEO_SCREENV2     => 'Screen video v2',
    416 			GETID3_FLV_VIDEO_H264         => 'Sorenson H.264',
    417 		);
    418 		return (isset($lookup[$id]) ? $lookup[$id] : false);
    419 	}
    420 }
    421 
    422 class AMFStream
    423 {
    424 	/**
    425 	 * @var string
    426 	 */
    427 	public $bytes;
    428 
    429 	/**
    430 	 * @var int
    431 	 */
    432 	public $pos;
    433 
    434 	/**
    435 	 * @param string $bytes
    436 	 */
    437 	public function __construct(&$bytes) {
    438 		$this->bytes =& $bytes;
    439 		$this->pos = 0;
    440 	}
    441 
    442 	/**
    443 	 * @return int
    444 	 */
    445 	public function readByte() { //  8-bit
    446 		return ord(substr($this->bytes, $this->pos++, 1));
    447 	}
    448 
    449 	/**
    450 	 * @return int
    451 	 */
    452 	public function readInt() { // 16-bit
    453 		return ($this->readByte() << 8) + $this->readByte();
    454 	}
    455 
    456 	/**
    457 	 * @return int
    458 	 */
    459 	public function readLong() { // 32-bit
    460 		return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
    461 	}
    462 
    463 	/**
    464 	 * @return float|false
    465 	 */
    466 	public function readDouble() {
    467 		return getid3_lib::BigEndian2Float($this->read(8));
    468 	}
    469 
    470 	/**
    471 	 * @return string
    472 	 */
    473 	public function readUTF() {
    474 		$length = $this->readInt();
    475 		return $this->read($length);
    476 	}
    477 
    478 	/**
    479 	 * @return string
    480 	 */
    481 	public function readLongUTF() {
    482 		$length = $this->readLong();
    483 		return $this->read($length);
    484 	}
    485 
    486 	/**
    487 	 * @param int $length
    488 	 *
    489 	 * @return string
    490 	 */
    491 	public function read($length) {
    492 		$val = substr($this->bytes, $this->pos, $length);
    493 		$this->pos += $length;
    494 		return $val;
    495 	}
    496 
    497 	/**
    498 	 * @return int
    499 	 */
    500 	public function peekByte() {
    501 		$pos = $this->pos;
    502 		$val = $this->readByte();
    503 		$this->pos = $pos;
    504 		return $val;
    505 	}
    506 
    507 	/**
    508 	 * @return int
    509 	 */
    510 	public function peekInt() {
    511 		$pos = $this->pos;
    512 		$val = $this->readInt();
    513 		$this->pos = $pos;
    514 		return $val;
    515 	}
    516 
    517 	/**
    518 	 * @return int
    519 	 */
    520 	public function peekLong() {
    521 		$pos = $this->pos;
    522 		$val = $this->readLong();
    523 		$this->pos = $pos;
    524 		return $val;
    525 	}
    526 
    527 	/**
    528 	 * @return float|false
    529 	 */
    530 	public function peekDouble() {
    531 		$pos = $this->pos;
    532 		$val = $this->readDouble();
    533 		$this->pos = $pos;
    534 		return $val;
    535 	}
    536 
    537 	/**
    538 	 * @return string
    539 	 */
    540 	public function peekUTF() {
    541 		$pos = $this->pos;
    542 		$val = $this->readUTF();
    543 		$this->pos = $pos;
    544 		return $val;
    545 	}
    546 
    547 	/**
    548 	 * @return string
    549 	 */
    550 	public function peekLongUTF() {
    551 		$pos = $this->pos;
    552 		$val = $this->readLongUTF();
    553 		$this->pos = $pos;
    554 		return $val;
    555 	}
    556 }
    557 
    558 class AMFReader
    559 {
    560 	/**
    561 	* @var AMFStream
    562 	*/
    563 	public $stream;
    564 
    565 	/**
    566 	 * @param AMFStream $stream
    567 	 */
    568 	public function __construct(AMFStream $stream) {
    569 		$this->stream = $stream;
    570 	}
    571 
    572 	/**
    573 	 * @return mixed
    574 	 */
    575 	public function readData() {
    576 		$value = null;
    577 
    578 		$type = $this->stream->readByte();
    579 		switch ($type) {
    580 
    581 			// Double
    582 			case 0:
    583 				$value = $this->readDouble();
    584 			break;
    585 
    586 			// Boolean
    587 			case 1:
    588 				$value = $this->readBoolean();
    589 				break;
    590 
    591 			// String
    592 			case 2:
    593 				$value = $this->readString();
    594 				break;
    595 
    596 			// Object
    597 			case 3:
    598 				$value = $this->readObject();
    599 				break;
    600 
    601 			// null
    602 			case 6:
    603 				return null;
    604 
    605 			// Mixed array
    606 			case 8:
    607 				$value = $this->readMixedArray();
    608 				break;
    609 
    610 			// Array
    611 			case 10:
    612 				$value = $this->readArray();
    613 				break;
    614 
    615 			// Date
    616 			case 11:
    617 				$value = $this->readDate();
    618 				break;
    619 
    620 			// Long string
    621 			case 13:
    622 				$value = $this->readLongString();
    623 				break;
    624 
    625 			// XML (handled as string)
    626 			case 15:
    627 				$value = $this->readXML();
    628 				break;
    629 
    630 			// Typed object (handled as object)
    631 			case 16:
    632 				$value = $this->readTypedObject();
    633 				break;
    634 
    635 			// Long string
    636 			default:
    637 				$value = '(unknown or unsupported data type)';
    638 				break;
    639 		}
    640 
    641 		return $value;
    642 	}
    643 
    644 	/**
    645 	 * @return float|false
    646 	 */
    647 	public function readDouble() {
    648 		return $this->stream->readDouble();
    649 	}
    650 
    651 	/**
    652 	 * @return bool
    653 	 */
    654 	public function readBoolean() {
    655 		return $this->stream->readByte() == 1;
    656 	}
    657 
    658 	/**
    659 	 * @return string
    660 	 */
    661 	public function readString() {
    662 		return $this->stream->readUTF();
    663 	}
    664 
    665 	/**
    666 	 * @return array
    667 	 */
    668 	public function readObject() {
    669 		// Get highest numerical index - ignored
    670 //		$highestIndex = $this->stream->readLong();
    671 
    672 		$data = array();
    673 		$key = null;
    674 
    675 		while ($key = $this->stream->readUTF()) {
    676 			$data[$key] = $this->readData();
    677 		}
    678 		// Mixed array record ends with empty string (0x00 0x00) and 0x09
    679 		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
    680 			// Consume byte
    681 			$this->stream->readByte();
    682 		}
    683 		return $data;
    684 	}
    685 
    686 	/**
    687 	 * @return array
    688 	 */
    689 	public function readMixedArray() {
    690 		// Get highest numerical index - ignored
    691 		$highestIndex = $this->stream->readLong();
    692 
    693 		$data = array();
    694 		$key = null;
    695 
    696 		while ($key = $this->stream->readUTF()) {
    697 			if (is_numeric($key)) {
    698 				$key = (int) $key;
    699 			}
    700 			$data[$key] = $this->readData();
    701 		}
    702 		// Mixed array record ends with empty string (0x00 0x00) and 0x09
    703 		if (($key == '') && ($this->stream->peekByte() == 0x09)) {
    704 			// Consume byte
    705 			$this->stream->readByte();
    706 		}
    707 
    708 		return $data;
    709 	}
    710 
    711 	/**
    712 	 * @return array
    713 	 */
    714 	public function readArray() {
    715 		$length = $this->stream->readLong();
    716 		$data = array();
    717 
    718 		for ($i = 0; $i < $length; $i++) {
    719 			$data[] = $this->readData();
    720 		}
    721 		return $data;
    722 	}
    723 
    724 	/**
    725 	 * @return float|false
    726 	 */
    727 	public function readDate() {
    728 		$timestamp = $this->stream->readDouble();
    729 		$timezone = $this->stream->readInt();
    730 		return $timestamp;
    731 	}
    732 
    733 	/**
    734 	 * @return string
    735 	 */
    736 	public function readLongString() {
    737 		return $this->stream->readLongUTF();
    738 	}
    739 
    740 	/**
    741 	 * @return string
    742 	 */
    743 	public function readXML() {
    744 		return $this->stream->readLongUTF();
    745 	}
    746 
    747 	/**
    748 	 * @return array
    749 	 */
    750 	public function readTypedObject() {
    751 		$className = $this->stream->readUTF();
    752 		return $this->readObject();
    753 	}
    754 }
    755 
    756 class AVCSequenceParameterSetReader
    757 {
    758 	/**
    759 	 * @var string
    760 	 */
    761 	public $sps;
    762 	public $start = 0;
    763 	public $currentBytes = 0;
    764 	public $currentBits = 0;
    765 
    766 	/**
    767 	 * @var int
    768 	 */
    769 	public $width;
    770 
    771 	/**
    772 	 * @var int
    773 	 */
    774 	public $height;
    775 
    776 	/**
    777 	 * @param string $sps
    778 	 */
    779 	public function __construct($sps) {
    780 		$this->sps = $sps;
    781 	}
    782 
    783 	public function readData() {
    784 		$this->skipBits(8);
    785 		$this->skipBits(8);
    786 		$profile = $this->getBits(8);                               // read profile
    787 		if ($profile > 0) {
    788 			$this->skipBits(8);
    789 			$level_idc = $this->getBits(8);                         // level_idc
    790 			$this->expGolombUe();                                   // seq_parameter_set_id // sps
    791 			$this->expGolombUe();                                   // log2_max_frame_num_minus4
    792 			$picOrderType = $this->expGolombUe();                   // pic_order_cnt_type
    793 			if ($picOrderType == 0) {
    794 				$this->expGolombUe();                               // log2_max_pic_order_cnt_lsb_minus4
    795 			} elseif ($picOrderType == 1) {
    796 				$this->skipBits(1);                                 // delta_pic_order_always_zero_flag
    797 				$this->expGolombSe();                               // offset_for_non_ref_pic
    798 				$this->expGolombSe();                               // offset_for_top_to_bottom_field
    799 				$num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
    800 				for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
    801 					$this->expGolombSe();                           // offset_for_ref_frame[ i ]
    802 				}
    803 			}
    804 			$this->expGolombUe();                                   // num_ref_frames
    805 			$this->skipBits(1);                                     // gaps_in_frame_num_value_allowed_flag
    806 			$pic_width_in_mbs_minus1 = $this->expGolombUe();        // pic_width_in_mbs_minus1
    807 			$pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1
    808 
    809 			$frame_mbs_only_flag = $this->getBits(1);               // frame_mbs_only_flag
    810 			if ($frame_mbs_only_flag == 0) {
    811 				$this->skipBits(1);                                 // mb_adaptive_frame_field_flag
    812 			}
    813 			$this->skipBits(1);                                     // direct_8x8_inference_flag
    814 			$frame_cropping_flag = $this->getBits(1);               // frame_cropping_flag
    815 
    816 			$frame_crop_left_offset   = 0;
    817 			$frame_crop_right_offset  = 0;
    818 			$frame_crop_top_offset    = 0;
    819 			$frame_crop_bottom_offset = 0;
    820 
    821 			if ($frame_cropping_flag) {
    822 				$frame_crop_left_offset   = $this->expGolombUe();   // frame_crop_left_offset
    823 				$frame_crop_right_offset  = $this->expGolombUe();   // frame_crop_right_offset
    824 				$frame_crop_top_offset    = $this->expGolombUe();   // frame_crop_top_offset
    825 				$frame_crop_bottom_offset = $this->expGolombUe();   // frame_crop_bottom_offset
    826 			}
    827 			$this->skipBits(1);                                     // vui_parameters_present_flag
    828 			// etc
    829 
    830 			$this->width  = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
    831 			$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);
    832 		}
    833 	}
    834 
    835 	/**
    836 	 * @param int $bits
    837 	 */
    838 	public function skipBits($bits) {
    839 		$newBits = $this->currentBits + $bits;
    840 		$this->currentBytes += (int)floor($newBits / 8);
    841 		$this->currentBits = $newBits % 8;
    842 	}
    843 
    844 	/**
    845 	 * @return int
    846 	 */
    847 	public function getBit() {
    848 		$result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
    849 		$this->skipBits(1);
    850 		return $result;
    851 	}
    852 
    853 	/**
    854 	 * @param int $bits
    855 	 *
    856 	 * @return int
    857 	 */
    858 	public function getBits($bits) {
    859 		$result = 0;
    860 		for ($i = 0; $i < $bits; $i++) {
    861 			$result = ($result << 1) + $this->getBit();
    862 		}
    863 		return $result;
    864 	}
    865 
    866 	/**
    867 	 * @return int
    868 	 */
    869 	public function expGolombUe() {
    870 		$significantBits = 0;
    871 		$bit = $this->getBit();
    872 		while ($bit == 0) {
    873 			$significantBits++;
    874 			$bit = $this->getBit();
    875 
    876 			if ($significantBits > 31) {
    877 				// something is broken, this is an emergency escape to prevent infinite loops
    878 				return 0;
    879 			}
    880 		}
    881 		return (1 << $significantBits) + $this->getBits($significantBits) - 1;
    882 	}
    883 
    884 	/**
    885 	 * @return int
    886 	 */
    887 	public function expGolombSe() {
    888 		$result = $this->expGolombUe();
    889 		if (($result & 0x01) == 0) {
    890 			return -($result >> 1);
    891 		} else {
    892 			return ($result + 1) >> 1;
    893 		}
    894 	}
    895 
    896 	/**
    897 	 * @return int
    898 	 */
    899 	public function getWidth() {
    900 		return $this->width;
    901 	}
    902 
    903 	/**
    904 	 * @return int
    905 	 */
    906 	public function getHeight() {
    907 		return $this->height;
    908 	}
    909 }