module.audio-video.flv.php 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. <?php
  2. /////////////////////////////////////////////////////////////////
  3. /// getID3() by James Heinrich <info@getid3.org> //
  4. // available at http://getid3.sourceforge.net //
  5. // or http://www.getid3.org //
  6. // also https://github.com/JamesHeinrich/getID3 //
  7. // //
  8. // FLV module by Seth Kaufman <sethØwhirl-i-gig*com> //
  9. // //
  10. // * version 0.1 (26 June 2005) //
  11. // //
  12. // //
  13. // * version 0.1.1 (15 July 2005) //
  14. // minor modifications by James Heinrich <info@getid3.org> //
  15. // //
  16. // * version 0.2 (22 February 2006) //
  17. // Support for On2 VP6 codec and meta information //
  18. // by Steve Webster <steve.websterØfeaturecreep*com> //
  19. // //
  20. // * version 0.3 (15 June 2006) //
  21. // Modified to not read entire file into memory //
  22. // by James Heinrich <info@getid3.org> //
  23. // //
  24. // * version 0.4 (07 December 2007) //
  25. // Bugfixes for incorrectly parsed FLV dimensions //
  26. // and incorrect parsing of onMetaTag //
  27. // by Evgeny Moysevich <moysevichØgmail*com> //
  28. // //
  29. // * version 0.5 (21 May 2009) //
  30. // Fixed parsing of audio tags and added additional codec //
  31. // details. The duration is now read from onMetaTag (if //
  32. // exists), rather than parsing whole file //
  33. // by Nigel Barnes <ngbarnesØhotmail*com> //
  34. // //
  35. // * version 0.6 (24 May 2009) //
  36. // Better parsing of files with h264 video //
  37. // by Evgeny Moysevich <moysevichØgmail*com> //
  38. // //
  39. // * version 0.6.1 (30 May 2011) //
  40. // prevent infinite loops in expGolombUe() //
  41. // //
  42. // * version 0.7.0 (16 Jul 2013) //
  43. // handle GETID3_FLV_VIDEO_VP6FLV_ALPHA //
  44. // improved AVCSequenceParameterSetReader::readData() //
  45. // by Xander Schouwerwou <schouwerwouØgmail*com> //
  46. // //
  47. /////////////////////////////////////////////////////////////////
  48. // //
  49. // module.audio-video.flv.php //
  50. // module for analyzing Shockwave Flash Video files //
  51. // dependencies: NONE //
  52. // ///
  53. /////////////////////////////////////////////////////////////////
  54. define('GETID3_FLV_TAG_AUDIO', 8);
  55. define('GETID3_FLV_TAG_VIDEO', 9);
  56. define('GETID3_FLV_TAG_META', 18);
  57. define('GETID3_FLV_VIDEO_H263', 2);
  58. define('GETID3_FLV_VIDEO_SCREEN', 3);
  59. define('GETID3_FLV_VIDEO_VP6FLV', 4);
  60. define('GETID3_FLV_VIDEO_VP6FLV_ALPHA', 5);
  61. define('GETID3_FLV_VIDEO_SCREENV2', 6);
  62. define('GETID3_FLV_VIDEO_H264', 7);
  63. define('H264_AVC_SEQUENCE_HEADER', 0);
  64. define('H264_PROFILE_BASELINE', 66);
  65. define('H264_PROFILE_MAIN', 77);
  66. define('H264_PROFILE_EXTENDED', 88);
  67. define('H264_PROFILE_HIGH', 100);
  68. define('H264_PROFILE_HIGH10', 110);
  69. define('H264_PROFILE_HIGH422', 122);
  70. define('H264_PROFILE_HIGH444', 144);
  71. define('H264_PROFILE_HIGH444_PREDICTIVE', 244);
  72. class getid3_flv extends getid3_handler {
  73. const magic = 'FLV';
  74. 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
  75. public function Analyze() {
  76. $info = &$this->getid3->info;
  77. $this->fseek($info['avdataoffset']);
  78. $FLVdataLength = $info['avdataend'] - $info['avdataoffset'];
  79. $FLVheader = $this->fread(5);
  80. $info['fileformat'] = 'flv';
  81. $info['flv']['header']['signature'] = substr($FLVheader, 0, 3);
  82. $info['flv']['header']['version'] = getid3_lib::BigEndian2Int(substr($FLVheader, 3, 1));
  83. $TypeFlags = getid3_lib::BigEndian2Int(substr($FLVheader, 4, 1));
  84. if ($info['flv']['header']['signature'] != self::magic) {
  85. $info['error'][] = 'Expecting "'.getid3_lib::PrintHexBytes(self::magic).'" at offset '.$info['avdataoffset'].', found "'.getid3_lib::PrintHexBytes($info['flv']['header']['signature']).'"';
  86. unset($info['flv'], $info['fileformat']);
  87. return false;
  88. }
  89. $info['flv']['header']['hasAudio'] = (bool) ($TypeFlags & 0x04);
  90. $info['flv']['header']['hasVideo'] = (bool) ($TypeFlags & 0x01);
  91. $FrameSizeDataLength = getid3_lib::BigEndian2Int($this->fread(4));
  92. $FLVheaderFrameLength = 9;
  93. if ($FrameSizeDataLength > $FLVheaderFrameLength) {
  94. $this->fseek($FrameSizeDataLength - $FLVheaderFrameLength, SEEK_CUR);
  95. }
  96. $Duration = 0;
  97. $found_video = false;
  98. $found_audio = false;
  99. $found_meta = false;
  100. $found_valid_meta_playtime = false;
  101. $tagParseCount = 0;
  102. $info['flv']['framecount'] = array('total'=>0, 'audio'=>0, 'video'=>0);
  103. $flv_framecount = &$info['flv']['framecount'];
  104. while ((($this->ftell() + 16) < $info['avdataend']) && (($tagParseCount++ <= $this->max_frames) || !$found_valid_meta_playtime)) {
  105. $ThisTagHeader = $this->fread(16);
  106. $PreviousTagLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 0, 4));
  107. $TagType = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 4, 1));
  108. $DataLength = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 5, 3));
  109. $Timestamp = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 8, 3));
  110. $LastHeaderByte = getid3_lib::BigEndian2Int(substr($ThisTagHeader, 15, 1));
  111. $NextOffset = $this->ftell() - 1 + $DataLength;
  112. if ($Timestamp > $Duration) {
  113. $Duration = $Timestamp;
  114. }
  115. $flv_framecount['total']++;
  116. switch ($TagType) {
  117. case GETID3_FLV_TAG_AUDIO:
  118. $flv_framecount['audio']++;
  119. if (!$found_audio) {
  120. $found_audio = true;
  121. $info['flv']['audio']['audioFormat'] = ($LastHeaderByte >> 4) & 0x0F;
  122. $info['flv']['audio']['audioRate'] = ($LastHeaderByte >> 2) & 0x03;
  123. $info['flv']['audio']['audioSampleSize'] = ($LastHeaderByte >> 1) & 0x01;
  124. $info['flv']['audio']['audioType'] = $LastHeaderByte & 0x01;
  125. }
  126. break;
  127. case GETID3_FLV_TAG_VIDEO:
  128. $flv_framecount['video']++;
  129. if (!$found_video) {
  130. $found_video = true;
  131. $info['flv']['video']['videoCodec'] = $LastHeaderByte & 0x07;
  132. $FLVvideoHeader = $this->fread(11);
  133. if ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H264) {
  134. // this code block contributed by: moysevichØgmail*com
  135. $AVCPacketType = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 0, 1));
  136. if ($AVCPacketType == H264_AVC_SEQUENCE_HEADER) {
  137. // read AVCDecoderConfigurationRecord
  138. $configurationVersion = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 1));
  139. $AVCProfileIndication = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 1));
  140. $profile_compatibility = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 1));
  141. $lengthSizeMinusOne = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 1));
  142. $numOfSequenceParameterSets = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 8, 1));
  143. if (($numOfSequenceParameterSets & 0x1F) != 0) {
  144. // there is at least one SequenceParameterSet
  145. // read size of the first SequenceParameterSet
  146. //$spsSize = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 9, 2));
  147. $spsSize = getid3_lib::LittleEndian2Int(substr($FLVvideoHeader, 9, 2));
  148. // read the first SequenceParameterSet
  149. $sps = $this->fread($spsSize);
  150. if (strlen($sps) == $spsSize) { // make sure that whole SequenceParameterSet was red
  151. $spsReader = new AVCSequenceParameterSetReader($sps);
  152. $spsReader->readData();
  153. $info['video']['resolution_x'] = $spsReader->getWidth();
  154. $info['video']['resolution_y'] = $spsReader->getHeight();
  155. }
  156. }
  157. }
  158. // end: moysevichØgmail*com
  159. } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_H263) {
  160. $PictureSizeType = (getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 3, 2))) >> 7;
  161. $PictureSizeType = $PictureSizeType & 0x0007;
  162. $info['flv']['header']['videoSizeType'] = $PictureSizeType;
  163. switch ($PictureSizeType) {
  164. case 0:
  165. //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2));
  166. //$PictureSizeEnc <<= 1;
  167. //$info['video']['resolution_x'] = ($PictureSizeEnc & 0xFF00) >> 8;
  168. //$PictureSizeEnc = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
  169. //$PictureSizeEnc <<= 1;
  170. //$info['video']['resolution_y'] = ($PictureSizeEnc & 0xFF00) >> 8;
  171. $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 2)) >> 7;
  172. $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 5, 2)) >> 7;
  173. $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFF;
  174. $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFF;
  175. break;
  176. case 1:
  177. $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 4, 3)) >> 7;
  178. $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 3)) >> 7;
  179. $info['video']['resolution_x'] = $PictureSizeEnc['x'] & 0xFFFF;
  180. $info['video']['resolution_y'] = $PictureSizeEnc['y'] & 0xFFFF;
  181. break;
  182. case 2:
  183. $info['video']['resolution_x'] = 352;
  184. $info['video']['resolution_y'] = 288;
  185. break;
  186. case 3:
  187. $info['video']['resolution_x'] = 176;
  188. $info['video']['resolution_y'] = 144;
  189. break;
  190. case 4:
  191. $info['video']['resolution_x'] = 128;
  192. $info['video']['resolution_y'] = 96;
  193. break;
  194. case 5:
  195. $info['video']['resolution_x'] = 320;
  196. $info['video']['resolution_y'] = 240;
  197. break;
  198. case 6:
  199. $info['video']['resolution_x'] = 160;
  200. $info['video']['resolution_y'] = 120;
  201. break;
  202. default:
  203. $info['video']['resolution_x'] = 0;
  204. $info['video']['resolution_y'] = 0;
  205. break;
  206. }
  207. } elseif ($info['flv']['video']['videoCodec'] == GETID3_FLV_VIDEO_VP6FLV_ALPHA) {
  208. /* contributed by schouwerwouØgmail*com */
  209. if (!isset($info['video']['resolution_x'])) { // only when meta data isn't set
  210. $PictureSizeEnc['x'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 6, 2));
  211. $PictureSizeEnc['y'] = getid3_lib::BigEndian2Int(substr($FLVvideoHeader, 7, 2));
  212. $info['video']['resolution_x'] = ($PictureSizeEnc['x'] & 0xFF) << 3;
  213. $info['video']['resolution_y'] = ($PictureSizeEnc['y'] & 0xFF) << 3;
  214. }
  215. /* end schouwerwouØgmail*com */
  216. }
  217. if (!empty($info['video']['resolution_x']) && !empty($info['video']['resolution_y'])) {
  218. $info['video']['pixel_aspect_ratio'] = $info['video']['resolution_x'] / $info['video']['resolution_y'];
  219. }
  220. }
  221. break;
  222. // Meta tag
  223. case GETID3_FLV_TAG_META:
  224. if (!$found_meta) {
  225. $found_meta = true;
  226. $this->fseek(-1, SEEK_CUR);
  227. $datachunk = $this->fread($DataLength);
  228. $AMFstream = new AMFStream($datachunk);
  229. $reader = new AMFReader($AMFstream);
  230. $eventName = $reader->readData();
  231. $info['flv']['meta'][$eventName] = $reader->readData();
  232. unset($reader);
  233. $copykeys = array('framerate'=>'frame_rate', 'width'=>'resolution_x', 'height'=>'resolution_y', 'audiodatarate'=>'bitrate', 'videodatarate'=>'bitrate');
  234. foreach ($copykeys as $sourcekey => $destkey) {
  235. if (isset($info['flv']['meta']['onMetaData'][$sourcekey])) {
  236. switch ($sourcekey) {
  237. case 'width':
  238. case 'height':
  239. $info['video'][$destkey] = intval(round($info['flv']['meta']['onMetaData'][$sourcekey]));
  240. break;
  241. case 'audiodatarate':
  242. $info['audio'][$destkey] = getid3_lib::CastAsInt(round($info['flv']['meta']['onMetaData'][$sourcekey] * 1000));
  243. break;
  244. case 'videodatarate':
  245. case 'frame_rate':
  246. default:
  247. $info['video'][$destkey] = $info['flv']['meta']['onMetaData'][$sourcekey];
  248. break;
  249. }
  250. }
  251. }
  252. if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
  253. $found_valid_meta_playtime = true;
  254. }
  255. }
  256. break;
  257. default:
  258. // noop
  259. break;
  260. }
  261. $this->fseek($NextOffset);
  262. }
  263. $info['playtime_seconds'] = $Duration / 1000;
  264. if ($info['playtime_seconds'] > 0) {
  265. $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
  266. }
  267. if ($info['flv']['header']['hasAudio']) {
  268. $info['audio']['codec'] = self::audioFormatLookup($info['flv']['audio']['audioFormat']);
  269. $info['audio']['sample_rate'] = self::audioRateLookup($info['flv']['audio']['audioRate']);
  270. $info['audio']['bits_per_sample'] = self::audioBitDepthLookup($info['flv']['audio']['audioSampleSize']);
  271. $info['audio']['channels'] = $info['flv']['audio']['audioType'] + 1; // 0=mono,1=stereo
  272. $info['audio']['lossless'] = ($info['flv']['audio']['audioFormat'] ? false : true); // 0=uncompressed
  273. $info['audio']['dataformat'] = 'flv';
  274. }
  275. if (!empty($info['flv']['header']['hasVideo'])) {
  276. $info['video']['codec'] = self::videoCodecLookup($info['flv']['video']['videoCodec']);
  277. $info['video']['dataformat'] = 'flv';
  278. $info['video']['lossless'] = false;
  279. }
  280. // Set information from meta
  281. if (!empty($info['flv']['meta']['onMetaData']['duration'])) {
  282. $info['playtime_seconds'] = $info['flv']['meta']['onMetaData']['duration'];
  283. $info['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
  284. }
  285. if (isset($info['flv']['meta']['onMetaData']['audiocodecid'])) {
  286. $info['audio']['codec'] = self::audioFormatLookup($info['flv']['meta']['onMetaData']['audiocodecid']);
  287. }
  288. if (isset($info['flv']['meta']['onMetaData']['videocodecid'])) {
  289. $info['video']['codec'] = self::videoCodecLookup($info['flv']['meta']['onMetaData']['videocodecid']);
  290. }
  291. return true;
  292. }
  293. public static function audioFormatLookup($id) {
  294. static $lookup = array(
  295. 0 => 'Linear PCM, platform endian',
  296. 1 => 'ADPCM',
  297. 2 => 'mp3',
  298. 3 => 'Linear PCM, little endian',
  299. 4 => 'Nellymoser 16kHz mono',
  300. 5 => 'Nellymoser 8kHz mono',
  301. 6 => 'Nellymoser',
  302. 7 => 'G.711A-law logarithmic PCM',
  303. 8 => 'G.711 mu-law logarithmic PCM',
  304. 9 => 'reserved',
  305. 10 => 'AAC',
  306. 11 => 'Speex',
  307. 12 => false, // unknown?
  308. 13 => false, // unknown?
  309. 14 => 'mp3 8kHz',
  310. 15 => 'Device-specific sound',
  311. );
  312. return (isset($lookup[$id]) ? $lookup[$id] : false);
  313. }
  314. public static function audioRateLookup($id) {
  315. static $lookup = array(
  316. 0 => 5500,
  317. 1 => 11025,
  318. 2 => 22050,
  319. 3 => 44100,
  320. );
  321. return (isset($lookup[$id]) ? $lookup[$id] : false);
  322. }
  323. public static function audioBitDepthLookup($id) {
  324. static $lookup = array(
  325. 0 => 8,
  326. 1 => 16,
  327. );
  328. return (isset($lookup[$id]) ? $lookup[$id] : false);
  329. }
  330. public static function videoCodecLookup($id) {
  331. static $lookup = array(
  332. GETID3_FLV_VIDEO_H263 => 'Sorenson H.263',
  333. GETID3_FLV_VIDEO_SCREEN => 'Screen video',
  334. GETID3_FLV_VIDEO_VP6FLV => 'On2 VP6',
  335. GETID3_FLV_VIDEO_VP6FLV_ALPHA => 'On2 VP6 with alpha channel',
  336. GETID3_FLV_VIDEO_SCREENV2 => 'Screen video v2',
  337. GETID3_FLV_VIDEO_H264 => 'Sorenson H.264',
  338. );
  339. return (isset($lookup[$id]) ? $lookup[$id] : false);
  340. }
  341. }
  342. class AMFStream {
  343. public $bytes;
  344. public $pos;
  345. public function __construct(&$bytes) {
  346. $this->bytes =& $bytes;
  347. $this->pos = 0;
  348. }
  349. public function readByte() {
  350. return getid3_lib::BigEndian2Int(substr($this->bytes, $this->pos++, 1));
  351. }
  352. public function readInt() {
  353. return ($this->readByte() << 8) + $this->readByte();
  354. }
  355. public function readLong() {
  356. return ($this->readByte() << 24) + ($this->readByte() << 16) + ($this->readByte() << 8) + $this->readByte();
  357. }
  358. public function readDouble() {
  359. return getid3_lib::BigEndian2Float($this->read(8));
  360. }
  361. public function readUTF() {
  362. $length = $this->readInt();
  363. return $this->read($length);
  364. }
  365. public function readLongUTF() {
  366. $length = $this->readLong();
  367. return $this->read($length);
  368. }
  369. public function read($length) {
  370. $val = substr($this->bytes, $this->pos, $length);
  371. $this->pos += $length;
  372. return $val;
  373. }
  374. public function peekByte() {
  375. $pos = $this->pos;
  376. $val = $this->readByte();
  377. $this->pos = $pos;
  378. return $val;
  379. }
  380. public function peekInt() {
  381. $pos = $this->pos;
  382. $val = $this->readInt();
  383. $this->pos = $pos;
  384. return $val;
  385. }
  386. public function peekLong() {
  387. $pos = $this->pos;
  388. $val = $this->readLong();
  389. $this->pos = $pos;
  390. return $val;
  391. }
  392. public function peekDouble() {
  393. $pos = $this->pos;
  394. $val = $this->readDouble();
  395. $this->pos = $pos;
  396. return $val;
  397. }
  398. public function peekUTF() {
  399. $pos = $this->pos;
  400. $val = $this->readUTF();
  401. $this->pos = $pos;
  402. return $val;
  403. }
  404. public function peekLongUTF() {
  405. $pos = $this->pos;
  406. $val = $this->readLongUTF();
  407. $this->pos = $pos;
  408. return $val;
  409. }
  410. }
  411. class AMFReader {
  412. public $stream;
  413. public function __construct(&$stream) {
  414. $this->stream =& $stream;
  415. }
  416. public function readData() {
  417. $value = null;
  418. $type = $this->stream->readByte();
  419. switch ($type) {
  420. // Double
  421. case 0:
  422. $value = $this->readDouble();
  423. break;
  424. // Boolean
  425. case 1:
  426. $value = $this->readBoolean();
  427. break;
  428. // String
  429. case 2:
  430. $value = $this->readString();
  431. break;
  432. // Object
  433. case 3:
  434. $value = $this->readObject();
  435. break;
  436. // null
  437. case 6:
  438. return null;
  439. break;
  440. // Mixed array
  441. case 8:
  442. $value = $this->readMixedArray();
  443. break;
  444. // Array
  445. case 10:
  446. $value = $this->readArray();
  447. break;
  448. // Date
  449. case 11:
  450. $value = $this->readDate();
  451. break;
  452. // Long string
  453. case 13:
  454. $value = $this->readLongString();
  455. break;
  456. // XML (handled as string)
  457. case 15:
  458. $value = $this->readXML();
  459. break;
  460. // Typed object (handled as object)
  461. case 16:
  462. $value = $this->readTypedObject();
  463. break;
  464. // Long string
  465. default:
  466. $value = '(unknown or unsupported data type)';
  467. break;
  468. }
  469. return $value;
  470. }
  471. public function readDouble() {
  472. return $this->stream->readDouble();
  473. }
  474. public function readBoolean() {
  475. return $this->stream->readByte() == 1;
  476. }
  477. public function readString() {
  478. return $this->stream->readUTF();
  479. }
  480. public function readObject() {
  481. // Get highest numerical index - ignored
  482. // $highestIndex = $this->stream->readLong();
  483. $data = array();
  484. while ($key = $this->stream->readUTF()) {
  485. $data[$key] = $this->readData();
  486. }
  487. // Mixed array record ends with empty string (0x00 0x00) and 0x09
  488. if (($key == '') && ($this->stream->peekByte() == 0x09)) {
  489. // Consume byte
  490. $this->stream->readByte();
  491. }
  492. return $data;
  493. }
  494. public function readMixedArray() {
  495. // Get highest numerical index - ignored
  496. $highestIndex = $this->stream->readLong();
  497. $data = array();
  498. while ($key = $this->stream->readUTF()) {
  499. if (is_numeric($key)) {
  500. $key = (float) $key;
  501. }
  502. $data[$key] = $this->readData();
  503. }
  504. // Mixed array record ends with empty string (0x00 0x00) and 0x09
  505. if (($key == '') && ($this->stream->peekByte() == 0x09)) {
  506. // Consume byte
  507. $this->stream->readByte();
  508. }
  509. return $data;
  510. }
  511. public function readArray() {
  512. $length = $this->stream->readLong();
  513. $data = array();
  514. for ($i = 0; $i < $length; $i++) {
  515. $data[] = $this->readData();
  516. }
  517. return $data;
  518. }
  519. public function readDate() {
  520. $timestamp = $this->stream->readDouble();
  521. $timezone = $this->stream->readInt();
  522. return $timestamp;
  523. }
  524. public function readLongString() {
  525. return $this->stream->readLongUTF();
  526. }
  527. public function readXML() {
  528. return $this->stream->readLongUTF();
  529. }
  530. public function readTypedObject() {
  531. $className = $this->stream->readUTF();
  532. return $this->readObject();
  533. }
  534. }
  535. class AVCSequenceParameterSetReader {
  536. public $sps;
  537. public $start = 0;
  538. public $currentBytes = 0;
  539. public $currentBits = 0;
  540. public $width;
  541. public $height;
  542. public function __construct($sps) {
  543. $this->sps = $sps;
  544. }
  545. public function readData() {
  546. $this->skipBits(8);
  547. $this->skipBits(8);
  548. $profile = $this->getBits(8); // read profile
  549. if ($profile > 0) {
  550. $this->skipBits(8);
  551. $level_idc = $this->getBits(8); // level_idc
  552. $this->expGolombUe(); // seq_parameter_set_id // sps
  553. $this->expGolombUe(); // log2_max_frame_num_minus4
  554. $picOrderType = $this->expGolombUe(); // pic_order_cnt_type
  555. if ($picOrderType == 0) {
  556. $this->expGolombUe(); // log2_max_pic_order_cnt_lsb_minus4
  557. } elseif ($picOrderType == 1) {
  558. $this->skipBits(1); // delta_pic_order_always_zero_flag
  559. $this->expGolombSe(); // offset_for_non_ref_pic
  560. $this->expGolombSe(); // offset_for_top_to_bottom_field
  561. $num_ref_frames_in_pic_order_cnt_cycle = $this->expGolombUe(); // num_ref_frames_in_pic_order_cnt_cycle
  562. for ($i = 0; $i < $num_ref_frames_in_pic_order_cnt_cycle; $i++) {
  563. $this->expGolombSe(); // offset_for_ref_frame[ i ]
  564. }
  565. }
  566. $this->expGolombUe(); // num_ref_frames
  567. $this->skipBits(1); // gaps_in_frame_num_value_allowed_flag
  568. $pic_width_in_mbs_minus1 = $this->expGolombUe(); // pic_width_in_mbs_minus1
  569. $pic_height_in_map_units_minus1 = $this->expGolombUe(); // pic_height_in_map_units_minus1
  570. $frame_mbs_only_flag = $this->getBits(1); // frame_mbs_only_flag
  571. if ($frame_mbs_only_flag == 0) {
  572. $this->skipBits(1); // mb_adaptive_frame_field_flag
  573. }
  574. $this->skipBits(1); // direct_8x8_inference_flag
  575. $frame_cropping_flag = $this->getBits(1); // frame_cropping_flag
  576. $frame_crop_left_offset = 0;
  577. $frame_crop_right_offset = 0;
  578. $frame_crop_top_offset = 0;
  579. $frame_crop_bottom_offset = 0;
  580. if ($frame_cropping_flag) {
  581. $frame_crop_left_offset = $this->expGolombUe(); // frame_crop_left_offset
  582. $frame_crop_right_offset = $this->expGolombUe(); // frame_crop_right_offset
  583. $frame_crop_top_offset = $this->expGolombUe(); // frame_crop_top_offset
  584. $frame_crop_bottom_offset = $this->expGolombUe(); // frame_crop_bottom_offset
  585. }
  586. $this->skipBits(1); // vui_parameters_present_flag
  587. // etc
  588. $this->width = (($pic_width_in_mbs_minus1 + 1) * 16) - ($frame_crop_left_offset * 2) - ($frame_crop_right_offset * 2);
  589. $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);
  590. }
  591. }
  592. public function skipBits($bits) {
  593. $newBits = $this->currentBits + $bits;
  594. $this->currentBytes += (int)floor($newBits / 8);
  595. $this->currentBits = $newBits % 8;
  596. }
  597. public function getBit() {
  598. $result = (getid3_lib::BigEndian2Int(substr($this->sps, $this->currentBytes, 1)) >> (7 - $this->currentBits)) & 0x01;
  599. $this->skipBits(1);
  600. return $result;
  601. }
  602. public function getBits($bits) {
  603. $result = 0;
  604. for ($i = 0; $i < $bits; $i++) {
  605. $result = ($result << 1) + $this->getBit();
  606. }
  607. return $result;
  608. }
  609. public function expGolombUe() {
  610. $significantBits = 0;
  611. $bit = $this->getBit();
  612. while ($bit == 0) {
  613. $significantBits++;
  614. $bit = $this->getBit();
  615. if ($significantBits > 31) {
  616. // something is broken, this is an emergency escape to prevent infinite loops
  617. return 0;
  618. }
  619. }
  620. return (1 << $significantBits) + $this->getBits($significantBits) - 1;
  621. }
  622. public function expGolombSe() {
  623. $result = $this->expGolombUe();
  624. if (($result & 0x01) == 0) {
  625. return -($result >> 1);
  626. } else {
  627. return ($result + 1) >> 1;
  628. }
  629. }
  630. public function getWidth() {
  631. return $this->width;
  632. }
  633. public function getHeight() {
  634. return $this->height;
  635. }
  636. }