disable the unrecognized nls and x flags
[AROS-Contrib.git] / FryingPan / DTLib / rMP3Audio.cpp
blobd7dfa1719456168965698121c00ce34fa4b50521
1 /*
2 * FryingPan - Amiga CD/DVD Recording Software (User Interface and supporting Libraries only)
3 * Copyright (C) 2001-2011 Tomasz Wiszkowski Tomasz.Wiszkowski at gmail.com
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 #include "rMP3Audio.h"
21 #include <libclass/dos.h>
22 #include <libclass/exec.h>
23 #include <libclass/utility.h>
24 #include <LibC/LibC.h>
26 #define FLIP16(a) ((((a)&0xff00)>>8) | (((a)&0xff)<<8))
27 #define MKID(a,b,c,d) (((a&255)<<24)|((b&255)<<16)|((c&255)<<8)|(d&255))
28 #define ID_ID3 MKID('I','D','3','\000')
30 bool rMP3Audio::sNoMpegaReported = false;
32 IFileReader *rMP3Audio::openRead(const char* sFile, EDtError &rc)
34 rMP3Audio *pSkel = 0;
35 if (true == checkFile(sFile, rc))
37 pSkel = new rMP3Audio(sFile, rc);
39 return pSkel;
42 rMP3Audio::rMP3Audio(const char *sName, EDtError &rc)
43 : FileReader(sName, rc)
45 if (rc != DT_OK)
46 return;
48 // set pStream to null == we do not have anything yet
49 pStream = 0;
50 handle = 0;
51 hAccess.Initialize(this, &rMP3Audio::access);
53 for (int i=0; i<MPEGA_MAX_CHANNELS; i++)
55 pPCM[i] = new uint16[MPEGA_PCM_SIZE];
58 pMPEGA = MPEGAIFace::GetInstance(MPEGA_VERSION);
59 _D(Lvl_Info, "%s: MPEGA Interface at %lx",
60 (int)__PRETTY_FUNCTION__,
61 (int)pMPEGA);
63 if (pMPEGA == NULL)
65 rc = DT_RequiredResourceNotAvailable;
66 return;
69 // initialize mpega_ctrl
70 fillControl(&hControl);
72 setBlockSize(2352);
73 setLittleEndian(false);
74 setType(Data_Audio);
75 setDataSize(0);
77 handle = DOS->Open((char*)getFileName(), MODE_OLDFILE);
79 readHeaders(rc);
82 rMP3Audio::~rMP3Audio()
84 for (int i=0; i<MPEGA_MAX_CHANNELS; i++)
86 delete [] pPCM[i];
89 if (0 != handle)
90 DOS->Close(handle);
92 if (NULL != pMPEGA)
94 pStream = 0;
95 pMPEGA->FreeInstance();
96 _D(Lvl_Info, "%s: MPEGA Interface disposed", (int)__PRETTY_FUNCTION__);
97 pMPEGA = 0;
101 void rMP3Audio::readHeaders(EDtError &rc)
103 MPEGA_STREAM *pStream;
106 * FIRST OF ALL determine the correct beginning and end of stream
107 * since MPEGA is freaking unable to do that.
110 uint8 x[256];
111 uint32 sz;
113 stream_size = 0; // we will modify this value slightly...
115 // 1. check for tag at the end
116 DOS->Seek(handle, -128, OFFSET_END);
117 DOS->Read(handle, &x, 128);
118 if ((x[0]== 'T') && (x[1] == 'A') && (x[2] == 'G'))
120 stream_size -= 128;
121 _D(Lvl_Info, "%s: found ID3v1 tag. Structure size: 128 bytes", (uint)__PRETTY_FUNCTION__);
123 else if ((x[118] == '3') && (x[119] == 'D') && (x[120] == 'I'))
125 stream_size -= 10;
126 _D(Lvl_Info, "%s: found ID3v2.%ld.%ld footer. Structure size: 10 bytes", (uint)__PRETTY_FUNCTION__, x[121], x[122]);
129 // 2. get file length (now we can)
130 sz = DOS->Seek(handle, 0, OFFSET_BEGINNING);
131 stream_size += sz;
133 // 3. check for id3v2
134 DOS->Read(handle, &x, 10);
135 if ((x[0] == 'I') && (x[1] == 'D') && (x[2] == '3'))
137 sz = (x[6]<<21) | (x[7]<<14) | (x[8]<<7) | (x[9]);
138 stream_start = sz+10;
139 stream_size -= stream_start;
140 _D(Lvl_Info, "%s: found ID3v2.%ld.%ld tag. Structure size: %ld bytes", (uint)__PRETTY_FUNCTION__, x[3], x[4], sz);
144 // 4. set remaining fields
145 current_location = 0;
148 pStream = pMPEGA->MPEGA_open(getFileName(), &hControl);
149 ASSERTS(NULL != pStream, "Verify routine passed stream that does not open!\nPlease do NOT use this track.");
150 if (NULL == pStream)
151 return;
153 _D(Lvl_Info, "%s: stream %08lx opened, len: %ld ms br: %ld, fr: %ld, ch: %ld",
154 (int)__PRETTY_FUNCTION__,
155 (int)pStream,
156 pStream->ms_duration,
157 pStream->bitrate,
158 pStream->frequency,
159 pStream->channels);
161 unsigned long lLen = 0;
163 pMPEGA->MPEGA_seek(pStream, pStream->ms_duration);
164 pMPEGA->MPEGA_time(pStream, &lLen);
165 pMPEGA->MPEGA_seek(pStream, 0);
167 _D(Lvl_Info, "%s: MPEGA claims this file is %ldms long. This file turns out to be %ldms",
168 (int)__PRETTY_FUNCTION__,
169 pStream->ms_duration,
170 lLen);
172 //lLen = pStream->ms_duration;
173 lLen *= 1764; // we need 44100Hz, *2 (stereo), *2 (16bit), that is 176400 bytes per second
174 lLen /= 10;
175 lLen /= getBlockSize(); //
176 lLen *= getBlockSize(); // round down to nearest multiplicity of block size
178 setDataSize(lLen);
180 pMPEGA->MPEGA_close(pStream);
182 return;
185 bool rMP3Audio::checkFile(const char* sFileName, EDtError &rc)
187 Library *pBase;
188 bool bRes;
189 BPTR fh;
191 // 1. verify we have the library.
192 // DPrintfA("Accessing mpega...\n");
194 rc = DT_RequiredResourceNotAvailable;
196 pBase = Exec->OpenLibrary("mpega.library", MPEGA_VERSION);
198 if ((false == sNoMpegaReported) && (NULL == pBase))
200 request("Information",
201 "MPEGA library seems to be missing.\nAs a result, your MP3 files will not work.\n\nThis message will not pop up any more.",
202 "Ok", 0);
204 sNoMpegaReported = true;
205 return false;
208 ASSERT(pBase != NULL);
209 Exec->CloseLibrary(pBase);
210 pBase = 0;
212 // 2. verify the file is EXACTLY what we are looking for.
213 // DPrintfA("accessing file...\n");
215 rc = DT_UnableToOpenFile;
217 fh = DOS->Open(const_cast<char*>(sFileName), MODE_OLDFILE);
218 if (0 == fh)
219 return false;
221 // DPrintfA("checking file...\n");
222 bRes = checkMP3(fh, rc);
223 DOS->Close(fh);
224 return bRes;
227 void rMP3Audio::dispose()
229 delete this;
232 const char *rMP3Audio::static_getName()
234 return "MP3 Audio Track";
237 bool rMP3Audio::static_isAudio()
239 return true;
242 bool rMP3Audio::static_isSession()
244 return false;
247 bool rMP3Audio::static_isData()
249 return false;
252 bool rMP3Audio::isAudio()
254 return static_isAudio();
257 bool rMP3Audio::isData()
259 return static_isData();
262 const char *rMP3Audio::getName()
264 return static_getName();
267 void rMP3Audio::fillControl(MPEGA_CTRL *pCtrl)
269 pCtrl->bs_access = const_cast<Hook*>(hAccess.GetHook());
270 pCtrl->check_mpeg = false;
271 pCtrl->stream_buffer_size = 0;
272 pCtrl->layer_3.force_mono = false;
273 pCtrl->layer_3.stereo.freq_div = 1;
274 pCtrl->layer_3.stereo.freq_max = 44100;
275 pCtrl->layer_3.stereo.quality = 2;
276 pCtrl->layer_3.mono.freq_div = 1;
277 pCtrl->layer_3.mono.freq_max = 44100;
278 pCtrl->layer_3.mono.quality = 2;
279 pCtrl->layer_1_2.force_mono = false;
280 pCtrl->layer_1_2.stereo.freq_div = 1;
281 pCtrl->layer_1_2.stereo.freq_max = 44100;
282 pCtrl->layer_1_2.stereo.quality = 2;
283 pCtrl->layer_1_2.mono.freq_div = 1;
284 pCtrl->layer_1_2.mono.freq_max = 44100;
285 pCtrl->layer_1_2.mono.quality = 2;
288 bool rMP3Audio::checkMP3(BPTR file, EDtError &rc)
290 unsigned char *buf = 0;
291 int len;
292 bool res = false;
293 int offset = 0;
294 int sync = 0;
295 int framelen = 0;
297 rc = DT_OutOfMemory;
298 buf = new unsigned char[131072];
299 if (NULL == buf)
300 return false;
302 len = DOS->Read(file, buf, 131072);
303 if (len != 131072)
305 delete [] buf;
306 rc = DT_FileMalformed;
307 return false;
310 // if we havent found anything within first 8k, there is no valid stream.
311 // we search only first 8kB for starting frame, but
312 // then we look up the whole 128kB for frame headers.
314 rc = DT_OK;
316 while (offset < 8192)
318 framelen = verifyFrame(&buf[offset], rc);
319 rc = DT_OK;
321 if (framelen > 0)
323 sync = offset;
324 while (sync < 131000) // do not increase!
326 framelen = verifyFrame(&buf[sync], rc);
327 sync += framelen;
328 if (framelen == 0)
329 break;
330 if (rc != DT_OK)
331 break;
334 if (rc != DT_OK)
335 break;
337 if (framelen == 0)
339 sync = 0;
340 offset++;
342 else
344 res = true;
345 break;
348 else
350 offset++;
353 delete [] buf;
354 DOS->Seek(file, 0, OFFSET_BEGINNING);
355 return res;
358 long rMP3Audio::verifyFrame(unsigned char* frame, EDtError &rc)
360 int bitrate = 0;
361 int pad = 0;
363 if (frame[0] != 0xff) // check sync
365 rc = DT_OK;
366 return 0;
368 if ((frame[1] & 0xfa) != 0xfa) // check valid type (mpeg 1 layer 3)
370 rc = DT_FileFormatNotSupported;
371 return 0;
373 if ((frame[2] & 0xf0) == 0xf0) // check valid bit rate
375 return 0;
377 if ((frame[2] & 0xc) != 0x0) // check valid freq
379 rc = DT_WrongFrequency;
380 return 0;
382 if ((frame[3] & 0xc0) == 0xc0) // check valid mode (stereo)
384 rc = DT_WrongChannelCount;
385 return 0;
388 switch ((frame[2]&0xf0) >> 4)
390 case 1: bitrate = 32; break;
391 case 2: bitrate = 40; break;
392 case 3: bitrate = 48; break;
393 case 4: bitrate = 56; break;
394 case 5: bitrate = 64; break;
395 case 6: bitrate = 80; break;
396 case 7: bitrate = 96; break;
397 case 8: bitrate = 112; break;
398 case 9: bitrate = 128; break;
399 case 10: bitrate = 160; break;
400 case 11: bitrate = 192; break;
401 case 12: bitrate = 224; break;
402 case 13: bitrate = 256; break;
403 case 14: bitrate = 320; break;
405 pad = (frame[2]&02) ? 1 : 0;
407 //DPrintfA("I think I found sync. Frame len: %ld bytes\n", ((1440*bitrate)/441)+pad);
409 return ((1440*bitrate)/441)+pad;
412 bool rMP3Audio::setUp()
414 int err;
416 pStream = pMPEGA->MPEGA_open(getFileName(), &hControl);
418 ASSERT(NULL != pStream);
419 if (pStream == NULL)
420 return false;
422 lPCMOffset = 0;
423 lSamples = 0;
424 lErrors = 0;
426 err = pMPEGA->MPEGA_seek(pStream, 1);
427 (void)err; // Unused if not debugging
428 _D(Lvl_Info, "%s: Initialized MPEGA stream (%08lx): error code %ld",
429 (int)__PRETTY_FUNCTION__,
430 (int)pStream,
431 err);
432 return true;
435 void rMP3Audio::cleanUp()
437 if (pStream != 0)
438 pMPEGA->MPEGA_close(pStream);
439 pStream = 0;
440 _D(Lvl_Info, "%s: MPEGA stream closed.",
441 (int)__PRETTY_FUNCTION__);
444 bool rMP3Audio::readData(const IOptItem* item, void* buff, int lBlocks)
446 uint16 *pBuff = (uint16*)buff;
447 uint32 lLen = lBlocks * getBlockSize();
449 ASSERT(NULL != pStream);
450 if (pStream == NULL)
451 return false;
453 while (lLen > 0)
455 if (lPCMOffset >= lSamples)
456 lPCMOffset = 0;
457 if (0 == lPCMOffset)
459 // if anything fails, do not return garbage, ok?
460 lSamples = pMPEGA->MPEGA_decode_frame(pStream, pPCM);
461 _D(Lvl_Info, "%s: Decoding MPEGA frames (stream %lx, buffer %lx): %ld",
462 (IPTR)__PRETTY_FUNCTION__,
463 (int)pStream,
464 (int)pPCM,
465 lSamples);
466 if (lSamples < 0)
468 if (lSamples != MPEGA_ERR_EOF)
470 lErrors++;
471 if (lErrors == 64)
473 request("Info", "Its been 64 'faulty' frames now and most likely it is going to increase\nI doubt this is the problem with MP3 file (but who knows?)\nIf problem persists, mail the brilliant author of your mpega.library\nI keep getting %ld.", "Proceed", ARRAY((IPTR)lSamples));
476 lSamples = MPEGA_PCM_SIZE;
477 memset(pPCM[0], 0, MPEGA_PCM_SIZE);
478 memset(pPCM[1], 0, MPEGA_PCM_SIZE);
480 // do nothing. things will not get copied anyways
483 // clone data to target buffer. remember to reverse byte order.
484 while (lPCMOffset<lSamples)
486 // clone reversed samples.
487 *pBuff = FLIP16(pPCM[0][lPCMOffset]);
488 pBuff++;
489 *pBuff = FLIP16(pPCM[1][lPCMOffset]);
490 pBuff++;
491 lLen -= 4;
492 lPCMOffset++;
493 // this loop will never begin with lLen = 0. see outer while loop.
494 if (lLen < 4)
495 break;
499 return true;
502 uint rMP3Audio::access(void* , MPEGA_ACCESS *acc)
504 register uint32 d;
506 switch (acc->func)
508 case MPEGA_BSFUNC_OPEN:
510 * since our stream is already open, we won't re-open it again.
512 acc->data.open.stream_size = stream_size;
513 DOS->Seek(handle, stream_start, OFFSET_BEGINNING);
514 current_location = 0;
515 _D(Lvl_Info, "%s: MPEGA_OPEN - Opening virtual stream. Current location: %ld", (uint)__PRETTY_FUNCTION__, current_location);
516 return (uint)this;
518 case MPEGA_BSFUNC_CLOSE:
520 * do nothing. we close everything on dispose()
522 _D(Lvl_Info, "%s: MPEGA_CLOSE - Disposing virtual stream.", (uint)__PRETTY_FUNCTION__);
523 return 0;
525 case MPEGA_BSFUNC_READ:
527 * that is fairly easy. just read()
529 if (current_location >= stream_size)
531 _D(Lvl_Info, "%s: MPEGA_READ - no more data", (uint)__PRETTY_FUNCTION__);
532 return 0;
535 d = DOS->Read(handle, acc->data.read.buffer,
536 (unsigned)acc->data.read.num_bytes < (stream_size - current_location) ? (unsigned)acc->data.read.num_bytes : (stream_size - current_location));
537 current_location += d;
538 _D(Lvl_Info, "%s: MPEGA_READ - Read %ld bytes. Advancing position to %ld.", (uint)__PRETTY_FUNCTION__, d, current_location);
539 return d;
541 case MPEGA_BSFUNC_SEEK:
542 current_location = ((unsigned)acc->data.seek.abs_byte_seek_pos < stream_size ? (unsigned)acc->data.seek.abs_byte_seek_pos : stream_size);
543 _D(Lvl_Info, "%s: MPEGA_SEEK - Seeking to relative location %ld.", (uint)__PRETTY_FUNCTION__, current_location);
544 DOS->Seek(handle, current_location + stream_start, OFFSET_BEGINNING);
545 return 0;
548 _D(Lvl_Warning, "%s: MPEGA_xxx - Your MPEGA library just asked me to do something I never heard of (%ld)", (uint)__PRETTY_FUNCTION__, acc->func);
549 ASSERTS(false, "Your mpega.library has just asked me\nto do something funny.");
550 return 0;