module.audio-video.flv.php 26 KB

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