1: <?php
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18: class getid3_aac extends getid3_handler
19: {
20: public function Analyze() {
21: $info = &$this->getid3->info;
22: $this->fseek($info['avdataoffset']);
23: if ($this->fread(4) == 'ADIF') {
24: $this->getAACADIFheaderFilepointer();
25: } else {
26: $this->getAACADTSheaderFilepointer();
27: }
28: return true;
29: }
30:
31:
32:
33: public function getAACADIFheaderFilepointer() {
34: $info = &$this->getid3->info;
35: $info['fileformat'] = 'aac';
36: $info['audio']['dataformat'] = 'aac';
37: $info['audio']['lossless'] = false;
38:
39: $this->fseek($info['avdataoffset']);
40: $AACheader = $this->fread(1024);
41: $offset = 0;
42:
43: if (substr($AACheader, 0, 4) == 'ADIF') {
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65: $AACheaderBitstream = getid3_lib::BigEndian2Bin($AACheader);
66: $bitoffset = 0;
67:
68: $info['aac']['header_type'] = 'ADIF';
69: $bitoffset += 32;
70: $info['aac']['header']['mpeg_version'] = 4;
71:
72: $info['aac']['header']['copyright'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
73: $bitoffset += 1;
74: if ($info['aac']['header']['copyright']) {
75: $info['aac']['header']['copyright_id'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 72));
76: $bitoffset += 72;
77: }
78: $info['aac']['header']['original_copy'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
79: $bitoffset += 1;
80: $info['aac']['header']['home'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
81: $bitoffset += 1;
82: $info['aac']['header']['is_vbr'] = (bool) (substr($AACheaderBitstream, $bitoffset, 1) == '1');
83: $bitoffset += 1;
84: if ($info['aac']['header']['is_vbr']) {
85: $info['audio']['bitrate_mode'] = 'vbr';
86: $info['aac']['header']['bitrate_max'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
87: $bitoffset += 23;
88: } else {
89: $info['audio']['bitrate_mode'] = 'cbr';
90: $info['aac']['header']['bitrate'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 23));
91: $bitoffset += 23;
92: $info['audio']['bitrate'] = $info['aac']['header']['bitrate'];
93: }
94: if ($info['audio']['bitrate'] == 0) {
95: $info['error'][] = 'Corrupt AAC file: bitrate_audio == zero';
96: return false;
97: }
98: $info['aac']['header']['num_program_configs'] = 1 + getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
99: $bitoffset += 4;
100:
101: for ($i = 0; $i < $info['aac']['header']['num_program_configs']; $i++) {
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150: if (!$info['aac']['header']['is_vbr']) {
151: $info['aac']['program_configs'][$i]['buffer_fullness'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 20));
152: $bitoffset += 20;
153: }
154: $info['aac']['program_configs'][$i]['element_instance_tag'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
155: $bitoffset += 4;
156: $info['aac']['program_configs'][$i]['object_type'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
157: $bitoffset += 2;
158: $info['aac']['program_configs'][$i]['sampling_frequency_index'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
159: $bitoffset += 4;
160: $info['aac']['program_configs'][$i]['num_front_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
161: $bitoffset += 4;
162: $info['aac']['program_configs'][$i]['num_side_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
163: $bitoffset += 4;
164: $info['aac']['program_configs'][$i]['num_back_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
165: $bitoffset += 4;
166: $info['aac']['program_configs'][$i]['num_lfe_channel_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
167: $bitoffset += 2;
168: $info['aac']['program_configs'][$i]['num_assoc_data_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 3));
169: $bitoffset += 3;
170: $info['aac']['program_configs'][$i]['num_valid_cc_elements'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
171: $bitoffset += 4;
172: $info['aac']['program_configs'][$i]['mono_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
173: $bitoffset += 1;
174: if ($info['aac']['program_configs'][$i]['mono_mixdown_present']) {
175: $info['aac']['program_configs'][$i]['mono_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
176: $bitoffset += 4;
177: }
178: $info['aac']['program_configs'][$i]['stereo_mixdown_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
179: $bitoffset += 1;
180: if ($info['aac']['program_configs'][$i]['stereo_mixdown_present']) {
181: $info['aac']['program_configs'][$i]['stereo_mixdown_element_number'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
182: $bitoffset += 4;
183: }
184: $info['aac']['program_configs'][$i]['matrix_mixdown_idx_present'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
185: $bitoffset += 1;
186: if ($info['aac']['program_configs'][$i]['matrix_mixdown_idx_present']) {
187: $info['aac']['program_configs'][$i]['matrix_mixdown_idx'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 2));
188: $bitoffset += 2;
189: $info['aac']['program_configs'][$i]['pseudo_surround_enable'] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
190: $bitoffset += 1;
191: }
192: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_front_channel_elements']; $j++) {
193: $info['aac']['program_configs'][$i]['front_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
194: $bitoffset += 1;
195: $info['aac']['program_configs'][$i]['front_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
196: $bitoffset += 4;
197: }
198: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_side_channel_elements']; $j++) {
199: $info['aac']['program_configs'][$i]['side_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
200: $bitoffset += 1;
201: $info['aac']['program_configs'][$i]['side_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
202: $bitoffset += 4;
203: }
204: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_back_channel_elements']; $j++) {
205: $info['aac']['program_configs'][$i]['back_element_is_cpe'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
206: $bitoffset += 1;
207: $info['aac']['program_configs'][$i]['back_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
208: $bitoffset += 4;
209: }
210: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_lfe_channel_elements']; $j++) {
211: $info['aac']['program_configs'][$i]['lfe_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
212: $bitoffset += 4;
213: }
214: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_assoc_data_elements']; $j++) {
215: $info['aac']['program_configs'][$i]['assoc_data_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
216: $bitoffset += 4;
217: }
218: for ($j = 0; $j < $info['aac']['program_configs'][$i]['num_valid_cc_elements']; $j++) {
219: $info['aac']['program_configs'][$i]['cc_element_is_ind_sw'][$j] = (bool) getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 1));
220: $bitoffset += 1;
221: $info['aac']['program_configs'][$i]['valid_cc_element_tag_select'][$j] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 4));
222: $bitoffset += 4;
223: }
224:
225: $bitoffset = ceil($bitoffset / 8) * 8;
226:
227: $info['aac']['program_configs'][$i]['comment_field_bytes'] = getid3_lib::Bin2Dec(substr($AACheaderBitstream, $bitoffset, 8));
228: $bitoffset += 8;
229: $info['aac']['program_configs'][$i]['comment_field'] = getid3_lib::Bin2String(substr($AACheaderBitstream, $bitoffset, 8 * $info['aac']['program_configs'][$i]['comment_field_bytes']));
230: $bitoffset += 8 * $info['aac']['program_configs'][$i]['comment_field_bytes'];
231:
232:
233: $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['program_configs'][$i]['object_type'], $info['aac']['header']['mpeg_version']);
234: $info['aac']['program_configs'][$i]['sampling_frequency'] = self::AACsampleRateLookup($info['aac']['program_configs'][$i]['sampling_frequency_index']);
235: $info['audio']['sample_rate'] = $info['aac']['program_configs'][$i]['sampling_frequency'];
236: $info['audio']['channels'] = self::AACchannelCountCalculate($info['aac']['program_configs'][$i]);
237: if ($info['aac']['program_configs'][$i]['comment_field']) {
238: $info['aac']['comments'][] = $info['aac']['program_configs'][$i]['comment_field'];
239: }
240: }
241: $info['playtime_seconds'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['audio']['bitrate'];
242:
243: $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
244:
245:
246:
247: return true;
248:
249: } else {
250:
251: unset($info['fileformat']);
252: unset($info['aac']);
253: $info['error'][] = 'AAC-ADIF synch not found at offset '.$info['avdataoffset'].' (expected "ADIF", found "'.substr($AACheader, 0, 4).'" instead)';
254: return false;
255:
256: }
257:
258: }
259:
260:
261: public function getAACADTSheaderFilepointer($MaxFramesToScan=1000000, $ReturnExtendedInfo=false) {
262: $info = &$this->getid3->info;
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
281:
282:
283:
284:
285:
286:
287:
288:
289:
290:
291:
292:
293:
294: $byteoffset = $info['avdataoffset'];
295: $framenumber = 0;
296:
297:
298: static $decbin = array();
299:
300:
301: for ($i = 0; $i < 256; $i++) {
302: $decbin[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
303: }
304:
305:
306: $BitrateCache = array();
307:
308:
309: while (true) {
310:
311:
312:
313: if (!getid3_lib::intValueSupported($byteoffset)) {
314: $info['warning'][] = 'Unable to parse AAC file beyond '.$this->ftell().' (PHP does not support file operations beyond '.round(PHP_INT_MAX / 1073741824).'GB)';
315: return false;
316: }
317: $this->fseek($byteoffset);
318:
319:
320: $substring = $this->fread(9);
321: $substringlength = strlen($substring);
322: if ($substringlength != 9) {
323: $info['error'][] = 'Failed to read 7 bytes at offset '.($this->ftell() - $substringlength).' (only read '.$substringlength.' bytes)';
324: return false;
325: }
326:
327: $header1 = getid3_lib::BigEndian2Int(substr($substring, 0, 2));
328: $header2 = getid3_lib::BigEndian2Int(substr($substring, 2, 4));
329: $header3 = getid3_lib::BigEndian2Int(substr($substring, 6, 1));
330:
331: $info['aac']['header']['raw']['syncword'] = ($header1 & 0xFFF0) >> 4;
332: if ($info['aac']['header']['raw']['syncword'] != 0x0FFF) {
333: $info['error'][] = 'Synch pattern (0x0FFF) not found at offset '.($this->ftell() - $substringlength).' (found 0x0'.strtoupper(dechex($info['aac']['header']['raw']['syncword'])).' instead)';
334:
335:
336:
337: unset($info['aac']);
338: return false;
339: }
340:
341:
342: if ($framenumber == 0) {
343: $info['aac']['header_type'] = 'ADTS';
344: $info['fileformat'] = 'aac';
345: $info['audio']['dataformat'] = 'aac';
346:
347: $info['aac']['header']['raw']['mpeg_version'] = ($header1 & 0x0008) >> 3;
348: $info['aac']['header']['raw']['mpeg_layer'] = ($header1 & 0x0006) >> 1;
349: $info['aac']['header']['raw']['protection_absent'] = ($header1 & 0x0001) >> 0;
350:
351: $info['aac']['header']['raw']['profile_code'] = ($header2 & 0xC0000000) >> 30;
352: $info['aac']['header']['raw']['sample_rate_code'] = ($header2 & 0x3C000000) >> 26;
353: $info['aac']['header']['raw']['private_stream'] = ($header2 & 0x02000000) >> 25;
354: $info['aac']['header']['raw']['channels_code'] = ($header2 & 0x01C00000) >> 22;
355: $info['aac']['header']['raw']['original'] = ($header2 & 0x00200000) >> 21;
356: $info['aac']['header']['raw']['home'] = ($header2 & 0x00100000) >> 20;
357: $info['aac']['header']['raw']['copyright_stream'] = ($header2 & 0x00080000) >> 19;
358: $info['aac']['header']['raw']['copyright_start'] = ($header2 & 0x00040000) >> 18;
359: $info['aac']['header']['raw']['frame_length'] = ($header2 & 0x0003FFE0) >> 5;
360:
361: $info['aac']['header']['mpeg_version'] = ($info['aac']['header']['raw']['mpeg_version'] ? 2 : 4);
362: $info['aac']['header']['crc_present'] = ($info['aac']['header']['raw']['protection_absent'] ? false: true);
363: $info['aac']['header']['profile'] = self::AACprofileLookup($info['aac']['header']['raw']['profile_code'], $info['aac']['header']['mpeg_version']);
364: $info['aac']['header']['sample_frequency'] = self::AACsampleRateLookup($info['aac']['header']['raw']['sample_rate_code']);
365: $info['aac']['header']['private'] = (bool) $info['aac']['header']['raw']['private_stream'];
366: $info['aac']['header']['original'] = (bool) $info['aac']['header']['raw']['original'];
367: $info['aac']['header']['home'] = (bool) $info['aac']['header']['raw']['home'];
368: $info['aac']['header']['channels'] = (($info['aac']['header']['raw']['channels_code'] == 7) ? 8 : $info['aac']['header']['raw']['channels_code']);
369: if ($ReturnExtendedInfo) {
370: $info['aac'][$framenumber]['copyright_id_bit'] = (bool) $info['aac']['header']['raw']['copyright_stream'];
371: $info['aac'][$framenumber]['copyright_id_start'] = (bool) $info['aac']['header']['raw']['copyright_start'];
372: }
373:
374: if ($info['aac']['header']['raw']['mpeg_layer'] != 0) {
375: $info['warning'][] = 'Layer error - expected "0", found "'.$info['aac']['header']['raw']['mpeg_layer'].'" instead';
376: }
377: if ($info['aac']['header']['sample_frequency'] == 0) {
378: $info['error'][] = 'Corrupt AAC file: sample_frequency == zero';
379: return false;
380: }
381:
382: $info['audio']['sample_rate'] = $info['aac']['header']['sample_frequency'];
383: $info['audio']['channels'] = $info['aac']['header']['channels'];
384: }
385:
386: $FrameLength = ($header2 & 0x0003FFE0) >> 5;
387:
388: if (!isset($BitrateCache[$FrameLength])) {
389: $BitrateCache[$FrameLength] = ($info['aac']['header']['sample_frequency'] / 1024) * $FrameLength * 8;
390: }
391: getid3_lib::safe_inc($info['aac']['bitrate_distribution'][$BitrateCache[$FrameLength]], 1);
392:
393: $info['aac'][$framenumber]['aac_frame_length'] = $FrameLength;
394:
395: $info['aac'][$framenumber]['adts_buffer_fullness'] = (($header2 & 0x0000001F) << 6) & (($header3 & 0xFC) >> 2);
396: if ($info['aac'][$framenumber]['adts_buffer_fullness'] == 0x07FF) {
397: $info['audio']['bitrate_mode'] = 'vbr';
398: } else {
399: $info['audio']['bitrate_mode'] = 'cbr';
400: }
401: $info['aac'][$framenumber]['num_raw_data_blocks'] = (($header3 & 0x03) >> 0);
402:
403: if ($info['aac']['header']['crc_present']) {
404:
405: }
406:
407: if (!$ReturnExtendedInfo) {
408: unset($info['aac'][$framenumber]);
409: }
410:
411: 412: 413: 414: 415: 416: 417: 418: 419:
420:
421: $byteoffset += $FrameLength;
422: if ((++$framenumber < $MaxFramesToScan) && (($byteoffset + 10) < $info['avdataend'])) {
423:
424:
425:
426: } else {
427:
428: $info['aac']['frames'] = $framenumber;
429: $info['playtime_seconds'] = ($info['avdataend'] / $byteoffset) * (($framenumber * 1024) / $info['aac']['header']['sample_frequency']);
430: if ($info['playtime_seconds'] == 0) {
431: $info['error'][] = 'Corrupt AAC file: playtime_seconds == zero';
432: return false;
433: }
434: $info['audio']['bitrate'] = (($info['avdataend'] - $info['avdataoffset']) * 8) / $info['playtime_seconds'];
435: ksort($info['aac']['bitrate_distribution']);
436:
437: $info['audio']['encoder_options'] = $info['aac']['header_type'].' '.$info['aac']['header']['profile'];
438:
439: return true;
440:
441: }
442: }
443:
444: }
445:
446: public static function AACsampleRateLookup($samplerateid) {
447: static $AACsampleRateLookup = array();
448: if (empty($AACsampleRateLookup)) {
449: $AACsampleRateLookup[0] = 96000;
450: $AACsampleRateLookup[1] = 88200;
451: $AACsampleRateLookup[2] = 64000;
452: $AACsampleRateLookup[3] = 48000;
453: $AACsampleRateLookup[4] = 44100;
454: $AACsampleRateLookup[5] = 32000;
455: $AACsampleRateLookup[6] = 24000;
456: $AACsampleRateLookup[7] = 22050;
457: $AACsampleRateLookup[8] = 16000;
458: $AACsampleRateLookup[9] = 12000;
459: $AACsampleRateLookup[10] = 11025;
460: $AACsampleRateLookup[11] = 8000;
461: $AACsampleRateLookup[12] = 0;
462: $AACsampleRateLookup[13] = 0;
463: $AACsampleRateLookup[14] = 0;
464: $AACsampleRateLookup[15] = 0;
465: }
466: return (isset($AACsampleRateLookup[$samplerateid]) ? $AACsampleRateLookup[$samplerateid] : 'invalid');
467: }
468:
469: public static function AACprofileLookup($profileid, $mpegversion) {
470: static $AACprofileLookup = array();
471: if (empty($AACprofileLookup)) {
472: $AACprofileLookup[2][0] = 'Main profile';
473: $AACprofileLookup[2][1] = 'Low Complexity profile (LC)';
474: $AACprofileLookup[2][2] = 'Scalable Sample Rate profile (SSR)';
475: $AACprofileLookup[2][3] = '(reserved)';
476: $AACprofileLookup[4][0] = 'AAC_MAIN';
477: $AACprofileLookup[4][1] = 'AAC_LC';
478: $AACprofileLookup[4][2] = 'AAC_SSR';
479: $AACprofileLookup[4][3] = 'AAC_LTP';
480: }
481: return (isset($AACprofileLookup[$mpegversion][$profileid]) ? $AACprofileLookup[$mpegversion][$profileid] : 'invalid');
482: }
483:
484: public static function AACchannelCountCalculate($program_configs) {
485: $channels = 0;
486: for ($i = 0; $i < $program_configs['num_front_channel_elements']; $i++) {
487: $channels++;
488: if ($program_configs['front_element_is_cpe'][$i]) {
489:
490: $channels++;
491: }
492: }
493: for ($i = 0; $i < $program_configs['num_side_channel_elements']; $i++) {
494: $channels++;
495: if ($program_configs['side_element_is_cpe'][$i]) {
496:
497: $channels++;
498: }
499: }
500: for ($i = 0; $i < $program_configs['num_back_channel_elements']; $i++) {
501: $channels++;
502: if ($program_configs['back_element_is_cpe'][$i]) {
503:
504: $channels++;
505: }
506: }
507: for ($i = 0; $i < $program_configs['num_lfe_channel_elements']; $i++) {
508: $channels++;
509: }
510: return $channels;
511: }
512:
513: }
514: