strex: added `detectUrl()`
[iv.d.git] / mp3scan.d
bloba939d380b831464be14c3f9d4a882a96fbb62009
1 /*
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*/;
26 import iv.alice;
29 // ////////////////////////////////////////////////////////////////////////// //
30 enum Mp3Max {
31 FrameSize = 1152, // maxinum frame size
32 Channels = 2,
35 enum Mp3Mode {
36 Stereo = 0,
37 JStereo = 1,
38 Dual = 2,
39 Mono = 3,
43 // ////////////////////////////////////////////////////////////////////////// //
44 struct Mp3Info {
45 uint sampleRate;
46 ubyte channels;
47 ulong samples;
48 Mp3Mode mode;
49 uint bitrate;
50 long id3v1ofs;
51 uint id3v1size;
52 long id3v2ofs;
53 uint id3v2size;
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({
65 ubyte[2] buf;
66 int rd = rdg(buf[]);
67 }))) {
68 Mp3Info info;
69 bool eofhit;
70 ubyte[4096] inbuf;
71 uint inbufpos, inbufused;
72 Mp3Ctx s;
73 uint headersCount;
74 ulong bytesRead = 0;
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;
84 inbufpos = 0;
85 } else {
86 inbufpos = inbufused = 0;
88 // read more bytes
89 while (!eofhit && inbufused < inbuf.length) {
90 int rd = rdg(inbuf[inbufused..$]);
91 if (rd <= 0) {
92 eofhit = true;
93 } else {
94 bytesRead += rd;
95 inbufused += rd;
100 bool skipBytes (uint count) {
101 while (!eofhit && count > 0) {
102 auto left = inbufused-inbufpos;
103 if (left == 0) {
104 fillInputBuffer();
105 } else {
106 if (left <= count) {
107 // eat whole buffer
108 inbufused = inbufpos = 0;
109 count -= left;
110 } else {
111 // eat buffer part
112 inbufpos += count;
113 count = 0;
117 return (count == 0);
120 // now skip frames
121 while (!eofhit) {
122 fillInputBuffer();
123 auto left = inbufused-inbufpos;
124 if (left < Mp3HeaderSize) break;
125 // check for tags
126 // check for ID3v2
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
129 // get tag size
130 uint sz = (inbuf.ptr[inbufpos+9]|(inbuf.ptr[inbufpos+8]<<7)|(inbuf.ptr[inbufpos+7]<<14)|(inbuf.ptr[inbufpos+6]<<21))+10;
131 if (sz > 10) {
132 info.id3v2ofs = streamOfs;
133 info.id3v2size = sz;
135 // skip `sz` bytes, it's a tag
136 skipBytes(sz);
137 continue;
139 // check for ID3v1
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;
144 skipBytes(128);
145 continue;
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); }
149 if (res < 0) {
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
157 } else {
158 // got valid frame
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;
170 if (!info.valid) {
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;
184 return info;
188 // ////////////////////////////////////////////////////////////////////////// //
189 private nothrow @nogc {
192 // ////////////////////////////////////////////////////////////////////////// //
193 struct Mp3Ctx {
194 int frame_size;
195 int error_protection;
196 int sample_rate;
197 int sample_rate_index;
198 int bit_rate;
199 int nb_channels;
200 int sample_count;
201 int mode;
202 int mode_ext;
203 int lsf;
204 uint last_header; //&0xffff0c00u;
205 bool valid;
209 // ////////////////////////////////////////////////////////////////////////// //
210 enum Mp3HeaderSize = 4;
213 // ////////////////////////////////////////////////////////////////////////// //
214 bool mp3CheckHeader (uint header) pure @safe {
215 // header
216 if ((header&0xffe00000u) != 0xffe00000u) return false;
217 // layer check
218 if ((header&(3<<17)) != (1<<17)) return false;
219 // bit rate
220 if ((header&(0xf<<12)) == 0xf<<12) return false;
221 // frequency
222 if ((header&(3<<10)) == 3<<10) return false;
223 // seems to be acceptable
224 return true;
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);
251 mpeg25 = 0;
252 } else {
253 s.lsf = 1;
254 mpeg25 = 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;
274 return true;
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;
286 s.valid = false;
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) {
294 // incomplete frame
295 s.valid = false;
296 } else {
297 s.valid = true;
298 bufleft -= s.frame_size;
299 s.last_header = header&0xffff0c00u;
300 //s.frame_size += extra_bytes;
302 return bufsize-bufleft;
306 ++buf;
307 --bufleft;
309 return -1;
315 // ////////////////////////////////////////////////////////////////////////// //
317 import iv.cmdcon;
318 import iv.vfs;
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);
331 if (!info.valid) {
332 conwriteln("invalid MP3 file!");
333 } else {
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) {
343 if (idx > 4) break;
344 conwriteln(idx, ": ", i);