2 * MPEG Audio Layer III decoder
3 * Copyright (c) 2001, 2002 Fabrice Bellard,
4 * (c) 2007 Martin J. Fiedler
6 * D conversion by Ketmar // Invisible Vector
8 * This file is a stripped-down version of the MPEG Audio decoder from
9 * the FFmpeg libavcodec library.
11 * FFmpeg and minimp3 are free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
16 * FFmpeg and minimp3 are distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with FFmpeg; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 module iv
.mp3scan
/*is aliced*/;
29 // ////////////////////////////////////////////////////////////////////////// //
31 FrameSize
= 1152, // maxinum frame size
43 // ////////////////////////////////////////////////////////////////////////// //
55 static struct Index
{ ulong fpos
; ulong samples
; } // samples done before this frame (*multiplied* by channels)
56 Index
[] index
; // WARNING: DON'T STORE SLICES OF IT!
58 @property bool valid () const pure nothrow @safe @nogc { return (sampleRate
!= 0); }
59 @property bool hasID3v1 () const pure nothrow @safe @nogc { return (id3v1size
== 128); }
60 @property bool hasID3v2 () const pure nothrow @safe @nogc { return (id3v2size
> 10); }
64 Mp3Info
mp3Scan(bool buildIndex
=false, RDG
) (scope RDG rdg
) if (is(typeof({
71 uint inbufpos
, inbufused
;
76 ulong streamOfs () { pragma(inline
, true); return bytesRead
-inbufused
+inbufpos
; }
78 void fillInputBuffer () {
79 if (inbufpos
< inbufused
) {
80 import core
.stdc
.string
: memmove
;
81 if (inbufused
-inbufpos
>= 1800) return; // no need to get more data
82 if (inbufpos
> 0) memmove(inbuf
.ptr
, inbuf
.ptr
+inbufpos
, inbufused
-inbufpos
);
83 inbufused
-= inbufpos
;
86 inbufpos
= inbufused
= 0;
89 while (!eofhit
&& inbufused
< inbuf
.length
) {
90 int rd
= rdg(inbuf
[inbufused
..$]);
100 bool skipBytes (uint count
) {
101 while (!eofhit
&& count
> 0) {
102 auto left
= inbufused
-inbufpos
;
108 inbufused
= inbufpos
= 0;
123 auto left
= inbufused
-inbufpos
;
124 if (left
< Mp3HeaderSize
) break;
127 if (info
.id3v2size
== 0 && left
>= 10 && inbuf
.ptr
[inbufpos
+0] == 'I' && inbuf
.ptr
[inbufpos
+1] == 'D' && inbuf
.ptr
[inbufpos
+2] == '3' && inbuf
.ptr
[inbufpos
+3] != 0xff && inbuf
.ptr
[inbufpos
+4] != 0xff &&
128 ((inbuf
.ptr
[inbufpos
+6]|inbuf
.ptr
[inbufpos
+7]|inbuf
.ptr
[inbufpos
+8]|inbuf
.ptr
[inbufpos
+9])&0x80) == 0) { // see ID3v2 specs
130 uint sz
= (inbuf
.ptr
[inbufpos
+9]|
(inbuf
.ptr
[inbufpos
+8]<<7)|
(inbuf
.ptr
[inbufpos
+7]<<14)|
(inbuf
.ptr
[inbufpos
+6]<<21))+10;
132 info
.id3v2ofs
= streamOfs
;
135 // skip `sz` bytes, it's a tag
140 if (info
.id3v1size
== 0 && left
>= 3 && inbuf
.ptr
[inbufpos
+0] == 'T' && inbuf
.ptr
[inbufpos
+1] == 'A' && inbuf
.ptr
[inbufpos
+2] == 'G') {
141 // this may be ID3v1, just skip 128 bytes
142 info
.id3v1ofs
= streamOfs
;
143 info
.id3v1size
= 128;
147 int res
= mp3SkipFrame(s
, inbuf
.ptr
+inbufpos
, left
); // return bytes used in buffer or -1
148 debug(mp3scan_verbose
) { import core
.stdc
.stdio
: printf
; printf("FRAME: res=%d; valid=%u; frame_size=%d; samples=%d (%u) read=%u (inbufpos=%u; inbufused=%u)\n", res
, (s
.valid ?
1 : 0), s
.frame_size
, s
.sample_count
, headersCount
, cast(uint)bytesRead
, inbufpos
, inbufused
); }
150 // no frame found in the buffer, get more data
151 // but left at least Mp3HeaderSize old bytes
152 assert(inbufused
>= Mp3HeaderSize
);
153 inbufpos
= inbufused
-Mp3HeaderSize
;
154 } else if (!s
.valid
) {
155 // got header, but there is not enough data for it
156 inbufpos
+= (res
> Mp3HeaderSize ? res
-Mp3HeaderSize
: 1); // move to header
159 static if (buildIndex
) {
160 auto optr
= info
.index
.ptr
;
161 info
.index
~= info
.Index(streamOfs
, info
.samples
);
162 if (info
.index
.ptr
!is optr
) {
163 import core
.memory
: GC
;
164 if (info
.index
.ptr
is GC
.addrOf(info
.index
.ptr
)) GC
.setAttr(info
.index
.ptr
, GC
.BlkAttr
.NO_INTERIOR
);
165 //GC.collect(); // somehow this fixes amper
168 inbufpos
+= res
; // move past frame
169 if (++headersCount
== 0) headersCount
= uint.max
;
171 // if first found frame is invalid... consider the whole file invalid
172 if (s
.sample_rate
< 1024 || s
.sample_rate
> 96000) break;
173 if (s
.nb_channels
< 1 || s
.nb_channels
> 2) break;
174 info
.sampleRate
= s
.sample_rate
;
175 info
.channels
= cast(ubyte)s
.nb_channels
;
176 info
.mode
= cast(Mp3Mode
)s
.mode
;
177 info
.bitrate
= s
.bit_rate
;
179 info
.samples
+= s
.sample_count
*s
.nb_channels
;
182 debug(mp3scan_verbose
) { import core
.stdc
.stdio
: printf
, fflush
, stdout
; printf("%u\n", headersCount
); fflush(stdout
); }
183 if (headersCount
< 6) info
= info
.init
;
188 // ////////////////////////////////////////////////////////////////////////// //
189 private nothrow @nogc {
192 // ////////////////////////////////////////////////////////////////////////// //
195 int error_protection
;
197 int sample_rate_index
;
204 uint last_header
; //&0xffff0c00u;
209 // ////////////////////////////////////////////////////////////////////////// //
210 enum Mp3HeaderSize
= 4;
213 // ////////////////////////////////////////////////////////////////////////// //
214 bool mp3CheckHeader (uint header
) pure @safe {
216 if ((header
&0xffe00000u
) != 0xffe00000u
) return false;
218 if ((header
&(3<<17)) != (1<<17)) return false;
220 if ((header
&(0xf<<12)) == 0xf<<12) return false;
222 if ((header
&(3<<10)) == 3<<10) return false;
223 // seems to be acceptable
228 bool mp3DecodeHeader (ref Mp3Ctx s
, uint header
) @trusted {
229 static immutable ushort[15][2] mp3_bitrate_tab
= [
230 [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 ],
231 [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]
234 static immutable ushort[3] mp3_freq_tab
= [ 44100, 48000, 32000 ];
236 static immutable short[4][4] sampleCount
= [
237 [0, 576, 1152, 384], // v2.5
238 [0, 0, 0, 0], // reserved
239 [0, 576, 1152, 384], // v2
240 [0, 1152, 1152, 384], // v1
242 ubyte mpid
= (header
>>19)&0x03;
243 ubyte layer
= (header
>>17)&0x03;
245 s
.sample_count
= sampleCount
.ptr
[mpid
].ptr
[layer
];
247 int sample_rate
, frame_size
, mpeg25
, padding
;
248 int sample_rate_index
, bitrate_index
;
249 if (header
&(1<<20)) {
250 s
.lsf
= (header
&(1<<19) ?
0 : 1);
257 sample_rate_index
= (header
>>10)&3;
258 sample_rate
= mp3_freq_tab
[sample_rate_index
]>>(s
.lsf
+mpeg25
);
259 sample_rate_index
+= 3*(s
.lsf
+mpeg25
);
260 s
.sample_rate_index
= sample_rate_index
;
261 s
.error_protection
= ((header
>>16)&1)^
1;
262 s
.sample_rate
= sample_rate
;
264 bitrate_index
= (header
>>12)&0xf;
265 padding
= (header
>>9)&1;
266 s
.mode
= (header
>>6)&3;
267 s
.mode_ext
= (header
>>4)&3;
268 s
.nb_channels
= (s
.mode
== Mp3Mode
.Mono ?
1 : 2);
270 if (bitrate_index
== 0) return false; // no frame size computed, signal it
271 frame_size
= mp3_bitrate_tab
[s
.lsf
][bitrate_index
];
272 s
.bit_rate
= frame_size
*1000;
273 s
.frame_size
= (frame_size
*144000)/(sample_rate
<<s
.lsf
)+padding
;
278 // return bytes used in `buf` or -1 if no header was found
279 // will set `s.valid` if fully valid header was skipped
280 // i.e. if it returned something that is not -1, and s.valid == false,
281 // it means that frame header is ok at the given offset, but there is
282 // no data to skip the full header; so caller may read more data and
283 // restart from that offset.
284 int mp3SkipFrame (ref Mp3Ctx s
, const(void)* buff
, int bufsize
) @trusted {
285 auto buf
= cast(const(ubyte)*)buff
;
287 int bufleft
= bufsize
;
288 while (bufleft
>= Mp3HeaderSize
) {
289 uint header
= (buf
[0]<<24)|
(buf
[1]<<16)|
(buf
[2]<<8)|buf
[3];
290 if (mp3CheckHeader(header
)) {
291 if (s
.last_header
== 0 ||
(header
&0xffff0c00u
) == s
.last_header
) {
292 if (mp3DecodeHeader(s
, header
)) {
293 if (s
.frame_size
<= 0 || s
.frame_size
> bufleft
) {
298 bufleft
-= s
.frame_size
;
299 s
.last_header
= header
&0xffff0c00u
;
300 //s.frame_size += extra_bytes;
302 return bufsize
-bufleft
;
315 // ////////////////////////////////////////////////////////////////////////// //
320 void main (string[] args) {
321 if (args.length == 1) args ~= "melodie_128.mp3";
323 auto fl = VFile(args[1]);
325 auto info = mp3Scan!true((void[] buf) {
326 auto rd = fl.rawRead(buf[]);
327 return cast(uint)rd.length;
329 //conwriteln(fl.tell);
332 conwriteln("invalid MP3 file!");
334 conwriteln("sample rate: ", info.sampleRate, " Hz");
335 conwriteln("channels : ", info.channels);
336 conwriteln("samples : ", info.samples);
337 conwriteln("mode : ", info.mode);
338 conwriteln("bitrate : ", info.bitrate/1000, " kbps");
339 auto seconds = info.samples/info.sampleRate;
340 conwritefln!"time: %2s:%02s"(seconds/60, seconds%60);
341 if (info.index.length) conwriteln(info.index.length, " index entries");
342 foreach (immutable idx, const ref i; info.index) {
344 conwriteln(idx, ": ", i);