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 56if (!defined('GETID3_INCLUDEPATH')) { // prevent path-exposing attacks that access modules directly on public webservers 57 exit; 58} 59 60define('GETID3_FLV_TAG_AUDIO', 8); 61define('GETID3_FLV_TAG_VIDEO', 9); 62define('GETID3_FLV_TAG_META', 18); 63 64define('GETID3_FLV_VIDEO_H263', 2); 65define('GETID3_FLV_VIDEO_SCREEN', 3); 66define('GETID3_FLV_VIDEO_VP6FLV', 4); 67define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5); 68define('GETID3_FLV_VIDEO_SCREENV2', 6); 69define('GETID3_FLV_VIDEO_H264', 7); 70 71define('H264_AVC_SEQUENCE_HEADER', 0); 72define('H264_PROFILE_BASELINE', 66); 73define('H264_PROFILE_MAIN', 77); 74define('H264_PROFILE_EXTENDED', 88); 75define('H264_PROFILE_HIGH', 100); 76define('H264_PROFILE_HIGH10', 110); 77define('H264_PROFILE_HIGH422', 122); 78define('H264_PROFILE_HIGH444', 144); 79define('H264_PROFILE_HIGH444_PREDICTIVE', 244); 80 81class 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 422class 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 558class 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 756class 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} 910