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