line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
/* |
2
|
|
|
|
|
|
|
* This program is free software; you can redistribute it and/or modify |
3
|
|
|
|
|
|
|
* it under the terms of the GNU General Public License as published by |
4
|
|
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
5
|
|
|
|
|
|
|
* (at your option) any later version. |
6
|
|
|
|
|
|
|
* |
7
|
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful, |
8
|
|
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
9
|
|
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10
|
|
|
|
|
|
|
* GNU General Public License for more details. |
11
|
|
|
|
|
|
|
* |
12
|
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License |
13
|
|
|
|
|
|
|
* along with this program; if not, write to the Free Software |
14
|
|
|
|
|
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
15
|
|
|
|
|
|
|
*/ |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
/* |
18
|
|
|
|
|
|
|
* This file is derived from mt-daap project. |
19
|
|
|
|
|
|
|
*/ |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
#include "mp3.h" |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
int |
24
|
82
|
|
|
|
|
|
get_mp3fileinfo(PerlIO *infile, char *file, HV *info) |
25
|
|
|
|
|
|
|
{ |
26
|
82
|
|
|
|
|
|
mp3info *mp3 = _mp3_parse(infile, file, info); |
27
|
|
|
|
|
|
|
|
28
|
82
|
|
|
|
|
|
buffer_free(mp3->buf); |
29
|
82
|
|
|
|
|
|
Safefree(mp3->buf); |
30
|
82
|
|
|
|
|
|
Safefree(mp3->first_frame); |
31
|
82
|
|
|
|
|
|
Safefree(mp3->xing_frame); |
32
|
82
|
|
|
|
|
|
Safefree(mp3); |
33
|
|
|
|
|
|
|
|
34
|
82
|
|
|
|
|
|
return 0; |
35
|
|
|
|
|
|
|
} |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
int |
38
|
89
|
|
|
|
|
|
get_mp3tags(PerlIO *infile, char *file, HV *info, HV *tags) |
39
|
|
|
|
|
|
|
{ |
40
|
|
|
|
|
|
|
int ret; |
41
|
|
|
|
|
|
|
|
42
|
89
|
|
|
|
|
|
off_t file_size = _file_size(infile); |
43
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
// See if this file has an APE tag as fast as possible |
45
|
|
|
|
|
|
|
// This is still a big performance hit :( |
46
|
89
|
100
|
|
|
|
|
if ( _has_ape(infile, file_size, info) ) { |
47
|
6
|
|
|
|
|
|
get_ape_metadata(infile, file, info, tags); |
48
|
|
|
|
|
|
|
} |
49
|
|
|
|
|
|
|
|
50
|
89
|
|
|
|
|
|
ret = parse_id3(infile, file, info, tags, 0, file_size); |
51
|
|
|
|
|
|
|
|
52
|
89
|
|
|
|
|
|
return ret; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
int |
56
|
175
|
|
|
|
|
|
_is_ape_header(char *bptr) |
57
|
|
|
|
|
|
|
{ |
58
|
175
|
100
|
|
|
|
|
if ( bptr[0] == 'A' && bptr[1] == 'P' && bptr[2] == 'E' |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
59
|
6
|
50
|
|
|
|
|
&& bptr[3] == 'T' && bptr[4] == 'A' && bptr[5] == 'G' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
60
|
6
|
50
|
|
|
|
|
&& bptr[6] == 'E' && bptr[7] == 'X' |
|
|
50
|
|
|
|
|
|
61
|
|
|
|
|
|
|
) { |
62
|
6
|
|
|
|
|
|
return 1; |
63
|
|
|
|
|
|
|
} |
64
|
|
|
|
|
|
|
|
65
|
169
|
|
|
|
|
|
return 0; |
66
|
|
|
|
|
|
|
} |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
int |
69
|
89
|
|
|
|
|
|
_has_ape(PerlIO *infile, off_t file_size, HV *info) |
70
|
|
|
|
|
|
|
{ |
71
|
|
|
|
|
|
|
Buffer buf; |
72
|
89
|
|
|
|
|
|
uint8_t ret = 0; |
73
|
|
|
|
|
|
|
char *bptr; |
74
|
|
|
|
|
|
|
|
75
|
89
|
50
|
|
|
|
|
if ( (PerlIO_seek(infile, file_size - 160, SEEK_SET)) == -1 ) { |
76
|
0
|
|
|
|
|
|
return 0; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
DEBUG_TRACE("Seeked to %d looking for APE tag\n", (int)PerlIO_tell(infile)); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
// Bug 9942, read 136 bytes so we can check at -32 bytes in case file |
82
|
|
|
|
|
|
|
// does not have an ID3v1 tag |
83
|
89
|
|
|
|
|
|
buffer_init(&buf, 136); |
84
|
89
|
50
|
|
|
|
|
if ( !_check_buf(infile, &buf, 136, 136) ) { |
85
|
0
|
|
|
|
|
|
goto out; |
86
|
|
|
|
|
|
|
} |
87
|
|
|
|
|
|
|
|
88
|
89
|
|
|
|
|
|
bptr = buffer_ptr(&buf); |
89
|
|
|
|
|
|
|
|
90
|
89
|
100
|
|
|
|
|
if ( _is_ape_header(bptr) ) { |
91
|
|
|
|
|
|
|
DEBUG_TRACE("APE tag found at -160 (with ID3v1)\n"); |
92
|
3
|
|
|
|
|
|
ret = 1; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
else { |
95
|
|
|
|
|
|
|
// Look for Lyrics tag which may possibly be between APE and ID3v1 |
96
|
86
|
|
|
|
|
|
bptr += 23; |
97
|
86
|
100
|
|
|
|
|
if ( bptr[0] == 'L' && bptr[1] == 'Y' && bptr[2] == 'R' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
98
|
1
|
50
|
|
|
|
|
&& bptr[3] == 'I' && bptr[4] == 'C' && bptr[5] == 'S' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
99
|
1
|
50
|
|
|
|
|
&& bptr[6] == '2' && bptr[7] == '0' && bptr[8] == '0' |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
100
|
|
|
|
|
|
|
) { |
101
|
|
|
|
|
|
|
// read Lyrics tag size, stored as a 6-digit number (!?) |
102
|
|
|
|
|
|
|
// http://www.id3.org/Lyrics3v2 |
103
|
1
|
|
|
|
|
|
uint32_t lyrics_size = 0; |
104
|
1
|
|
|
|
|
|
off_t file_size = _file_size(infile); |
105
|
|
|
|
|
|
|
|
106
|
1
|
|
|
|
|
|
bptr -= 6; |
107
|
1
|
|
|
|
|
|
lyrics_size = atoi(bptr); |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
DEBUG_TRACE("LYRICS200 tag found (size %d), adjusting APE offset (%d)\n", lyrics_size, -(160 + lyrics_size + 15)); |
110
|
|
|
|
|
|
|
|
111
|
1
|
50
|
|
|
|
|
if ( (PerlIO_seek(infile, file_size - (160 + lyrics_size + 15), SEEK_SET)) == -1 ) { |
112
|
0
|
|
|
|
|
|
goto out; |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
DEBUG_TRACE("Seeked before Lyrics tag to %d\n", (int)PerlIO_tell(infile)); |
116
|
|
|
|
|
|
|
|
117
|
1
|
|
|
|
|
|
buffer_clear(&buf); |
118
|
1
|
50
|
|
|
|
|
if ( !_check_buf(infile, &buf, 136, 136) ) { |
119
|
0
|
|
|
|
|
|
goto out; |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
|
122
|
1
|
50
|
|
|
|
|
if ( _is_ape_header( buffer_ptr(&buf) ) ) { |
123
|
|
|
|
|
|
|
DEBUG_TRACE("APE tag found at %d (ID3v1 + Lyricsv2)\n", -(160 + lyrics_size + 15)); |
124
|
1
|
|
|
|
|
|
ret = 1; |
125
|
1
|
|
|
|
|
|
goto out; |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
// APE code will remove the lyrics_size from audio_size, but if no APE tag do it here |
129
|
0
|
0
|
|
|
|
|
if (my_hv_exists(info, "audio_size")) { |
130
|
0
|
0
|
|
|
|
|
int audio_size = SvIV(*(my_hv_fetch(info, "audio_size"))); |
131
|
0
|
|
|
|
|
|
my_hv_store(info, "audio_size", newSVuv(audio_size - lyrics_size - 15)); |
132
|
|
|
|
|
|
|
DEBUG_TRACE("Reduced audio_size value by Lyrics2 tag size %d\n", lyrics_size + 15); |
133
|
|
|
|
|
|
|
} |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
// APE tag without ID3v1 tag will be -32 bytes from end |
137
|
85
|
|
|
|
|
|
buffer_consume(&buf, 128); |
138
|
|
|
|
|
|
|
|
139
|
85
|
|
|
|
|
|
bptr = buffer_ptr(&buf); |
140
|
|
|
|
|
|
|
|
141
|
85
|
100
|
|
|
|
|
if ( _is_ape_header(bptr) ) { |
142
|
|
|
|
|
|
|
DEBUG_TRACE("APE tag found at -32 (no ID3v1)\n"); |
143
|
2
|
|
|
|
|
|
ret = 1; |
144
|
|
|
|
|
|
|
} |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
out: |
148
|
89
|
|
|
|
|
|
buffer_free(&buf); |
149
|
|
|
|
|
|
|
|
150
|
89
|
|
|
|
|
|
return ret; |
151
|
|
|
|
|
|
|
} |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
// _decode_mp3_frame, based on pcutmp3 FrameHeader.decode() |
154
|
|
|
|
|
|
|
int |
155
|
1656
|
|
|
|
|
|
_decode_mp3_frame(unsigned char *bptr, struct mp3frame *frame) |
156
|
|
|
|
|
|
|
{ |
157
|
|
|
|
|
|
|
int i; |
158
|
|
|
|
|
|
|
|
159
|
1656
|
|
|
|
|
|
frame->header32 = GET_INT32BE(bptr); |
160
|
|
|
|
|
|
|
|
161
|
1656
|
|
|
|
|
|
frame->mpegID = (frame->header32 >> 19) & 3; |
162
|
1656
|
|
|
|
|
|
frame->layerID = (frame->header32 >> 17) & 3; |
163
|
1656
|
|
|
|
|
|
frame->crc16_used = (frame->header32 & 0x00010000) == 0; |
164
|
1656
|
|
|
|
|
|
frame->bitrate_index = (frame->header32 >> 12) & 0xF; |
165
|
1656
|
|
|
|
|
|
frame->samplingrate_index = (frame->header32 >> 10) & 3; |
166
|
1656
|
|
|
|
|
|
frame->padding = (frame->header32 & 0x00000200) != 0; |
167
|
1656
|
|
|
|
|
|
frame->private_bit_set = (frame->header32 & 0x00000100) != 0; |
168
|
1656
|
|
|
|
|
|
frame->mode = (frame->header32 >> 6) & 3; |
169
|
1656
|
|
|
|
|
|
frame->mode_extension = (frame->header32 >> 4) & 3; |
170
|
1656
|
|
|
|
|
|
frame->copyrighted = (frame->header32 & 0x00000008) != 0; |
171
|
1656
|
|
|
|
|
|
frame->original = (frame->header32 & 0x00000004) == 0; // bit set -> copy |
172
|
1656
|
|
|
|
|
|
frame->emphasis = frame->header32 & 3; |
173
|
|
|
|
|
|
|
|
174
|
3312
|
|
|
|
|
|
frame->valid = (frame->mpegID != ILLEGAL_MPEG_ID) |
175
|
1652
|
100
|
|
|
|
|
&& (frame->layerID != ILLEGAL_LAYER_ID) |
176
|
1620
|
100
|
|
|
|
|
&& (frame->bitrate_index != 0) |
177
|
1617
|
100
|
|
|
|
|
&& (frame->bitrate_index != 15) |
178
|
3308
|
100
|
|
|
|
|
&& (frame->samplingrate_index != ILLEGAL_SR); |
|
|
100
|
|
|
|
|
|
179
|
|
|
|
|
|
|
|
180
|
1656
|
100
|
|
|
|
|
if (!frame->valid) { |
181
|
74
|
|
|
|
|
|
return -1; |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
1582
|
|
|
|
|
|
frame->samplerate = sample_rate_tbl[ frame->samplingrate_index ]; |
185
|
|
|
|
|
|
|
|
186
|
1582
|
100
|
|
|
|
|
if (frame->mpegID == MPEG2_ID) |
187
|
114
|
|
|
|
|
|
frame->samplerate >>= 1; // 16,22,48 kHz |
188
|
|
|
|
|
|
|
|
189
|
1582
|
100
|
|
|
|
|
if (frame->mpegID == MPEG25_ID) |
190
|
35
|
|
|
|
|
|
frame->samplerate >>= 2; // 8,11,24 kHz |
191
|
|
|
|
|
|
|
|
192
|
1582
|
100
|
|
|
|
|
frame->channels = (frame->mode == MODE_MONO) ? 1 : 2; |
193
|
|
|
|
|
|
|
|
194
|
1582
|
|
|
|
|
|
frame->bitrate_kbps = bitrate_map[ frame->mpegID ][ frame->layerID ][ frame->bitrate_index ]; |
195
|
|
|
|
|
|
|
|
196
|
1582
|
100
|
|
|
|
|
if (frame->layerID == LAYER1_ID) { |
197
|
|
|
|
|
|
|
// layer 1: always 384 samples/frame and 4byte-slots |
198
|
5
|
|
|
|
|
|
frame->samples_per_frame = 384; |
199
|
5
|
|
|
|
|
|
frame->bytes_per_slot = 4; |
200
|
|
|
|
|
|
|
} |
201
|
|
|
|
|
|
|
else { |
202
|
|
|
|
|
|
|
// layer 2: always 1152 samples/frame |
203
|
|
|
|
|
|
|
// layer 3: MPEG1: 1152 samples/frame, MPEG2/2.5: 576 samples/frame |
204
|
1577
|
100
|
|
|
|
|
frame->samples_per_frame = ((frame->mpegID == MPEG1_ID) || (frame->layerID == LAYER2_ID)) ? 1152 : 576; |
|
|
100
|
|
|
|
|
|
205
|
1577
|
|
|
|
|
|
frame->bytes_per_slot = 1; |
206
|
|
|
|
|
|
|
} |
207
|
|
|
|
|
|
|
|
208
|
1582
|
|
|
|
|
|
frame->frame_size = ((frame->bitrate_kbps * 125) * frame->samples_per_frame) / frame->samplerate; |
209
|
|
|
|
|
|
|
|
210
|
1582
|
100
|
|
|
|
|
if (frame->bytes_per_slot > 1) |
211
|
5
|
|
|
|
|
|
frame->frame_size -= frame->frame_size % frame->bytes_per_slot; |
212
|
|
|
|
|
|
|
|
213
|
1582
|
100
|
|
|
|
|
if (frame->padding) |
214
|
176
|
|
|
|
|
|
frame->frame_size += frame->bytes_per_slot; |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
DEBUG_TRACE("Frame @%p: size=%d, %d samples, %dkbps %d/%d\n", |
217
|
|
|
|
|
|
|
bptr, frame->frame_size, frame->samples_per_frame, |
218
|
|
|
|
|
|
|
frame->bitrate_kbps, frame->samplerate, frame->channels); |
219
|
|
|
|
|
|
|
|
220
|
1582
|
|
|
|
|
|
return 0; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
// _mp3_get_average_bitrate |
224
|
|
|
|
|
|
|
// average bitrate by averaging all the frames in the file. This used |
225
|
|
|
|
|
|
|
// to seek to the middle of the file and take a 32K chunk but this was |
226
|
|
|
|
|
|
|
// found to have bugs if it seeked near invalid FF sync bytes that could |
227
|
|
|
|
|
|
|
// be detected as a real frame |
228
|
54
|
|
|
|
|
|
static short _mp3_get_average_bitrate(mp3info *mp3, uint32_t offset, uint32_t audio_size) |
229
|
|
|
|
|
|
|
{ |
230
|
|
|
|
|
|
|
struct mp3frame frame; |
231
|
54
|
|
|
|
|
|
int frame_count = 0; |
232
|
54
|
|
|
|
|
|
int bitrate_total = 0; |
233
|
54
|
|
|
|
|
|
int err = 0; |
234
|
54
|
|
|
|
|
|
int done = 0; |
235
|
54
|
|
|
|
|
|
int wrap_skip = 0; |
236
|
54
|
|
|
|
|
|
int prev_bitrate = 0; |
237
|
54
|
|
|
|
|
|
bool vbr = FALSE; |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
unsigned char *bptr; |
240
|
|
|
|
|
|
|
|
241
|
54
|
|
|
|
|
|
buffer_clear(mp3->buf); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
// Seek to offset |
244
|
54
|
|
|
|
|
|
PerlIO_seek(mp3->infile, offset, SEEK_SET); |
245
|
|
|
|
|
|
|
|
246
|
113
|
100
|
|
|
|
|
while ( done < audio_size - 4 ) { |
247
|
|
|
|
|
|
|
// Buffer size is optimized for a possible common case: 20 frames of 192kbps CBR |
248
|
86
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 4, MP3_BLOCK_SIZE * 3) ) { |
249
|
0
|
|
|
|
|
|
err = -1; |
250
|
0
|
|
|
|
|
|
goto out; |
251
|
|
|
|
|
|
|
} |
252
|
|
|
|
|
|
|
|
253
|
86
|
|
|
|
|
|
done += buffer_len(mp3->buf); |
254
|
|
|
|
|
|
|
|
255
|
86
|
100
|
|
|
|
|
if (wrap_skip) { |
256
|
|
|
|
|
|
|
// Skip rest of frame from last buffer |
257
|
|
|
|
|
|
|
DEBUG_TRACE("Wrapped, consuming %d bytes from previous frame\n", wrap_skip); |
258
|
32
|
|
|
|
|
|
buffer_consume(mp3->buf, wrap_skip); |
259
|
32
|
|
|
|
|
|
wrap_skip = 0; |
260
|
|
|
|
|
|
|
} |
261
|
|
|
|
|
|
|
|
262
|
1347
|
100
|
|
|
|
|
while ( buffer_len(mp3->buf) >= 4 ) { |
263
|
1288
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
264
|
1412
|
100
|
|
|
|
|
while ( *bptr != 0xFF ) { |
265
|
125
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
266
|
|
|
|
|
|
|
|
267
|
125
|
100
|
|
|
|
|
if ( buffer_len(mp3->buf) < 4 ) { |
268
|
|
|
|
|
|
|
// ran out of data |
269
|
1
|
|
|
|
|
|
goto out; |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
124
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
273
|
|
|
|
|
|
|
} |
274
|
|
|
|
|
|
|
|
275
|
1287
|
50
|
|
|
|
|
if ( !_decode_mp3_frame( buffer_ptr(mp3->buf), &frame ) ) { |
276
|
|
|
|
|
|
|
// Found a valid frame |
277
|
1287
|
|
|
|
|
|
frame_count++; |
278
|
1287
|
|
|
|
|
|
bitrate_total += frame.bitrate_kbps; |
279
|
|
|
|
|
|
|
|
280
|
1287
|
100
|
|
|
|
|
if ( !vbr ) { |
281
|
|
|
|
|
|
|
// If we see the bitrate changing, we have a VBR file, and read |
282
|
|
|
|
|
|
|
// the entire file. Otherwise, if we see 20 frames with the same |
283
|
|
|
|
|
|
|
// bitrate, assume CBR and stop |
284
|
699
|
100
|
|
|
|
|
if (prev_bitrate > 0 && prev_bitrate != frame.bitrate_kbps) { |
|
|
100
|
|
|
|
|
|
285
|
|
|
|
|
|
|
DEBUG_TRACE("Bitrate changed, assuming file is VBR\n"); |
286
|
13
|
|
|
|
|
|
vbr = TRUE; |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
else { |
289
|
686
|
100
|
|
|
|
|
if (frame_count > 20) { |
290
|
|
|
|
|
|
|
DEBUG_TRACE("Found 20 frames with same bitrate, assuming CBR\n"); |
291
|
26
|
|
|
|
|
|
goto out; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
660
|
|
|
|
|
|
prev_bitrate = frame.bitrate_kbps; |
295
|
|
|
|
|
|
|
} |
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
//DEBUG_TRACE(" Frame %d: %dkbps, %dkHz\n", frame_count, frame.bitrate_kbps, frame.samplerate); |
299
|
|
|
|
|
|
|
|
300
|
1261
|
100
|
|
|
|
|
if (frame.frame_size > buffer_len(mp3->buf)) { |
301
|
|
|
|
|
|
|
// Partial frame in buffer |
302
|
44
|
|
|
|
|
|
wrap_skip = frame.frame_size - buffer_len(mp3->buf); |
303
|
44
|
|
|
|
|
|
buffer_consume(mp3->buf, buffer_len(mp3->buf)); |
304
|
|
|
|
|
|
|
} |
305
|
|
|
|
|
|
|
else { |
306
|
1261
|
|
|
|
|
|
buffer_consume(mp3->buf, frame.frame_size); |
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
} |
309
|
|
|
|
|
|
|
else { |
310
|
|
|
|
|
|
|
// Not a valid frame, stray 0xFF |
311
|
0
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
312
|
|
|
|
|
|
|
} |
313
|
|
|
|
|
|
|
} |
314
|
|
|
|
|
|
|
} |
315
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
out: |
317
|
54
|
50
|
|
|
|
|
if (err) return err; |
318
|
|
|
|
|
|
|
|
319
|
54
|
50
|
|
|
|
|
if (!frame_count) return -1; |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
DEBUG_TRACE("Average of %d frames: %dkbps\n", frame_count, bitrate_total / frame_count); |
322
|
|
|
|
|
|
|
|
323
|
54
|
|
|
|
|
|
return bitrate_total / frame_count; |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
static int |
327
|
85
|
|
|
|
|
|
_parse_xing(mp3info *mp3) |
328
|
|
|
|
|
|
|
{ |
329
|
|
|
|
|
|
|
int i; |
330
|
|
|
|
|
|
|
unsigned char *bptr; |
331
|
85
|
|
|
|
|
|
int xing_offset = 4; |
332
|
|
|
|
|
|
|
|
333
|
85
|
100
|
|
|
|
|
if (mp3->first_frame->mpegID == MPEG1_ID) { |
334
|
70
|
100
|
|
|
|
|
xing_offset += mp3->first_frame->channels == 2 ? 32 : 17; |
335
|
|
|
|
|
|
|
} |
336
|
|
|
|
|
|
|
else { |
337
|
15
|
100
|
|
|
|
|
xing_offset += mp3->first_frame->channels == 2 ? 17 : 9; |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
|
340
|
85
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 4 + xing_offset, MP3_BLOCK_SIZE) ) { |
341
|
0
|
|
|
|
|
|
return 0; |
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
85
|
|
|
|
|
|
buffer_consume(mp3->buf, xing_offset); |
345
|
|
|
|
|
|
|
|
346
|
85
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
347
|
|
|
|
|
|
|
|
348
|
85
|
100
|
|
|
|
|
if ( bptr[0] == 'X' || bptr[0] == 'I' ) { |
|
|
100
|
|
|
|
|
|
349
|
58
|
100
|
|
|
|
|
if ( |
350
|
22
|
50
|
|
|
|
|
( bptr[1] == 'i' && bptr[2] == 'n' && bptr[3] == 'g' ) |
|
|
50
|
|
|
|
|
|
351
|
7
|
50
|
|
|
|
|
|| |
352
|
7
|
50
|
|
|
|
|
( bptr[1] == 'n' && bptr[2] == 'f' && bptr[3] == 'o' ) |
|
|
50
|
|
|
|
|
|
353
|
|
|
|
|
|
|
) { |
354
|
|
|
|
|
|
|
DEBUG_TRACE("Found Xing/Info tag\n"); |
355
|
|
|
|
|
|
|
|
356
|
29
|
|
|
|
|
|
mp3->xing_frame->xing_tag = bptr[0] == 'X'; |
357
|
29
|
|
|
|
|
|
mp3->xing_frame->info_tag = bptr[0] == 'I'; |
358
|
29
|
|
|
|
|
|
mp3->xing_frame->frame_size = mp3->first_frame->frame_size; |
359
|
|
|
|
|
|
|
|
360
|
29
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 160, MP3_BLOCK_SIZE) ) { |
361
|
0
|
|
|
|
|
|
return 0; |
362
|
|
|
|
|
|
|
} |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
// It's VBR if tag is Xing, and CBR if Info |
365
|
29
|
100
|
|
|
|
|
mp3->vbr = bptr[1] == 'i' ? VBR : CBR; |
366
|
|
|
|
|
|
|
|
367
|
29
|
|
|
|
|
|
buffer_consume(mp3->buf, 4); |
368
|
|
|
|
|
|
|
|
369
|
29
|
|
|
|
|
|
mp3->xing_frame->flags = buffer_get_int(mp3->buf); |
370
|
|
|
|
|
|
|
|
371
|
29
|
50
|
|
|
|
|
if (mp3->xing_frame->flags & XING_FRAMES) { |
372
|
29
|
|
|
|
|
|
mp3->xing_frame->xing_frames = buffer_get_int(mp3->buf); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
29
|
50
|
|
|
|
|
if ( mp3->xing_frame->flags & XING_BYTES) { |
376
|
29
|
|
|
|
|
|
mp3->xing_frame->xing_bytes = buffer_get_int(mp3->buf); |
377
|
|
|
|
|
|
|
} |
378
|
|
|
|
|
|
|
|
379
|
29
|
50
|
|
|
|
|
if (mp3->xing_frame->flags & XING_TOC) { |
380
|
|
|
|
|
|
|
uint8_t i; |
381
|
29
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
382
|
2929
|
100
|
|
|
|
|
for (i = 0; i < 100; i++) { |
383
|
2900
|
|
|
|
|
|
mp3->xing_frame->xing_toc[i] = bptr[i]; |
384
|
|
|
|
|
|
|
} |
385
|
|
|
|
|
|
|
|
386
|
29
|
|
|
|
|
|
mp3->xing_frame->has_toc = 1; |
387
|
|
|
|
|
|
|
|
388
|
29
|
|
|
|
|
|
buffer_consume(mp3->buf, 100); |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
|
391
|
29
|
100
|
|
|
|
|
if (mp3->xing_frame->flags & XING_QUALITY) { |
392
|
28
|
|
|
|
|
|
mp3->xing_frame->xing_quality = buffer_get_int(mp3->buf); |
393
|
|
|
|
|
|
|
} |
394
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
// LAME tag |
396
|
29
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
397
|
29
|
100
|
|
|
|
|
if ( bptr[0] == 'L' && bptr[1] == 'A' && bptr[2] == 'M' && bptr[3] == 'E' ) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
398
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_tag = TRUE; |
399
|
|
|
|
|
|
|
|
400
|
19
|
|
|
|
|
|
strncpy(mp3->xing_frame->lame_encoder_version, (char *)bptr, 9); |
401
|
19
|
|
|
|
|
|
bptr += 9; |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
// revision/vbr method byte |
404
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_tag_revision = bptr[0] >> 4; |
405
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_vbr_method = bptr[0] & 15; |
406
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 10); |
407
|
|
|
|
|
|
|
|
408
|
|
|
|
|
|
|
// Determine vbr status |
409
|
19
|
|
|
|
|
|
switch (mp3->xing_frame->lame_vbr_method) { |
410
|
|
|
|
|
|
|
case 1: |
411
|
|
|
|
|
|
|
case 8: |
412
|
7
|
|
|
|
|
|
mp3->vbr = CBR; |
413
|
7
|
|
|
|
|
|
break; |
414
|
|
|
|
|
|
|
case 2: |
415
|
|
|
|
|
|
|
case 9: |
416
|
3
|
|
|
|
|
|
mp3->vbr = ABR; |
417
|
3
|
|
|
|
|
|
break; |
418
|
|
|
|
|
|
|
default: |
419
|
9
|
|
|
|
|
|
mp3->vbr = VBR; |
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
|
422
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_lowpass = buffer_get_char(mp3->buf) * 100; |
423
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
// Skip peak |
425
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 4); |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
// Replay Gain, code from mpg123 |
428
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_replay_gain[0] = 0; |
429
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_replay_gain[1] = 0; |
430
|
|
|
|
|
|
|
|
431
|
57
|
100
|
|
|
|
|
for (i=0; i<2; i++) { |
432
|
|
|
|
|
|
|
// Originator |
433
|
|
|
|
|
|
|
unsigned char origin; |
434
|
38
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
435
|
|
|
|
|
|
|
|
436
|
38
|
|
|
|
|
|
origin = (bptr[0] >> 2) & 0x7; |
437
|
|
|
|
|
|
|
|
438
|
38
|
100
|
|
|
|
|
if (origin != 0) { |
439
|
|
|
|
|
|
|
// Gain type |
440
|
15
|
|
|
|
|
|
unsigned char gt = bptr[0] >> 5; |
441
|
15
|
50
|
|
|
|
|
if (gt == 1) |
442
|
15
|
|
|
|
|
|
gt = 0; /* radio */ |
443
|
0
|
0
|
|
|
|
|
else if (gt == 2) |
444
|
0
|
|
|
|
|
|
gt = 1; /* audiophile */ |
445
|
|
|
|
|
|
|
else |
446
|
0
|
|
|
|
|
|
continue; |
447
|
|
|
|
|
|
|
|
448
|
15
|
|
|
|
|
|
mp3->xing_frame->lame_replay_gain[gt] |
449
|
15
|
50
|
|
|
|
|
= (( (bptr[0] & 0x4) >> 2 ) ? -0.1 : 0.1) |
450
|
15
|
|
|
|
|
|
* ( (bptr[0] & 0x3) | bptr[1] ); |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
38
|
|
|
|
|
|
buffer_consume(mp3->buf, 2); |
454
|
|
|
|
|
|
|
} |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
// Skip encoding flags |
457
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
458
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
// ABR rate/VBR minimum |
460
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_abr_rate = buffer_get_char(mp3->buf); |
461
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
// Encoder delay/padding |
463
|
19
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
464
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_encoder_delay = ((((int)bptr[0]) << 4) | (((int)bptr[1]) >> 4)); |
465
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_encoder_padding = (((((int)bptr[1]) << 8) | (((int)bptr[2]))) & 0xfff); |
466
|
|
|
|
|
|
|
// sanity check |
467
|
19
|
50
|
|
|
|
|
if (mp3->xing_frame->lame_encoder_delay < 0 || mp3->xing_frame->lame_encoder_delay > 3000) { |
|
|
50
|
|
|
|
|
|
468
|
0
|
|
|
|
|
|
mp3->xing_frame->lame_encoder_delay = -1; |
469
|
|
|
|
|
|
|
} |
470
|
19
|
50
|
|
|
|
|
if (mp3->xing_frame->lame_encoder_padding < 0 || mp3->xing_frame->lame_encoder_padding > 3000) { |
|
|
50
|
|
|
|
|
|
471
|
0
|
|
|
|
|
|
mp3->xing_frame->lame_encoder_padding = -1; |
472
|
|
|
|
|
|
|
} |
473
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 3); |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
// Misc |
476
|
19
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
477
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_noise_shaping = bptr[0] & 0x3; |
478
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_stereo_mode = (bptr[0] & 0x1C) >> 2; |
479
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_unwise = (bptr[0] & 0x20) >> 5; |
480
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_source_freq = (bptr[0] & 0xC0) >> 6; |
481
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
// XXX MP3 Gain, can't find a test file, current |
484
|
|
|
|
|
|
|
// mp3gain doesn't write this data |
485
|
|
|
|
|
|
|
/* |
486
|
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
487
|
|
|
|
|
|
|
unsigned char sign = (bptr[0] & 0x80) >> 7; |
488
|
|
|
|
|
|
|
mp3->xing_frame->lame_mp3gain = bptr[0] & 0x7F; |
489
|
|
|
|
|
|
|
if (sign) { |
490
|
|
|
|
|
|
|
mp3->xing_frame->lame_mp3gain *= -1; |
491
|
|
|
|
|
|
|
} |
492
|
|
|
|
|
|
|
mp3->xing_frame->lame_mp3gain_db = mp3->xing_frame->lame_mp3gain * 1.5; |
493
|
|
|
|
|
|
|
*/ |
494
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
// Preset/Surround |
497
|
19
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
498
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_surround = (bptr[0] & 0x38) >> 3; |
499
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_preset = ((bptr[0] << 8) | bptr[1]) & 0x7ff; |
500
|
19
|
|
|
|
|
|
buffer_consume(mp3->buf, 2); |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
// Music Length |
503
|
19
|
|
|
|
|
|
mp3->xing_frame->lame_music_length = buffer_get_int(mp3->buf); |
504
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
// Skip CRCs |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
} |
508
|
|
|
|
|
|
|
} |
509
|
|
|
|
|
|
|
// Check for VBRI header from Fhg encoders |
510
|
56
|
100
|
|
|
|
|
else if ( bptr[0] == 'V' && bptr[1] == 'B' && bptr[2] == 'R' && bptr[3] == 'I' ) { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
511
|
|
|
|
|
|
|
DEBUG_TRACE("Found VBRI tag\n"); |
512
|
|
|
|
|
|
|
|
513
|
2
|
|
|
|
|
|
mp3->xing_frame->vbri_tag = TRUE; |
514
|
2
|
|
|
|
|
|
mp3->vbr = VBR; |
515
|
|
|
|
|
|
|
|
516
|
2
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 14, MP3_BLOCK_SIZE) ) { |
517
|
0
|
|
|
|
|
|
return 0; |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
// Skip tag and version ID |
521
|
2
|
|
|
|
|
|
buffer_consume(mp3->buf, 6); |
522
|
|
|
|
|
|
|
|
523
|
2
|
|
|
|
|
|
mp3->xing_frame->vbri_delay = buffer_get_short(mp3->buf); |
524
|
2
|
|
|
|
|
|
mp3->xing_frame->vbri_quality = buffer_get_short(mp3->buf); |
525
|
2
|
|
|
|
|
|
mp3->xing_frame->vbri_bytes = buffer_get_int(mp3->buf); |
526
|
2
|
|
|
|
|
|
mp3->xing_frame->vbri_frames = buffer_get_int(mp3->buf); |
527
|
|
|
|
|
|
|
} |
528
|
|
|
|
|
|
|
|
529
|
85
|
|
|
|
|
|
return 1; |
530
|
|
|
|
|
|
|
} |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
static int |
533
|
85
|
|
|
|
|
|
_is_mp3x_profile(mp3info *mp3) |
534
|
|
|
|
|
|
|
{ |
535
|
85
|
100
|
|
|
|
|
if (mp3->first_frame->layerID != LAYER3_ID) |
536
|
2
|
|
|
|
|
|
return 0; |
537
|
|
|
|
|
|
|
|
538
|
83
|
100
|
|
|
|
|
if (mp3->first_frame->mpegID != MPEG1_ID && mp3->first_frame->mpegID != MPEG2_ID) |
|
|
100
|
|
|
|
|
|
539
|
6
|
|
|
|
|
|
return 0; |
540
|
|
|
|
|
|
|
|
541
|
77
|
100
|
|
|
|
|
if (mp3->first_frame->samplerate != 16000 |
542
|
75
|
100
|
|
|
|
|
&& mp3->first_frame->samplerate != 22050 |
543
|
71
|
100
|
|
|
|
|
&& mp3->first_frame->samplerate != 24000) |
544
|
69
|
|
|
|
|
|
return 0; |
545
|
|
|
|
|
|
|
|
546
|
8
|
50
|
|
|
|
|
if (mp3->bitrate >= 8 && mp3->bitrate <= 320) |
|
|
50
|
|
|
|
|
|
547
|
8
|
|
|
|
|
|
return 1; |
548
|
|
|
|
|
|
|
|
549
|
0
|
|
|
|
|
|
return 0; |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
static int |
553
|
77
|
|
|
|
|
|
_is_mp3_profile(mp3info *mp3) |
554
|
|
|
|
|
|
|
{ |
555
|
77
|
100
|
|
|
|
|
if (mp3->first_frame->layerID != LAYER3_ID) |
556
|
2
|
|
|
|
|
|
return 0; |
557
|
|
|
|
|
|
|
|
558
|
75
|
100
|
|
|
|
|
if (mp3->first_frame->mpegID != MPEG1_ID) |
559
|
6
|
|
|
|
|
|
return 0; |
560
|
|
|
|
|
|
|
|
561
|
69
|
100
|
|
|
|
|
if (mp3->first_frame->samplerate != 32000 |
562
|
37
|
100
|
|
|
|
|
&& mp3->first_frame->samplerate != 44100 |
563
|
1
|
50
|
|
|
|
|
&& mp3->first_frame->samplerate != 48000) |
564
|
0
|
|
|
|
|
|
return 0; |
565
|
|
|
|
|
|
|
|
566
|
69
|
50
|
|
|
|
|
if (mp3->bitrate >= 32 && mp3->bitrate <= 320) |
|
|
50
|
|
|
|
|
|
567
|
69
|
|
|
|
|
|
return 1; |
568
|
|
|
|
|
|
|
|
569
|
0
|
|
|
|
|
|
return 0; |
570
|
|
|
|
|
|
|
} |
571
|
|
|
|
|
|
|
|
572
|
|
|
|
|
|
|
mp3info * |
573
|
86
|
|
|
|
|
|
_mp3_parse(PerlIO *infile, char *file, HV *info) |
574
|
|
|
|
|
|
|
{ |
575
|
|
|
|
|
|
|
unsigned char *bptr; |
576
|
|
|
|
|
|
|
char id3v1taghdr[4]; |
577
|
|
|
|
|
|
|
|
578
|
86
|
|
|
|
|
|
uint32_t song_length_ms = 0; |
579
|
86
|
|
|
|
|
|
uint64_t total_samples = 0; |
580
|
|
|
|
|
|
|
struct mp3frame frame; |
581
|
|
|
|
|
|
|
|
582
|
86
|
|
|
|
|
|
bool found_first_frame = FALSE; |
583
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
mp3info *mp3; |
585
|
86
|
|
|
|
|
|
Newz(0, mp3, sizeof(mp3info), mp3info); |
586
|
86
|
|
|
|
|
|
Newz(0, mp3->buf, sizeof(Buffer), Buffer); |
587
|
86
|
|
|
|
|
|
Newz(0, mp3->first_frame, sizeof(mp3frame), mp3frame); |
588
|
86
|
|
|
|
|
|
Newz(0, mp3->xing_frame, sizeof(xingframe), xingframe); |
589
|
|
|
|
|
|
|
|
590
|
86
|
|
|
|
|
|
mp3->infile = infile; |
591
|
86
|
|
|
|
|
|
mp3->file = file; |
592
|
86
|
|
|
|
|
|
mp3->info = info; |
593
|
|
|
|
|
|
|
|
594
|
86
|
|
|
|
|
|
mp3->file_size = _file_size(infile); |
595
|
86
|
|
|
|
|
|
mp3->id3_size = 0; |
596
|
86
|
|
|
|
|
|
mp3->audio_offset = 0; |
597
|
86
|
|
|
|
|
|
mp3->audio_size = 0; |
598
|
86
|
|
|
|
|
|
mp3->bitrate = 0; |
599
|
|
|
|
|
|
|
|
600
|
86
|
|
|
|
|
|
buffer_init(mp3->buf, MP3_BLOCK_SIZE); |
601
|
|
|
|
|
|
|
|
602
|
86
|
|
|
|
|
|
my_hv_store( info, "file_size", newSVuv(mp3->file_size) ); |
603
|
|
|
|
|
|
|
|
604
|
86
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 10, MP3_BLOCK_SIZE) ) { |
605
|
0
|
|
|
|
|
|
goto out; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
|
608
|
86
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
609
|
|
|
|
|
|
|
|
610
|
86
|
100
|
|
|
|
|
if ( |
611
|
65
|
50
|
|
|
|
|
(bptr[0] == 'I' && bptr[1] == 'D' && bptr[2] == '3') && |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
612
|
65
|
50
|
|
|
|
|
bptr[3] < 0xff && bptr[4] < 0xff && |
|
|
50
|
|
|
|
|
|
613
|
65
|
50
|
|
|
|
|
bptr[6] < 0x80 && bptr[7] < 0x80 && bptr[8] < 0x80 && bptr[9] < 0x80 |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
614
|
|
|
|
|
|
|
) { |
615
|
|
|
|
|
|
|
/* found an ID3 header... */ |
616
|
65
|
|
|
|
|
|
mp3->id3_size = 10 + (bptr[6]<<21) + (bptr[7]<<14) + (bptr[8]<<7) + bptr[9]; |
617
|
|
|
|
|
|
|
|
618
|
65
|
50
|
|
|
|
|
if (bptr[5] & 0x10) { |
619
|
|
|
|
|
|
|
// footer present |
620
|
0
|
|
|
|
|
|
mp3->id3_size += 10; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
DEBUG_TRACE("Found ID3v2.%d.%d tag, size %d\n", bptr[3], bptr[4], mp3->id3_size); |
624
|
|
|
|
|
|
|
|
625
|
|
|
|
|
|
|
// Always seek past the ID3 tags |
626
|
65
|
|
|
|
|
|
_mp3_skip(mp3, mp3->id3_size); |
627
|
|
|
|
|
|
|
|
628
|
65
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 4, MP3_BLOCK_SIZE) ) { |
629
|
0
|
|
|
|
|
|
goto out; |
630
|
|
|
|
|
|
|
} |
631
|
|
|
|
|
|
|
|
632
|
65
|
|
|
|
|
|
mp3->audio_offset += mp3->id3_size; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
// Find an MP3 frame |
636
|
249
|
100
|
|
|
|
|
while ( !found_first_frame && buffer_len(mp3->buf) ) { |
|
|
50
|
|
|
|
|
|
637
|
164
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
638
|
|
|
|
|
|
|
|
639
|
16170
|
100
|
|
|
|
|
while ( *bptr != 0xFF ) { |
640
|
16007
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
641
|
|
|
|
|
|
|
|
642
|
16007
|
|
|
|
|
|
mp3->audio_offset++; |
643
|
|
|
|
|
|
|
|
644
|
16007
|
100
|
|
|
|
|
if ( !buffer_len(mp3->buf) ) { |
645
|
3
|
100
|
|
|
|
|
if (mp3->audio_offset >= mp3->file_size - 4) { |
646
|
|
|
|
|
|
|
// No audio frames in file |
647
|
1
|
|
|
|
|
|
warn("Unable to find any MP3 frames in file: %s\n", file); |
648
|
1
|
|
|
|
|
|
goto out; |
649
|
|
|
|
|
|
|
} |
650
|
|
|
|
|
|
|
|
651
|
2
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 4, MP3_BLOCK_SIZE) ) { |
652
|
0
|
|
|
|
|
|
warn("Unable to find any MP3 frames in file: %s\n", file); |
653
|
0
|
|
|
|
|
|
goto out; |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
} |
656
|
|
|
|
|
|
|
|
657
|
16006
|
|
|
|
|
|
bptr = buffer_ptr(mp3->buf); |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
DEBUG_TRACE("Found FF sync at offset %d\n", (int)mp3->audio_offset); |
661
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
// Make sure we have 4 bytes |
663
|
163
|
50
|
|
|
|
|
if ( !_check_buf(mp3->infile, mp3->buf, 4, MP3_BLOCK_SIZE) ) { |
664
|
0
|
|
|
|
|
|
goto out; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
|
667
|
163
|
100
|
|
|
|
|
if ( !_decode_mp3_frame( (unsigned char *)buffer_ptr(mp3->buf), &frame ) ) { |
668
|
|
|
|
|
|
|
struct mp3frame frame2, frame3; |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
// Need the whole frame to consider it valid |
671
|
109
|
50
|
|
|
|
|
if ( _check_buf(mp3->infile, mp3->buf, frame.frame_size, MP3_BLOCK_SIZE) |
672
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
// If we have enough data for the start of the next frame then |
674
|
|
|
|
|
|
|
// it must also look valid and be consistent |
675
|
109
|
50
|
|
|
|
|
&& ( |
676
|
109
|
|
|
|
|
|
!_check_buf(mp3->infile, mp3->buf, frame.frame_size + 4, MP3_BLOCK_SIZE) |
677
|
109
|
100
|
|
|
|
|
|| ( |
678
|
109
|
|
|
|
|
|
!_decode_mp3_frame( (unsigned char *)buffer_ptr(mp3->buf) + frame.frame_size, &frame2 ) |
679
|
97
|
100
|
|
|
|
|
&& frame.samplerate == frame2.samplerate |
680
|
85
|
50
|
|
|
|
|
&& frame.channels == frame2.channels |
681
|
|
|
|
|
|
|
) |
682
|
|
|
|
|
|
|
) |
683
|
|
|
|
|
|
|
|
684
|
|
|
|
|
|
|
// If we have enough data for the start of the over-next frame then |
685
|
|
|
|
|
|
|
// it must also look valid and be consistent |
686
|
85
|
50
|
|
|
|
|
&& ( |
687
|
85
|
|
|
|
|
|
!_check_buf(mp3->infile, mp3->buf, frame.frame_size + frame2.frame_size + 4, MP3_BLOCK_SIZE) |
688
|
85
|
50
|
|
|
|
|
|| ( |
689
|
85
|
|
|
|
|
|
!_decode_mp3_frame( (unsigned char *)buffer_ptr(mp3->buf) + frame.frame_size + frame2.frame_size, &frame3 ) |
690
|
85
|
50
|
|
|
|
|
&& frame.samplerate == frame3.samplerate |
691
|
85
|
50
|
|
|
|
|
&& frame.channels == frame3.channels |
692
|
|
|
|
|
|
|
) |
693
|
|
|
|
|
|
|
) |
694
|
|
|
|
|
|
|
) { |
695
|
|
|
|
|
|
|
// Found a valid frame |
696
|
|
|
|
|
|
|
DEBUG_TRACE(" valid frame\n"); |
697
|
|
|
|
|
|
|
|
698
|
109
|
|
|
|
|
|
found_first_frame = 1; |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
else { |
701
|
|
|
|
|
|
|
DEBUG_TRACE(" false sync\n"); |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
} |
704
|
|
|
|
|
|
|
|
705
|
163
|
100
|
|
|
|
|
if (!found_first_frame) { |
706
|
|
|
|
|
|
|
// Not a valid frame, stray 0xFF |
707
|
|
|
|
|
|
|
DEBUG_TRACE(" invalid frame\n"); |
708
|
|
|
|
|
|
|
|
709
|
78
|
|
|
|
|
|
buffer_consume(mp3->buf, 1); |
710
|
78
|
|
|
|
|
|
mp3->audio_offset++; |
711
|
|
|
|
|
|
|
} |
712
|
|
|
|
|
|
|
} |
713
|
|
|
|
|
|
|
|
714
|
85
|
50
|
|
|
|
|
if ( !found_first_frame ) { |
715
|
0
|
|
|
|
|
|
warn("Unable to find any MP3 frames in file (checked 4K): %s\n", file); |
716
|
0
|
|
|
|
|
|
goto out; |
717
|
|
|
|
|
|
|
} |
718
|
|
|
|
|
|
|
|
719
|
85
|
|
|
|
|
|
mp3->audio_size = mp3->file_size - mp3->audio_offset; |
720
|
|
|
|
|
|
|
|
721
|
85
|
|
|
|
|
|
memcpy(mp3->first_frame, &frame, sizeof(mp3frame)); |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
// now check for Xing/Info/VBRI/LAME headers |
724
|
85
|
50
|
|
|
|
|
if ( !_parse_xing(mp3) ) { |
725
|
0
|
|
|
|
|
|
goto out; |
726
|
|
|
|
|
|
|
} |
727
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
// use LAME CBR/ABR value for bitrate if available |
729
|
85
|
100
|
|
|
|
|
if ( (mp3->vbr == CBR || mp3->vbr == ABR) && mp3->xing_frame->lame_abr_rate ) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
730
|
10
|
100
|
|
|
|
|
if (mp3->xing_frame->lame_abr_rate >= 255) { |
731
|
|
|
|
|
|
|
// ABR rate field only codes up to 255, use preset value instead |
732
|
2
|
50
|
|
|
|
|
if (mp3->xing_frame->lame_preset <= 320) { |
733
|
2
|
|
|
|
|
|
mp3->bitrate = mp3->xing_frame->lame_preset; |
734
|
|
|
|
|
|
|
DEBUG_TRACE("bitrate from lame_preset: %d\n", mp3->bitrate); |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
} |
737
|
|
|
|
|
|
|
else { |
738
|
8
|
|
|
|
|
|
mp3->bitrate = mp3->xing_frame->lame_abr_rate; |
739
|
|
|
|
|
|
|
DEBUG_TRACE("bitrate from lame_abr_rate: %d\n", mp3->bitrate); |
740
|
|
|
|
|
|
|
} |
741
|
|
|
|
|
|
|
} |
742
|
|
|
|
|
|
|
|
743
|
|
|
|
|
|
|
// Or if we have a Xing header, use it to determine bitrate |
744
|
104
|
100
|
|
|
|
|
if (!mp3->bitrate && (mp3->xing_frame->xing_frames && mp3->xing_frame->xing_bytes)) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
745
|
19
|
100
|
|
|
|
|
float mfs = (float)frame.samplerate / ( frame.mpegID == MPEG2_ID || frame.mpegID == MPEG25_ID ? 72000. : 144000. ); |
|
|
100
|
|
|
|
|
|
746
|
19
|
|
|
|
|
|
mp3->bitrate = ( mp3->xing_frame->xing_bytes / mp3->xing_frame->xing_frames * mfs ); |
747
|
|
|
|
|
|
|
DEBUG_TRACE("bitrate from Xing header: %d\n", mp3->bitrate); |
748
|
|
|
|
|
|
|
} |
749
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
// Or use VBRI header |
751
|
66
|
100
|
|
|
|
|
else if (mp3->xing_frame->vbri_frames && mp3->xing_frame->vbri_bytes) { |
|
|
50
|
|
|
|
|
|
752
|
2
|
50
|
|
|
|
|
float mfs = (float)frame.samplerate / ( frame.mpegID == MPEG2_ID || frame.mpegID == MPEG25_ID ? 72000. : 144000. ); |
|
|
50
|
|
|
|
|
|
753
|
2
|
|
|
|
|
|
mp3->bitrate = ( mp3->xing_frame->vbri_bytes / mp3->xing_frame->vbri_frames * mfs ); |
754
|
|
|
|
|
|
|
DEBUG_TRACE("bitrate from VBRI header: %d\n", mp3->bitrate); |
755
|
|
|
|
|
|
|
} |
756
|
|
|
|
|
|
|
|
757
|
|
|
|
|
|
|
// check if last 128 bytes is ID3v1.0 or ID3v1.1 tag |
758
|
85
|
|
|
|
|
|
PerlIO_seek(infile, mp3->file_size - 128, SEEK_SET); |
759
|
85
|
50
|
|
|
|
|
if (PerlIO_read(infile, id3v1taghdr, 4) == 4) { |
760
|
85
|
100
|
|
|
|
|
if (id3v1taghdr[0]=='T' && id3v1taghdr[1]=='A' && id3v1taghdr[2]=='G') { |
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
761
|
|
|
|
|
|
|
DEBUG_TRACE("ID3v1 tag found\n"); |
762
|
13
|
|
|
|
|
|
mp3->audio_size -= 128; |
763
|
|
|
|
|
|
|
} |
764
|
|
|
|
|
|
|
} |
765
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
// If we don't know the bitrate from Xing/LAME/VBRI, calculate average |
767
|
85
|
100
|
|
|
|
|
if ( !mp3->bitrate ) { |
768
|
|
|
|
|
|
|
DEBUG_TRACE("Calculating average bitrate starting from %d...\n", (int)mp3->audio_offset); |
769
|
54
|
|
|
|
|
|
mp3->bitrate = _mp3_get_average_bitrate(mp3, mp3->audio_offset, mp3->audio_size); |
770
|
|
|
|
|
|
|
|
771
|
54
|
50
|
|
|
|
|
if (mp3->bitrate <= 0) { |
772
|
|
|
|
|
|
|
// Couldn't determine bitrate, just use |
773
|
|
|
|
|
|
|
// the bitrate from the last frame we parsed |
774
|
|
|
|
|
|
|
DEBUG_TRACE("Unable to determine bitrate, using bitrate of most recent frame (%d)\n", frame.bitrate_kbps); |
775
|
0
|
|
|
|
|
|
mp3->bitrate = frame.bitrate_kbps; |
776
|
|
|
|
|
|
|
} |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
|
779
|
85
|
100
|
|
|
|
|
if (mp3->xing_frame->xing_frames) { |
780
|
29
|
|
|
|
|
|
total_samples = mp3->xing_frame->xing_frames * frame.samples_per_frame; |
781
|
|
|
|
|
|
|
|
782
|
29
|
100
|
|
|
|
|
if (mp3->xing_frame->lame_tag) { |
783
|
|
|
|
|
|
|
// subtract delay/padding to get accurate sample count |
784
|
19
|
|
|
|
|
|
total_samples -= (mp3->xing_frame->lame_encoder_delay + mp3->xing_frame->lame_encoder_padding); |
785
|
|
|
|
|
|
|
} |
786
|
|
|
|
|
|
|
|
787
|
29
|
|
|
|
|
|
song_length_ms = (int) ((double)(total_samples * 1000.) / (double) frame.samplerate); |
788
|
|
|
|
|
|
|
} |
789
|
56
|
100
|
|
|
|
|
else if (mp3->xing_frame->vbri_frames) { |
790
|
4
|
|
|
|
|
|
song_length_ms = (int) ((double)(mp3->xing_frame->vbri_frames * frame.samples_per_frame * 1000.)/ |
791
|
2
|
|
|
|
|
|
(double) frame.samplerate); |
792
|
2
|
|
|
|
|
|
total_samples = mp3->xing_frame->vbri_frames * frame.samples_per_frame; |
793
|
|
|
|
|
|
|
} |
794
|
|
|
|
|
|
|
else { |
795
|
54
|
|
|
|
|
|
song_length_ms = (int) ((double)mp3->audio_size * 8. / |
796
|
54
|
|
|
|
|
|
(double)mp3->bitrate); |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
85
|
|
|
|
|
|
mp3->song_length_ms = song_length_ms; |
800
|
|
|
|
|
|
|
|
801
|
85
|
|
|
|
|
|
my_hv_store( info, "song_length_ms", newSVuv(song_length_ms) ); |
802
|
85
|
|
|
|
|
|
my_hv_store( info, "layer", newSVuv(frame.layerID) ); |
803
|
85
|
|
|
|
|
|
my_hv_store( info, "stereo", newSVuv(frame.channels == 2 ? 1 : 0) ); |
804
|
85
|
|
|
|
|
|
my_hv_store( info, "samples_per_frame", newSVuv(frame.samples_per_frame) ); |
805
|
85
|
|
|
|
|
|
my_hv_store( info, "padding", newSVuv(frame.padding) ); |
806
|
85
|
|
|
|
|
|
my_hv_store( info, "audio_size", newSVuv(mp3->audio_size) ); |
807
|
85
|
|
|
|
|
|
my_hv_store( info, "audio_offset", newSVuv(mp3->audio_offset) ); |
808
|
85
|
|
|
|
|
|
my_hv_store( info, "bitrate", newSVuv( mp3->bitrate * 1000 ) ); |
809
|
85
|
|
|
|
|
|
my_hv_store( info, "samplerate", newSVuv( frame.samplerate ) ); |
810
|
|
|
|
|
|
|
|
811
|
85
|
100
|
|
|
|
|
if (mp3->xing_frame->xing_tag || mp3->xing_frame->info_tag) { |
|
|
100
|
|
|
|
|
|
812
|
29
|
50
|
|
|
|
|
if (mp3->xing_frame->xing_frames) { |
813
|
29
|
|
|
|
|
|
my_hv_store( info, "xing_frames", newSVuv(mp3->xing_frame->xing_frames) ); |
814
|
|
|
|
|
|
|
} |
815
|
|
|
|
|
|
|
|
816
|
29
|
50
|
|
|
|
|
if (mp3->xing_frame->xing_bytes) { |
817
|
29
|
|
|
|
|
|
my_hv_store( info, "xing_bytes", newSVuv(mp3->xing_frame->xing_bytes) ); |
818
|
|
|
|
|
|
|
} |
819
|
|
|
|
|
|
|
|
820
|
29
|
50
|
|
|
|
|
if (mp3->xing_frame->has_toc) { |
821
|
|
|
|
|
|
|
uint8_t i; |
822
|
29
|
|
|
|
|
|
AV *xing_toc = newAV(); |
823
|
|
|
|
|
|
|
|
824
|
2929
|
100
|
|
|
|
|
for (i = 0; i < 100; i++) { |
825
|
2900
|
|
|
|
|
|
av_push( xing_toc, newSVuv(mp3->xing_frame->xing_toc[i]) ); |
826
|
|
|
|
|
|
|
} |
827
|
|
|
|
|
|
|
|
828
|
29
|
|
|
|
|
|
my_hv_store( info, "xing_toc", newRV_noinc( (SV *)xing_toc ) ); |
829
|
|
|
|
|
|
|
} |
830
|
|
|
|
|
|
|
|
831
|
29
|
100
|
|
|
|
|
if (mp3->xing_frame->xing_quality) { |
832
|
22
|
|
|
|
|
|
my_hv_store( info, "xing_quality", newSVuv(mp3->xing_frame->xing_quality) ); |
833
|
|
|
|
|
|
|
} |
834
|
|
|
|
|
|
|
} |
835
|
|
|
|
|
|
|
|
836
|
85
|
100
|
|
|
|
|
if (mp3->xing_frame->vbri_tag) { |
837
|
2
|
|
|
|
|
|
my_hv_store( info, "vbri_delay", newSVuv(mp3->xing_frame->vbri_delay) ); |
838
|
2
|
|
|
|
|
|
my_hv_store( info, "vbri_frames", newSVuv(mp3->xing_frame->vbri_frames) ); |
839
|
2
|
|
|
|
|
|
my_hv_store( info, "vbri_bytes", newSVuv(mp3->xing_frame->vbri_bytes) ); |
840
|
2
|
|
|
|
|
|
my_hv_store( info, "vbri_quality", newSVuv(mp3->xing_frame->vbri_quality) ); |
841
|
|
|
|
|
|
|
} |
842
|
|
|
|
|
|
|
|
843
|
85
|
100
|
|
|
|
|
if (mp3->xing_frame->lame_tag) { |
844
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_encoder_version", newSVpvn(mp3->xing_frame->lame_encoder_version, 9) ); |
845
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_tag_revision", newSViv(mp3->xing_frame->lame_tag_revision) ); |
846
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_vbr_method", newSVpv( vbr_methods[mp3->xing_frame->lame_vbr_method], 0 ) ); |
847
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_lowpass", newSViv(mp3->xing_frame->lame_lowpass) ); |
848
|
|
|
|
|
|
|
|
849
|
19
|
100
|
|
|
|
|
if (mp3->xing_frame->lame_replay_gain[0]) { |
850
|
15
|
|
|
|
|
|
my_hv_store( info, "lame_replay_gain_radio", newSVpvf( "%.1f dB", mp3->xing_frame->lame_replay_gain[0] ) ); |
851
|
|
|
|
|
|
|
} |
852
|
|
|
|
|
|
|
|
853
|
19
|
50
|
|
|
|
|
if (mp3->xing_frame->lame_replay_gain[1]) { |
854
|
0
|
|
|
|
|
|
my_hv_store( info, "lame_replay_gain_audiophile", newSVpvf( "%.1f dB", mp3->xing_frame->lame_replay_gain[1] ) ); |
855
|
|
|
|
|
|
|
} |
856
|
|
|
|
|
|
|
|
857
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_encoder_delay", newSViv(mp3->xing_frame->lame_encoder_delay) ); |
858
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_encoder_padding", newSViv(mp3->xing_frame->lame_encoder_padding) ); |
859
|
|
|
|
|
|
|
|
860
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_noise_shaping", newSViv(mp3->xing_frame->lame_noise_shaping) ); |
861
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_stereo_mode", newSVpv( stereo_modes[mp3->xing_frame->lame_stereo_mode], 0 ) ); |
862
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_unwise_settings", newSViv(mp3->xing_frame->lame_unwise) ); |
863
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_source_freq", newSVpv( source_freqs[mp3->xing_frame->lame_source_freq], 0 ) ); |
864
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
// my_hv_store( info, "lame_mp3gain", newSViv(mp3->xing_frame->lame_mp3gain) ); |
866
|
|
|
|
|
|
|
// my_hv_store( info, "lame_mp3gain_db", newSVnv(mp3->xing_frame->lame_mp3gain_db) ); |
867
|
|
|
|
|
|
|
|
868
|
19
|
|
|
|
|
|
my_hv_store( info, "lame_surround", newSVpv( surround[mp3->xing_frame->lame_surround], 0 ) ); |
869
|
|
|
|
|
|
|
|
870
|
19
|
100
|
|
|
|
|
if (mp3->xing_frame->lame_preset < 8) { |
871
|
1
|
|
|
|
|
|
my_hv_store( info, "lame_preset", newSVpvn( "Unknown", 7 ) ); |
872
|
|
|
|
|
|
|
} |
873
|
18
|
100
|
|
|
|
|
else if (mp3->xing_frame->lame_preset <= 320) { |
874
|
10
|
|
|
|
|
|
my_hv_store( info, "lame_preset", newSVpvf( "ABR %d", mp3->xing_frame->lame_preset ) ); |
875
|
|
|
|
|
|
|
} |
876
|
8
|
50
|
|
|
|
|
else if (mp3->xing_frame->lame_preset <= 500) { |
877
|
8
|
|
|
|
|
|
mp3->xing_frame->lame_preset /= 10; |
878
|
8
|
|
|
|
|
|
mp3->xing_frame->lame_preset -= 41; |
879
|
8
|
50
|
|
|
|
|
if ( presets_v[mp3->xing_frame->lame_preset] ) { |
880
|
8
|
|
|
|
|
|
my_hv_store( info, "lame_preset", newSVpv( presets_v[mp3->xing_frame->lame_preset], 0 ) ); |
881
|
|
|
|
|
|
|
} |
882
|
|
|
|
|
|
|
} |
883
|
0
|
0
|
|
|
|
|
else if (mp3->xing_frame->lame_preset >= 1000 && mp3->xing_frame->lame_preset <= 1007) { |
|
|
0
|
|
|
|
|
|
884
|
0
|
|
|
|
|
|
mp3->xing_frame->lame_preset -= 1000; |
885
|
0
|
0
|
|
|
|
|
if ( presets_old[mp3->xing_frame->lame_preset] ) { |
886
|
0
|
|
|
|
|
|
my_hv_store( info, "lame_preset", newSVpv( presets_old[mp3->xing_frame->lame_preset], 0 ) ); |
887
|
|
|
|
|
|
|
} |
888
|
|
|
|
|
|
|
} |
889
|
|
|
|
|
|
|
} |
890
|
|
|
|
|
|
|
|
891
|
85
|
100
|
|
|
|
|
if (mp3->vbr == ABR || mp3->vbr == VBR) { |
|
|
100
|
|
|
|
|
|
892
|
24
|
|
|
|
|
|
my_hv_store( info, "vbr", newSViv(1) ); |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
// DLNA profile detection |
896
|
85
|
100
|
|
|
|
|
if (_is_mp3x_profile(mp3)) |
897
|
8
|
|
|
|
|
|
my_hv_store( info, "dlna_profile", newSVpvn( "MP3X", 4 ) ); |
898
|
77
|
100
|
|
|
|
|
else if (_is_mp3_profile(mp3)) |
899
|
69
|
|
|
|
|
|
my_hv_store( info, "dlna_profile", newSVpvn( "MP3", 3 ) ); |
900
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
out: |
902
|
|
|
|
|
|
|
|
903
|
86
|
|
|
|
|
|
return mp3; |
904
|
|
|
|
|
|
|
} |
905
|
|
|
|
|
|
|
|
906
|
|
|
|
|
|
|
int |
907
|
4
|
|
|
|
|
|
mp3_find_frame(PerlIO *infile, char *file, int offset) |
908
|
|
|
|
|
|
|
{ |
909
|
|
|
|
|
|
|
Buffer mp3_buf; |
910
|
|
|
|
|
|
|
unsigned char *bptr; |
911
|
|
|
|
|
|
|
unsigned int buf_size; |
912
|
|
|
|
|
|
|
struct mp3frame frame; |
913
|
4
|
|
|
|
|
|
int frame_offset = -1; |
914
|
4
|
|
|
|
|
|
HV *info = newHV(); |
915
|
|
|
|
|
|
|
|
916
|
4
|
|
|
|
|
|
mp3info *mp3 = _mp3_parse(infile, file, info); |
917
|
|
|
|
|
|
|
|
918
|
4
|
|
|
|
|
|
buffer_init(&mp3_buf, MP3_BLOCK_SIZE); |
919
|
|
|
|
|
|
|
|
920
|
4
|
50
|
|
|
|
|
if (!mp3->song_length_ms) |
921
|
0
|
|
|
|
|
|
goto out; |
922
|
|
|
|
|
|
|
|
923
|
|
|
|
|
|
|
// (undocumented) If offset is negative, treat it as an absolute file offset in bytes |
924
|
|
|
|
|
|
|
// This is a bit ugly but avoids the need to write an entirely new method |
925
|
4
|
100
|
|
|
|
|
if (offset < 0) { |
926
|
1
|
|
|
|
|
|
frame_offset = abs(offset); |
927
|
1
|
50
|
|
|
|
|
if (frame_offset < mp3->audio_offset) { |
928
|
|
|
|
|
|
|
// Force offset to be at least audio_offset, so we don't end up in an ID3 tag |
929
|
1
|
|
|
|
|
|
frame_offset = mp3->audio_offset; |
930
|
|
|
|
|
|
|
} |
931
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: using absolute offset value %d\n", frame_offset); |
932
|
|
|
|
|
|
|
} |
933
|
|
|
|
|
|
|
else { |
934
|
3
|
50
|
|
|
|
|
if (offset >= mp3->song_length_ms) { |
935
|
0
|
|
|
|
|
|
goto out; |
936
|
|
|
|
|
|
|
} |
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
// Use Xing TOC if available |
939
|
3
|
100
|
|
|
|
|
if ( mp3->xing_frame->has_toc ) { |
940
|
|
|
|
|
|
|
float percent; |
941
|
|
|
|
|
|
|
uint8_t ipercent; |
942
|
|
|
|
|
|
|
uint16_t tva; |
943
|
|
|
|
|
|
|
uint16_t tvb; |
944
|
|
|
|
|
|
|
float tvx; |
945
|
|
|
|
|
|
|
|
946
|
1
|
|
|
|
|
|
percent = (offset * 1.0 / mp3->song_length_ms) * 100; |
947
|
1
|
|
|
|
|
|
ipercent = (int)percent; |
948
|
|
|
|
|
|
|
|
949
|
1
|
50
|
|
|
|
|
if (ipercent > 99) |
950
|
0
|
|
|
|
|
|
ipercent = 99; |
951
|
|
|
|
|
|
|
|
952
|
|
|
|
|
|
|
// Interpolate between 2 TOC points |
953
|
1
|
|
|
|
|
|
tva = mp3->xing_frame->xing_toc[ipercent]; |
954
|
1
|
50
|
|
|
|
|
if (ipercent < 99) { |
955
|
1
|
|
|
|
|
|
tvb = mp3->xing_frame->xing_toc[ipercent + 1]; |
956
|
|
|
|
|
|
|
} |
957
|
|
|
|
|
|
|
else { |
958
|
0
|
|
|
|
|
|
tvb = 256; |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
1
|
|
|
|
|
|
tvx = tva + (tvb - tva) * (percent - ipercent); |
962
|
|
|
|
|
|
|
|
963
|
1
|
|
|
|
|
|
frame_offset = (int)((1.0/256.0) * tvx * mp3->xing_frame->xing_bytes); |
964
|
|
|
|
|
|
|
|
965
|
1
|
|
|
|
|
|
frame_offset += mp3->audio_offset; |
966
|
|
|
|
|
|
|
|
967
|
|
|
|
|
|
|
// Don't return offset == audio_offset, because that would be the Xing frame |
968
|
1
|
50
|
|
|
|
|
if (frame_offset == mp3->audio_offset) { |
969
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: frame_offset == audio_offset, skipping to next frame\n"); |
970
|
1
|
|
|
|
|
|
frame_offset += 1; |
971
|
|
|
|
|
|
|
} |
972
|
|
|
|
|
|
|
|
973
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: using Xing TOC, song_length_ms: %d, percent: %f, tva: %d, tvb: %d, tvx: %f, frame offset: %d\n", |
974
|
|
|
|
|
|
|
mp3->song_length_ms, percent, tva, tvb, tvx, frame_offset |
975
|
|
|
|
|
|
|
); |
976
|
|
|
|
|
|
|
} |
977
|
|
|
|
|
|
|
else { |
978
|
|
|
|
|
|
|
// calculate offset using bitrate |
979
|
2
|
|
|
|
|
|
float bytes_per_ms = mp3->bitrate / 8.0; |
980
|
|
|
|
|
|
|
|
981
|
2
|
|
|
|
|
|
frame_offset = (int)(bytes_per_ms * offset); |
982
|
|
|
|
|
|
|
|
983
|
2
|
|
|
|
|
|
frame_offset += mp3->audio_offset; |
984
|
|
|
|
|
|
|
|
985
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: using bitrate %d, bytes_per_ms: %f, frame offset: %d\n", mp3->bitrate, bytes_per_ms, frame_offset); |
986
|
|
|
|
|
|
|
} |
987
|
|
|
|
|
|
|
} |
988
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
// If frame_offset is too near the end of the file we won't find a valid frame |
990
|
|
|
|
|
|
|
// so require offset to be at least 1000 bytes from the end of the file |
991
|
|
|
|
|
|
|
// XXX this would be more accurate if we determined max_frame_len |
992
|
4
|
100
|
|
|
|
|
if ((mp3->file_size - frame_offset) < 1000) { |
993
|
1
|
|
|
|
|
|
frame_offset -= 1000 - (mp3->file_size - frame_offset); |
994
|
1
|
50
|
|
|
|
|
if (frame_offset < 0) |
995
|
0
|
|
|
|
|
|
frame_offset = 0; |
996
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: offset too close to end of file, adjusted to %d\n", frame_offset); |
997
|
|
|
|
|
|
|
} |
998
|
|
|
|
|
|
|
|
999
|
4
|
|
|
|
|
|
PerlIO_seek(infile, frame_offset, SEEK_SET); |
1000
|
|
|
|
|
|
|
|
1001
|
4
|
50
|
|
|
|
|
if ( !_check_buf(infile, &mp3_buf, 4, MP3_BLOCK_SIZE) ) { |
1002
|
0
|
|
|
|
|
|
frame_offset = -1; |
1003
|
0
|
|
|
|
|
|
goto out; |
1004
|
|
|
|
|
|
|
} |
1005
|
|
|
|
|
|
|
|
1006
|
4
|
|
|
|
|
|
bptr = (unsigned char *)buffer_ptr(&mp3_buf); |
1007
|
4
|
|
|
|
|
|
buf_size = buffer_len(&mp3_buf); |
1008
|
|
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
// Find 0xFF sync and verify it's a valid mp3 frame header |
1010
|
|
|
|
|
|
|
while (1) { |
1011
|
1508
|
50
|
|
|
|
|
if ( |
1012
|
|
|
|
|
|
|
buf_size < 4 |
1013
|
1508
|
100
|
|
|
|
|
|| |
1014
|
12
|
100
|
|
|
|
|
( bptr[0] == 0xFF && !_decode_mp3_frame( bptr, &frame ) ) |
1015
|
|
|
|
|
|
|
) { |
1016
|
|
|
|
|
|
|
break; |
1017
|
|
|
|
|
|
|
} |
1018
|
|
|
|
|
|
|
|
1019
|
1504
|
|
|
|
|
|
bptr++; |
1020
|
1504
|
|
|
|
|
|
buf_size--; |
1021
|
1504
|
|
|
|
|
|
} |
1022
|
|
|
|
|
|
|
|
1023
|
4
|
50
|
|
|
|
|
if (buf_size >= 4) { |
1024
|
4
|
|
|
|
|
|
frame_offset += buffer_len(&mp3_buf) - buf_size; |
1025
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: frame_offset: %d\n", frame_offset); |
1026
|
|
|
|
|
|
|
} |
1027
|
|
|
|
|
|
|
else { |
1028
|
|
|
|
|
|
|
// Didn't find a valid frame, probably too near the end of the file |
1029
|
|
|
|
|
|
|
DEBUG_TRACE("find_frame: did not find a valid frame\n"); |
1030
|
0
|
|
|
|
|
|
frame_offset = -1; |
1031
|
|
|
|
|
|
|
} |
1032
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
out: |
1034
|
4
|
|
|
|
|
|
buffer_free(&mp3_buf); |
1035
|
4
|
|
|
|
|
|
SvREFCNT_dec(info); |
1036
|
|
|
|
|
|
|
|
1037
|
4
|
|
|
|
|
|
buffer_free(mp3->buf); |
1038
|
4
|
|
|
|
|
|
Safefree(mp3->buf); |
1039
|
4
|
|
|
|
|
|
Safefree(mp3->first_frame); |
1040
|
4
|
|
|
|
|
|
Safefree(mp3->xing_frame); |
1041
|
4
|
|
|
|
|
|
Safefree(mp3); |
1042
|
|
|
|
|
|
|
|
1043
|
4
|
|
|
|
|
|
return frame_offset; |
1044
|
|
|
|
|
|
|
} |
1045
|
|
|
|
|
|
|
|
1046
|
|
|
|
|
|
|
void |
1047
|
65
|
|
|
|
|
|
_mp3_skip(mp3info *mp3, uint32_t size) |
1048
|
|
|
|
|
|
|
{ |
1049
|
65
|
100
|
|
|
|
|
if ( buffer_len(mp3->buf) >= size ) { |
1050
|
42
|
|
|
|
|
|
buffer_consume(mp3->buf, size); |
1051
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
DEBUG_TRACE(" skipped buffer data size %d\n", size); |
1053
|
|
|
|
|
|
|
} |
1054
|
|
|
|
|
|
|
else { |
1055
|
23
|
|
|
|
|
|
PerlIO_seek(mp3->infile, size - buffer_len(mp3->buf), SEEK_CUR); |
1056
|
23
|
|
|
|
|
|
buffer_clear(mp3->buf); |
1057
|
|
|
|
|
|
|
|
1058
|
|
|
|
|
|
|
DEBUG_TRACE(" seeked past %d bytes to %d\n", size, (int)PerlIO_tell(mp3->infile)); |
1059
|
|
|
|
|
|
|
} |
1060
|
65
|
|
|
|
|
|
} |