rlm@3
|
1 <?php
|
rlm@3
|
2 // +----------------------------------------------------------------------+
|
rlm@3
|
3 // | PHP version 5 |
|
rlm@3
|
4 // +----------------------------------------------------------------------+
|
rlm@3
|
5 // | Copyright (c) 2002-2006 James Heinrich, Allan Hansen |
|
rlm@3
|
6 // +----------------------------------------------------------------------+
|
rlm@3
|
7 // | This source file is subject to version 2 of the GPL license, |
|
rlm@3
|
8 // | that is bundled with this package in the file license.txt and is |
|
rlm@3
|
9 // | available through the world-wide-web at the following url: |
|
rlm@3
|
10 // | http://www.gnu.org/copyleft/gpl.html |
|
rlm@3
|
11 // +----------------------------------------------------------------------+
|
rlm@3
|
12 // | getID3() - http://getid3.sourceforge.net or http://www.getid3.org |
|
rlm@3
|
13 // +----------------------------------------------------------------------+
|
rlm@3
|
14 // | Authors: James Heinrich <infoØgetid3*org> |
|
rlm@3
|
15 // | Allan Hansen <ahØartemis*dk> |
|
rlm@3
|
16 // +----------------------------------------------------------------------+
|
rlm@3
|
17 // | module.audio.aac_adts.php |
|
rlm@3
|
18 // | Module for analyzing AAC files with ADTS header. |
|
rlm@3
|
19 // | dependencies: NONE |
|
rlm@3
|
20 // +----------------------------------------------------------------------+
|
rlm@3
|
21 //
|
rlm@3
|
22 // $Id: module.audio.aac_adts.php,v 1.4 2006/11/02 10:48:01 ah Exp $
|
rlm@3
|
23
|
rlm@3
|
24
|
rlm@3
|
25
|
rlm@3
|
26 class getid3_aac_adts extends getid3_handler
|
rlm@3
|
27 {
|
rlm@3
|
28
|
rlm@3
|
29 public $option_max_frames_to_scan = 1000000;
|
rlm@3
|
30 public $option_return_extended_info = false;
|
rlm@3
|
31
|
rlm@3
|
32
|
rlm@3
|
33 private $decbin_cache;
|
rlm@3
|
34 private $bitrate_cache;
|
rlm@3
|
35
|
rlm@3
|
36
|
rlm@3
|
37
|
rlm@3
|
38 public function __construct(getID3 $getid3) {
|
rlm@3
|
39
|
rlm@3
|
40 parent::__construct($getid3);
|
rlm@3
|
41
|
rlm@3
|
42 // Populate bindec_cache
|
rlm@3
|
43 for ($i = 0; $i < 256; $i++) {
|
rlm@3
|
44 $this->decbin_cache[chr($i)] = str_pad(decbin($i), 8, '0', STR_PAD_LEFT);
|
rlm@3
|
45 }
|
rlm@3
|
46
|
rlm@3
|
47 // Init cache
|
rlm@3
|
48 $this->bitrate_cache = array ();
|
rlm@3
|
49
|
rlm@3
|
50 // Fast scanning?
|
rlm@3
|
51 if (!$getid3->option_accurate_results) {
|
rlm@3
|
52 $this->option_max_frames_to_scan = 200;
|
rlm@3
|
53 $getid3->warning('option_accurate_results set to false - bitrate and playing time are not accurate.');
|
rlm@3
|
54 }
|
rlm@3
|
55 }
|
rlm@3
|
56
|
rlm@3
|
57
|
rlm@3
|
58
|
rlm@3
|
59 public function Analyze() {
|
rlm@3
|
60
|
rlm@3
|
61 $getid3 = $this->getid3;
|
rlm@3
|
62
|
rlm@3
|
63 // based loosely on code from AACfile by Jurgen Faul <jfaulØgmx.de>
|
rlm@3
|
64 // http://jfaul.de/atl or http://j-faul.virtualave.net/atl/atl.html
|
rlm@3
|
65
|
rlm@3
|
66
|
rlm@3
|
67 // http://faac.sourceforge.net/wiki/index.php?page=ADTS
|
rlm@3
|
68
|
rlm@3
|
69 // * ADTS Fixed Header: these don't change from frame to frame
|
rlm@3
|
70 // syncword 12 always: '111111111111'
|
rlm@3
|
71 // ID 1 0: MPEG-4, 1: MPEG-2
|
rlm@3
|
72 // layer 2 always: '00'
|
rlm@3
|
73 // protection_absent 1
|
rlm@3
|
74 // profile 2
|
rlm@3
|
75 // sampling_frequency_index 4
|
rlm@3
|
76 // private_bit 1
|
rlm@3
|
77 // channel_configuration 3
|
rlm@3
|
78 // original/copy 1
|
rlm@3
|
79 // home 1
|
rlm@3
|
80 // emphasis 2 only if ID == 0 (ie MPEG-4)
|
rlm@3
|
81
|
rlm@3
|
82 // * ADTS Variable Header: these can change from frame to frame
|
rlm@3
|
83 // copyright_identification_bit 1
|
rlm@3
|
84 // copyright_identification_start 1
|
rlm@3
|
85 // aac_frame_length 13 length of the frame including header (in bytes)
|
rlm@3
|
86 // adts_buffer_fullness 11 0x7FF indicates VBR
|
rlm@3
|
87 // no_raw_data_blocks_in_frame 2
|
rlm@3
|
88
|
rlm@3
|
89 // * ADTS Error check
|
rlm@3
|
90 // crc_check 16 only if protection_absent == 0
|
rlm@3
|
91
|
rlm@3
|
92 $getid3->info['aac']['header'] = array () ;
|
rlm@3
|
93 $info_aac = &$getid3->info['aac'];
|
rlm@3
|
94 $info_aac_header = & $info_aac['header'];
|
rlm@3
|
95
|
rlm@3
|
96 $byte_offset = $frame_number = 0;
|
rlm@3
|
97
|
rlm@3
|
98 while (true) {
|
rlm@3
|
99
|
rlm@3
|
100 // Breaks out when end-of-file encountered, or invalid data found,
|
rlm@3
|
101 // or MaxFramesToScan frames have been scanned
|
rlm@3
|
102
|
rlm@3
|
103 fseek($getid3->fp, $byte_offset, SEEK_SET);
|
rlm@3
|
104
|
rlm@3
|
105 // First get substring
|
rlm@3
|
106 $sub_string = fread($getid3->fp, 10);
|
rlm@3
|
107 $sub_string_length = strlen($sub_string);
|
rlm@3
|
108 if ($sub_string_length != 10) {
|
rlm@3
|
109 throw new getid3_exception('Failed to read 10 bytes at offset '.(ftell($getid3->fp) - $sub_string_length).' (only read '.$sub_string_length.' bytes)');
|
rlm@3
|
110 }
|
rlm@3
|
111
|
rlm@3
|
112 // Initialise $aac_header_bitstream
|
rlm@3
|
113 $aac_header_bitstream = '';
|
rlm@3
|
114
|
rlm@3
|
115 // Loop thru substring chars
|
rlm@3
|
116 for ($i = 0; $i < 10; $i++) {
|
rlm@3
|
117 $aac_header_bitstream .= $this->decbin_cache[$sub_string[$i]];
|
rlm@3
|
118 }
|
rlm@3
|
119
|
rlm@3
|
120 $sync_test = bindec(substr($aac_header_bitstream, 0, 12));
|
rlm@3
|
121 $bit_offset = 12;
|
rlm@3
|
122
|
rlm@3
|
123 if ($sync_test != 0x0FFF) {
|
rlm@3
|
124 throw new getid3_exception('Synch pattern (0x0FFF) not found at offset '.(ftell($getid3->fp) - 10).' (found 0x0'.strtoupper(dechex($sync_test)).' instead)');
|
rlm@3
|
125 }
|
rlm@3
|
126
|
rlm@3
|
127 // Only gather info once - this takes time to do 1000 times!
|
rlm@3
|
128 if ($frame_number > 0) {
|
rlm@3
|
129
|
rlm@3
|
130 // MPEG-4: 20, // MPEG-2: 18
|
rlm@3
|
131 $bit_offset += $aac_header_bitstream[$bit_offset] ? 18 : 20;
|
rlm@3
|
132 }
|
rlm@3
|
133
|
rlm@3
|
134 // Gather info for first frame only - this takes time to do 1000 times!
|
rlm@3
|
135 else {
|
rlm@3
|
136
|
rlm@3
|
137 $info_aac['header_type'] = 'ADTS';
|
rlm@3
|
138 $info_aac_header['synch'] = $sync_test;
|
rlm@3
|
139 $getid3->info['fileformat'] = 'aac';
|
rlm@3
|
140 $getid3->info['audio']['dataformat'] = 'aac';
|
rlm@3
|
141
|
rlm@3
|
142 $info_aac_header['mpeg_version'] = $aac_header_bitstream{$bit_offset++} == '0' ? 4 : 2;
|
rlm@3
|
143 $info_aac_header['layer'] = bindec(substr($aac_header_bitstream, $bit_offset, 2));
|
rlm@3
|
144 $bit_offset += 2;
|
rlm@3
|
145
|
rlm@3
|
146 if ($info_aac_header['layer'] != 0) {
|
rlm@3
|
147 throw new getid3_exception('Layer error - expected 0x00, found 0x'.dechex($info_aac_header['layer']).' instead');
|
rlm@3
|
148 }
|
rlm@3
|
149
|
rlm@3
|
150 $info_aac_header['crc_present'] = $aac_header_bitstream{$bit_offset++} == '0' ? true : false;
|
rlm@3
|
151
|
rlm@3
|
152 $info_aac_header['profile_id'] = bindec(substr($aac_header_bitstream, $bit_offset, 2));
|
rlm@3
|
153 $bit_offset += 2;
|
rlm@3
|
154
|
rlm@3
|
155 $info_aac_header['profile_text'] = getid3_aac_adts::AACprofileLookup($info_aac_header['profile_id'], $info_aac_header['mpeg_version']);
|
rlm@3
|
156
|
rlm@3
|
157 $info_aac_header['sample_frequency_index'] = bindec(substr($aac_header_bitstream, $bit_offset, 4));
|
rlm@3
|
158 $bit_offset += 4;
|
rlm@3
|
159
|
rlm@3
|
160 $info_aac_header['sample_frequency'] = getid3_aac_adts::AACsampleRateLookup($info_aac_header['sample_frequency_index']);
|
rlm@3
|
161
|
rlm@3
|
162 $getid3->info['audio']['sample_rate'] = $info_aac_header['sample_frequency'];
|
rlm@3
|
163
|
rlm@3
|
164 $info_aac_header['private'] = $aac_header_bitstream{$bit_offset++} == 1;
|
rlm@3
|
165
|
rlm@3
|
166 $info_aac_header['channel_configuration'] = $getid3->info['audio']['channels'] = bindec(substr($aac_header_bitstream, $bit_offset, 3));
|
rlm@3
|
167 $bit_offset += 3;
|
rlm@3
|
168
|
rlm@3
|
169 $info_aac_header['original'] = $aac_header_bitstream{$bit_offset++} == 1;
|
rlm@3
|
170 $info_aac_header['home'] = $aac_header_bitstream{$bit_offset++} == 1;
|
rlm@3
|
171
|
rlm@3
|
172 if ($info_aac_header['mpeg_version'] == 4) {
|
rlm@3
|
173 $info_aac_header['emphasis'] = bindec(substr($aac_header_bitstream, $bit_offset, 2));
|
rlm@3
|
174 $bit_offset += 2;
|
rlm@3
|
175 }
|
rlm@3
|
176
|
rlm@3
|
177 if ($this->option_return_extended_info) {
|
rlm@3
|
178
|
rlm@3
|
179 $info_aac[$frame_number]['copyright_id_bit'] = $aac_header_bitstream{$bit_offset++} == 1;
|
rlm@3
|
180 $info_aac[$frame_number]['copyright_id_start'] = $aac_header_bitstream{$bit_offset++} == 1;
|
rlm@3
|
181
|
rlm@3
|
182 } else {
|
rlm@3
|
183 $bit_offset += 2;
|
rlm@3
|
184 }
|
rlm@3
|
185 }
|
rlm@3
|
186
|
rlm@3
|
187 $frame_length = bindec(substr($aac_header_bitstream, $bit_offset, 13));
|
rlm@3
|
188
|
rlm@3
|
189 if (!isset($this->bitrate_cache[$frame_length])) {
|
rlm@3
|
190 $this->bitrate_cache[$frame_length] = ($info_aac_header['sample_frequency'] / 1024) * $frame_length * 8;
|
rlm@3
|
191 }
|
rlm@3
|
192 @$info_aac['bitrate_distribution'][$this->bitrate_cache[$frame_length]]++;
|
rlm@3
|
193
|
rlm@3
|
194 $info_aac[$frame_number]['aac_frame_length'] = $frame_length;
|
rlm@3
|
195 $bit_offset += 13;
|
rlm@3
|
196
|
rlm@3
|
197 $info_aac[$frame_number]['adts_buffer_fullness'] = bindec(substr($aac_header_bitstream, $bit_offset, 11));
|
rlm@3
|
198 $bit_offset += 11;
|
rlm@3
|
199
|
rlm@3
|
200 $getid3->info['audio']['bitrate_mode'] = ($info_aac[$frame_number]['adts_buffer_fullness'] == 0x07FF) ? 'vbr' : 'cbr';
|
rlm@3
|
201
|
rlm@3
|
202 $info_aac[$frame_number]['num_raw_data_blocks'] = bindec(substr($aac_header_bitstream, $bit_offset, 2));
|
rlm@3
|
203 $bit_offset += 2;
|
rlm@3
|
204
|
rlm@3
|
205 if ($info_aac_header['crc_present']) {
|
rlm@3
|
206 $bit_offset += 16;
|
rlm@3
|
207 }
|
rlm@3
|
208
|
rlm@3
|
209 if (!$this->option_return_extended_info) {
|
rlm@3
|
210 unset($info_aac[$frame_number]);
|
rlm@3
|
211 }
|
rlm@3
|
212
|
rlm@3
|
213 $byte_offset += $frame_length;
|
rlm@3
|
214 if ((++$frame_number < $this->option_max_frames_to_scan) && (($byte_offset + 10) < $getid3->info['avdataend'])) {
|
rlm@3
|
215
|
rlm@3
|
216 // keep scanning
|
rlm@3
|
217
|
rlm@3
|
218 } else {
|
rlm@3
|
219
|
rlm@3
|
220 $info_aac['frames'] = $frame_number;
|
rlm@3
|
221 $getid3->info['playtime_seconds'] = ($getid3->info['avdataend'] / $byte_offset) * (($frame_number * 1024) / $info_aac_header['sample_frequency']); // (1 / % of file scanned) * (samples / (samples/sec)) = seconds
|
rlm@3
|
222 $getid3->info['audio']['bitrate'] = (($getid3->info['avdataend'] - $getid3->info['avdataoffset']) * 8) / $getid3->info['playtime_seconds'];
|
rlm@3
|
223 ksort($info_aac['bitrate_distribution']);
|
rlm@3
|
224
|
rlm@3
|
225 $getid3->info['audio']['encoder_options'] = $info_aac['header_type'].' '.$info_aac_header['profile_text'];
|
rlm@3
|
226
|
rlm@3
|
227 return true;
|
rlm@3
|
228 }
|
rlm@3
|
229 }
|
rlm@3
|
230 }
|
rlm@3
|
231
|
rlm@3
|
232
|
rlm@3
|
233
|
rlm@3
|
234 public static function AACsampleRateLookup($samplerate_id) {
|
rlm@3
|
235
|
rlm@3
|
236 static $lookup = array (
|
rlm@3
|
237 0 => 96000,
|
rlm@3
|
238 1 => 88200,
|
rlm@3
|
239 2 => 64000,
|
rlm@3
|
240 3 => 48000,
|
rlm@3
|
241 4 => 44100,
|
rlm@3
|
242 5 => 32000,
|
rlm@3
|
243 6 => 24000,
|
rlm@3
|
244 7 => 22050,
|
rlm@3
|
245 8 => 16000,
|
rlm@3
|
246 9 => 12000,
|
rlm@3
|
247 10 => 11025,
|
rlm@3
|
248 11 => 8000,
|
rlm@3
|
249 12 => 0,
|
rlm@3
|
250 13 => 0,
|
rlm@3
|
251 14 => 0,
|
rlm@3
|
252 15 => 0
|
rlm@3
|
253 );
|
rlm@3
|
254 return (isset($lookup[$samplerate_id]) ? $lookup[$samplerate_id] : 'invalid');
|
rlm@3
|
255 }
|
rlm@3
|
256
|
rlm@3
|
257
|
rlm@3
|
258
|
rlm@3
|
259 public static function AACprofileLookup($profile_id, $mpeg_version) {
|
rlm@3
|
260
|
rlm@3
|
261 static $lookup = array (
|
rlm@3
|
262 2 => array (
|
rlm@3
|
263 0 => 'Main profile',
|
rlm@3
|
264 1 => 'Low Complexity profile (LC)',
|
rlm@3
|
265 2 => 'Scalable Sample Rate profile (SSR)',
|
rlm@3
|
266 3 => '(reserved)'
|
rlm@3
|
267 ),
|
rlm@3
|
268 4 => array (
|
rlm@3
|
269 0 => 'AAC_MAIN',
|
rlm@3
|
270 1 => 'AAC_LC',
|
rlm@3
|
271 2 => 'AAC_SSR',
|
rlm@3
|
272 3 => 'AAC_LTP'
|
rlm@3
|
273 )
|
rlm@3
|
274 );
|
rlm@3
|
275 return (isset($lookup[$mpeg_version][$profile_id]) ? $lookup[$mpeg_version][$profile_id] : 'invalid');
|
rlm@3
|
276 }
|
rlm@3
|
277
|
rlm@3
|
278
|
rlm@3
|
279 }
|
rlm@3
|
280
|
rlm@3
|
281
|
rlm@3
|
282 ?> |