annotate data/fileio/MP3FileReader.cpp @ 1833:21c792334c2e sensible-delimited-data-strings

Rewrite all the DelimitedDataString stuff so as to return vectors of individual cell strings rather than having the classes add the delimiters themselves. Rename accordingly to names based on StringExport. Take advantage of this in the CSV writer code so as to properly quote cells that contain delimiter characters.
author Chris Cannam
date Fri, 03 Apr 2020 17:11:05 +0100
parents 70e172e6cc59
children 14747f24ad04
rev   line source
Chris@148 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@148 2
Chris@148 3 /*
Chris@148 4 Sonic Visualiser
Chris@148 5 An audio file viewer and annotation editor.
Chris@148 6 Centre for Digital Music, Queen Mary, University of London.
Chris@148 7 This file copyright 2006 Chris Cannam.
Chris@148 8
Chris@148 9 This program is free software; you can redistribute it and/or
Chris@148 10 modify it under the terms of the GNU General Public License as
Chris@148 11 published by the Free Software Foundation; either version 2 of the
Chris@148 12 License, or (at your option) any later version. See the file
Chris@148 13 COPYING included with this distribution for more information.
Chris@148 14 */
Chris@148 15
Chris@148 16 #ifdef HAVE_MAD
Chris@148 17
Chris@148 18 #include "MP3FileReader.h"
Chris@392 19 #include "base/ProgressReporter.h"
Chris@357 20
Chris@150 21 #include "system/System.h"
Chris@148 22
Chris@148 23 #include <sys/types.h>
Chris@148 24 #include <sys/stat.h>
Chris@148 25 #include <fcntl.h>
Chris@148 26
Chris@148 27 #include <iostream>
Chris@148 28
Chris@405 29 #include <cstdlib>
Chris@405 30
Chris@271 31 #ifdef HAVE_ID3TAG
Chris@271 32 #include <id3tag.h>
Chris@271 33 #endif
Chris@1168 34
Chris@1364 35 #ifdef _WIN32
Chris@1364 36 #include <io.h>
Chris@1364 37 #include <fcntl.h>
Chris@1364 38 #else
Chris@1364 39 #include <fcntl.h>
Chris@1364 40 #include <unistd.h>
Chris@1364 41 #endif
Chris@1364 42
Chris@186 43 #include <QFileInfo>
Chris@148 44
Chris@1346 45 #include <QTextCodec>
Chris@1346 46
Chris@1305 47 using std::string;
Chris@1305 48
Chris@1305 49 static sv_frame_t DEFAULT_DECODER_DELAY = 529;
Chris@1305 50
Chris@317 51 MP3FileReader::MP3FileReader(FileSource source, DecodeMode decodeMode,
Chris@1305 52 CacheMode mode, GaplessMode gaplessMode,
Chris@1305 53 sv_samplerate_t targetRate,
Chris@920 54 bool normalised,
Chris@392 55 ProgressReporter *reporter) :
Chris@920 56 CodedAudioFileReader(mode, targetRate, normalised),
Chris@316 57 m_source(source),
Chris@316 58 m_path(source.getLocalFilename()),
Chris@1305 59 m_gaplessMode(gaplessMode),
Chris@1284 60 m_decodeErrorShown(false),
Chris@1582 61 m_decodeThread(nullptr)
Chris@148 62 {
Chris@1279 63 SVDEBUG << "MP3FileReader: local path: \"" << m_path
Chris@1279 64 << "\", decode mode: " << decodeMode << " ("
Chris@1279 65 << (decodeMode == DecodeAtOnce ? "DecodeAtOnce" : "DecodeThreaded")
Chris@1279 66 << ")" << endl;
Chris@1279 67
Chris@148 68 m_channelCount = 0;
Chris@297 69 m_fileRate = 0;
Chris@148 70 m_fileSize = 0;
Chris@148 71 m_bitrateNum = 0;
Chris@148 72 m_bitrateDenom = 0;
Chris@148 73 m_cancelled = false;
Chris@1288 74 m_mp3FrameCount = 0;
Chris@265 75 m_completion = 0;
Chris@263 76 m_done = false;
Chris@392 77 m_reporter = reporter;
Chris@148 78
Chris@1313 79 if (m_gaplessMode == GaplessMode::Gapless) {
Chris@1307 80 CodedAudioFileReader::setFramesToTrim(DEFAULT_DECODER_DELAY, 0);
Chris@1305 81 }
Chris@1305 82
Chris@1348 83 m_fileSize = 0;
Chris@148 84
Chris@1582 85 m_fileBuffer = nullptr;
Chris@1290 86 m_fileBufferSize = 0;
Chris@1290 87
Chris@1582 88 m_sampleBuffer = nullptr;
Chris@1290 89 m_sampleBufferSize = 0;
Chris@977 90
Chris@1348 91 QFile qfile(m_path);
Chris@1348 92 if (!qfile.open(QIODevice::ReadOnly)) {
Chris@1342 93 m_error = QString("Failed to open file %1 for reading.").arg(m_path);
Chris@1342 94 SVDEBUG << "MP3FileReader: " << m_error << endl;
Chris@1342 95 return;
Chris@1343 96 }
Chris@148 97
Chris@1348 98 m_fileSize = qfile.size();
Chris@1348 99
Chris@148 100 try {
Chris@1290 101 // We need a mysterious MAD_BUFFER_GUARD (== 8) zero bytes at
Chris@1290 102 // end of input, to ensure libmad decodes the last frame
Chris@1290 103 // correctly. Otherwise the decoded audio is truncated.
Chris@1401 104 SVDEBUG << "file size = " << m_fileSize << ", buffer guard = " << MAD_BUFFER_GUARD << endl;
Chris@1290 105 m_fileBufferSize = m_fileSize + MAD_BUFFER_GUARD;
Chris@1290 106 m_fileBuffer = new unsigned char[m_fileBufferSize];
Chris@1290 107 memset(m_fileBuffer + m_fileSize, 0, MAD_BUFFER_GUARD);
Chris@148 108 } catch (...) {
Chris@290 109 m_error = QString("Out of memory");
Chris@1342 110 SVDEBUG << "MP3FileReader: " << m_error << endl;
Chris@1342 111 return;
Chris@148 112 }
Chris@1348 113
Chris@1348 114 auto amountRead = qfile.read(reinterpret_cast<char *>(m_fileBuffer),
Chris@1348 115 m_fileSize);
Chris@1348 116
Chris@1348 117 if (amountRead < m_fileSize) {
Chris@1348 118 SVCERR << QString("MP3FileReader::MP3FileReader: Warning: reached EOF after only %1 of %2 bytes")
Chris@1348 119 .arg(amountRead).arg(m_fileSize) << endl;
Chris@1348 120 memset(m_fileBuffer + amountRead, 0, m_fileSize - amountRead);
Chris@1348 121 m_fileSize = amountRead;
Chris@148 122 }
Chris@1348 123
Chris@1348 124 loadTags(qfile.handle());
Chris@148 125
Chris@1348 126 qfile.close();
Chris@271 127
Chris@263 128 if (decodeMode == DecodeAtOnce) {
Chris@263 129
Chris@392 130 if (m_reporter) {
Chris@392 131 connect(m_reporter, SIGNAL(cancelled()), this, SLOT(cancelled()));
Chris@392 132 m_reporter->setMessage
Chris@392 133 (tr("Decoding %1...").arg(QFileInfo(m_path).fileName()));
Chris@327 134 }
Chris@148 135
Chris@1290 136 if (!decode(m_fileBuffer, m_fileBufferSize)) {
Chris@316 137 m_error = QString("Failed to decode file %1.").arg(m_path);
Chris@263 138 }
Chris@1402 139
Chris@1402 140 if (m_sampleBuffer) {
Chris@1402 141 for (int c = 0; c < m_channelCount; ++c) {
Chris@1402 142 delete[] m_sampleBuffer[c];
Chris@1402 143 }
Chris@1402 144 delete[] m_sampleBuffer;
Chris@1582 145 m_sampleBuffer = nullptr;
Chris@1402 146 }
Chris@263 147
Chris@1290 148 delete[] m_fileBuffer;
Chris@1582 149 m_fileBuffer = nullptr;
Chris@148 150
Chris@263 151 if (isDecodeCacheInitialised()) finishDecodeCache();
Chris@398 152 endSerialised();
Chris@263 153
Chris@392 154 } else {
Chris@263 155
Chris@392 156 if (m_reporter) m_reporter->setProgress(100);
Chris@263 157
Chris@263 158 m_decodeThread = new DecodeThread(this);
Chris@263 159 m_decodeThread->start();
Chris@263 160
Chris@386 161 while ((m_channelCount == 0 || m_fileRate == 0 || m_sampleRate == 0)
Chris@386 162 && !m_done) {
Chris@263 163 usleep(10);
Chris@263 164 }
Chris@386 165
Chris@1288 166 SVDEBUG << "MP3FileReader: decoding startup complete, file rate = " << m_fileRate << endl;
Chris@148 167 }
Chris@499 168
Chris@499 169 if (m_error != "") {
Chris@1279 170 SVDEBUG << "MP3FileReader::MP3FileReader(\"" << m_path << "\"): ERROR: " << m_error << endl;
Chris@499 171 }
Chris@148 172 }
Chris@148 173
Chris@148 174 MP3FileReader::~MP3FileReader()
Chris@148 175 {
Chris@263 176 if (m_decodeThread) {
Chris@265 177 m_cancelled = true;
Chris@263 178 m_decodeThread->wait();
Chris@263 179 delete m_decodeThread;
Chris@263 180 }
Chris@148 181 }
Chris@148 182
Chris@263 183 void
Chris@392 184 MP3FileReader::cancelled()
Chris@392 185 {
Chris@392 186 m_cancelled = true;
Chris@392 187 }
Chris@392 188
Chris@392 189 void
Chris@1348 190 MP3FileReader::loadTags(int fd)
Chris@271 191 {
Chris@271 192 m_title = "";
Chris@271 193
Chris@271 194 #ifdef HAVE_ID3TAG
Chris@271 195
Chris@1364 196 #ifdef _WIN32
Chris@1364 197 int id3fd = _dup(fd);
Chris@1364 198 #else
Chris@1364 199 int id3fd = dup(fd);
Chris@1364 200 #endif
Chris@1364 201
Chris@1364 202 id3_file *file = id3_file_fdopen(id3fd, ID3_FILE_MODE_READONLY);
Chris@271 203 if (!file) return;
Chris@271 204
Chris@273 205 // We can do this a lot more elegantly, but we'll leave that for
Chris@273 206 // when we implement support for more than just the one tag!
Chris@273 207
Chris@271 208 id3_tag *tag = id3_file_tag(file);
Chris@273 209 if (!tag) {
Chris@1279 210 SVDEBUG << "MP3FileReader::loadTags: No ID3 tag found" << endl;
Chris@1364 211 id3_file_close(file); // also closes our dup'd fd
Chris@273 212 return;
Chris@271 213 }
Chris@271 214
Chris@333 215 m_title = loadTag(tag, "TIT2"); // work title
Chris@333 216 if (m_title == "") m_title = loadTag(tag, "TIT1");
Chris@1287 217 if (m_title == "") SVDEBUG << "MP3FileReader::loadTags: No title found" << endl;
Chris@273 218
Chris@333 219 m_maker = loadTag(tag, "TPE1"); // "lead artist"
Chris@333 220 if (m_maker == "") m_maker = loadTag(tag, "TPE2");
Chris@1287 221 if (m_maker == "") SVDEBUG << "MP3FileReader::loadTags: No artist/maker found" << endl;
Chris@273 222
Chris@632 223 for (unsigned int i = 0; i < tag->nframes; ++i) {
Chris@632 224 if (tag->frames[i]) {
Chris@632 225 QString value = loadTag(tag, tag->frames[i]->id);
Chris@1287 226 if (value != "") {
Chris@1287 227 m_tags[tag->frames[i]->id] = value;
Chris@1287 228 }
Chris@632 229 }
Chris@632 230 }
Chris@632 231
Chris@1364 232 id3_file_close(file); // also closes our dup'd fd
Chris@273 233
Chris@273 234 #else
Chris@1287 235 SVDEBUG << "MP3FileReader::loadTags: ID3 tag support not compiled in" << endl;
Chris@273 236 #endif
Chris@333 237 }
Chris@273 238
Chris@333 239 QString
Chris@333 240 MP3FileReader::loadTag(void *vtag, const char *name)
Chris@333 241 {
Chris@333 242 #ifdef HAVE_ID3TAG
Chris@333 243 id3_tag *tag = (id3_tag *)vtag;
Chris@333 244
Chris@333 245 id3_frame *frame = id3_tag_findframe(tag, name, 0);
Chris@333 246 if (!frame) {
Chris@1287 247 SVDEBUG << "MP3FileReader::loadTag: No \"" << name << "\" frame found in ID3 tag" << endl;
Chris@333 248 return "";
Chris@333 249 }
Chris@333 250
Chris@333 251 if (frame->nfields < 2) {
Chris@1287 252 cerr << "MP3FileReader::loadTag: WARNING: Not enough fields (" << frame->nfields << ") for \"" << name << "\" in ID3 tag" << endl;
Chris@333 253 return "";
Chris@333 254 }
Chris@333 255
Chris@333 256 unsigned int nstrings = id3_field_getnstrings(&frame->fields[1]);
Chris@333 257 if (nstrings == 0) {
Chris@1287 258 SVDEBUG << "MP3FileReader::loadTag: No strings for \"" << name << "\" in ID3 tag" << endl;
Chris@333 259 return "";
Chris@333 260 }
Chris@333 261
Chris@333 262 id3_ucs4_t const *ustr = id3_field_getstrings(&frame->fields[1], 0);
Chris@333 263 if (!ustr) {
Chris@1287 264 SVDEBUG << "MP3FileReader::loadTag: Invalid or absent data for \"" << name << "\" in ID3 tag" << endl;
Chris@333 265 return "";
Chris@333 266 }
Chris@333 267
Chris@333 268 id3_utf8_t *u8str = id3_ucs4_utf8duplicate(ustr);
Chris@333 269 if (!u8str) {
Chris@1287 270 SVDEBUG << "MP3FileReader::loadTag: ERROR: Internal error: Failed to convert UCS4 to UTF8 in ID3 tag" << endl;
Chris@333 271 return "";
Chris@333 272 }
Chris@333 273
Chris@333 274 QString rv = QString::fromUtf8((const char *)u8str);
Chris@333 275 free(u8str);
Chris@334 276
Chris@1287 277 SVDEBUG << "MP3FileReader::loadTag: Tag \"" << name << "\" -> \""
Chris@1287 278 << rv << "\"" << endl;
Chris@334 279
Chris@333 280 return rv;
Chris@333 281
Chris@333 282 #else
Chris@333 283 return "";
Chris@333 284 #endif
Chris@271 285 }
Chris@271 286
Chris@271 287 void
Chris@263 288 MP3FileReader::DecodeThread::run()
Chris@263 289 {
Chris@1290 290 if (!m_reader->decode(m_reader->m_fileBuffer, m_reader->m_fileBufferSize)) {
Chris@290 291 m_reader->m_error = QString("Failed to decode file %1.").arg(m_reader->m_path);
Chris@263 292 }
Chris@263 293
Chris@1290 294 delete[] m_reader->m_fileBuffer;
Chris@1582 295 m_reader->m_fileBuffer = nullptr;
Chris@297 296
Chris@1290 297 if (m_reader->m_sampleBuffer) {
Chris@929 298 for (int c = 0; c < m_reader->m_channelCount; ++c) {
Chris@1290 299 delete[] m_reader->m_sampleBuffer[c];
Chris@297 300 }
Chris@1290 301 delete[] m_reader->m_sampleBuffer;
Chris@1582 302 m_reader->m_sampleBuffer = nullptr;
Chris@297 303 }
Chris@297 304
Chris@263 305 if (m_reader->isDecodeCacheInitialised()) m_reader->finishDecodeCache();
Chris@263 306
Chris@263 307 m_reader->m_done = true;
Chris@265 308 m_reader->m_completion = 100;
Chris@297 309
Chris@297 310 m_reader->endSerialised();
Chris@263 311 }
Chris@263 312
Chris@148 313 bool
Chris@1038 314 MP3FileReader::decode(void *mm, sv_frame_t sz)
Chris@148 315 {
Chris@148 316 DecoderData data;
Chris@148 317 struct mad_decoder decoder;
Chris@148 318
Chris@148 319 data.start = (unsigned char const *)mm;
Chris@1288 320 data.length = sz;
Chris@1310 321 data.finished = false;
Chris@148 322 data.reader = this;
Chris@148 323
Chris@1288 324 mad_decoder_init(&decoder, // decoder to initialise
Chris@1288 325 &data, // our own data block for callbacks
Chris@1288 326 input_callback, // provides (entire) input to mad
Chris@1582 327 nullptr, // checks header
Chris@1288 328 filter_callback, // filters frame before decoding
Chris@1288 329 output_callback, // receives decoded output
Chris@1288 330 error_callback, // handles decode errors
Chris@1582 331 nullptr); // "message_func"
Chris@1288 332
Chris@148 333 mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
Chris@148 334 mad_decoder_finish(&decoder);
Chris@148 335
Chris@1288 336 SVDEBUG << "MP3FileReader: Decoding complete, decoded " << m_mp3FrameCount
Chris@1288 337 << " mp3 frames" << endl;
Chris@1288 338
Chris@263 339 m_done = true;
Chris@148 340 return true;
Chris@148 341 }
Chris@148 342
Chris@148 343 enum mad_flow
Chris@1288 344 MP3FileReader::input_callback(void *dp, struct mad_stream *stream)
Chris@148 345 {
Chris@148 346 DecoderData *data = (DecoderData *)dp;
Chris@148 347
Chris@1310 348 if (!data->length) {
Chris@1310 349 data->finished = true;
Chris@1310 350 return MAD_FLOW_STOP;
Chris@1310 351 }
Chris@348 352
Chris@348 353 unsigned char const *start = data->start;
Chris@1288 354 sv_frame_t length = data->length;
Chris@348 355
Chris@348 356 #ifdef HAVE_ID3TAG
Chris@1288 357 while (length > ID3_TAG_QUERYSIZE) {
Chris@1038 358 ssize_t taglen = id3_tag_query(start, ID3_TAG_QUERYSIZE);
Chris@1288 359 if (taglen <= 0) {
Chris@1288 360 break;
Chris@348 361 }
Chris@1288 362 SVDEBUG << "MP3FileReader: ID3 tag length to skip: " << taglen << endl;
Chris@1288 363 start += taglen;
Chris@1288 364 length -= taglen;
Chris@348 365 }
Chris@348 366 #endif
Chris@348 367
Chris@348 368 mad_stream_buffer(stream, start, length);
Chris@148 369 data->length = 0;
Chris@148 370
Chris@148 371 return MAD_FLOW_CONTINUE;
Chris@148 372 }
Chris@148 373
Chris@148 374 enum mad_flow
Chris@1288 375 MP3FileReader::filter_callback(void *dp,
Chris@1288 376 struct mad_stream const *stream,
Chris@1288 377 struct mad_frame *frame)
Chris@1288 378 {
Chris@1288 379 DecoderData *data = (DecoderData *)dp;
Chris@1288 380 return data->reader->filter(stream, frame);
Chris@1288 381 }
Chris@1288 382
Chris@1307 383 static string toMagic(unsigned long fourcc)
Chris@1305 384 {
Chris@1305 385 string magic("....");
Chris@1305 386 for (int i = 0; i < 4; ++i) {
Chris@1305 387 magic[3-i] = char((fourcc >> (8*i)) & 0xff);
Chris@1305 388 }
Chris@1305 389 return magic;
Chris@1305 390 }
Chris@1305 391
Chris@1288 392 enum mad_flow
Chris@1288 393 MP3FileReader::filter(struct mad_stream const *stream,
Chris@1288 394 struct mad_frame *)
Chris@1288 395 {
Chris@1289 396 if (m_mp3FrameCount > 0) {
Chris@1305 397 // only handle info frame if it appears as first mp3 frame
Chris@1289 398 return MAD_FLOW_CONTINUE;
Chris@1305 399 }
Chris@1305 400
Chris@1313 401 if (m_gaplessMode == GaplessMode::Gappy) {
Chris@1305 402 // Our non-gapless mode does not even filter out the Xing/LAME
Chris@1305 403 // frame. That's because the main reason non-gapless mode
Chris@1305 404 // exists is for backward compatibility with MP3FileReader
Chris@1305 405 // behaviour before the gapless support was added, so we even
Chris@1305 406 // need to keep the spurious 1152 samples resulting from
Chris@1305 407 // feeding Xing/LAME frame to the decoder as otherwise we'd
Chris@1305 408 // have different output from before.
Chris@1305 409 SVDEBUG << "MP3FileReader: Not gapless mode, not checking Xing/LAME frame"
Chris@1305 410 << endl;
Chris@1305 411 return MAD_FLOW_CONTINUE;
Chris@1305 412 }
Chris@1305 413
Chris@1305 414 struct mad_bitptr ptr = stream->anc_ptr;
Chris@1305 415 string magic = toMagic(mad_bit_read(&ptr, 32));
Chris@1305 416
Chris@1305 417 if (magic == "Xing" || magic == "Info") {
Chris@1305 418
Chris@1305 419 SVDEBUG << "MP3FileReader: Found Xing/LAME metadata frame (magic = \""
Chris@1305 420 << magic << "\")" << endl;
Chris@1305 421
Chris@1305 422 // All we want at this point is the LAME encoder delay and
Chris@1305 423 // padding values. We expect to see the Xing/Info magic (which
Chris@1305 424 // we've already read), then 116 bytes of Xing data, then LAME
Chris@1305 425 // magic, 5 byte version string, 12 bytes of LAME data that we
Chris@1305 426 // aren't currently interested in, then the delays encoded as
Chris@1305 427 // two 12-bit numbers into three bytes.
Chris@1305 428 //
Chris@1305 429 // (See gabriel.mp3-tech.org/mp3infotag.html)
Chris@1305 430
Chris@1305 431 for (int skip = 0; skip < 116; ++skip) {
Chris@1305 432 (void)mad_bit_read(&ptr, 8);
Chris@1305 433 }
Chris@1305 434
Chris@1305 435 magic = toMagic(mad_bit_read(&ptr, 32));
Chris@1305 436
Chris@1305 437 if (magic == "LAME") {
Chris@1305 438
Chris@1305 439 SVDEBUG << "MP3FileReader: Found LAME-specific metadata" << endl;
Chris@1305 440
Chris@1305 441 for (int skip = 0; skip < 5 + 12; ++skip) {
Chris@1305 442 (void)mad_bit_read(&ptr, 8);
Chris@1305 443 }
Chris@1305 444
Chris@1307 445 auto delay = mad_bit_read(&ptr, 12);
Chris@1307 446 auto padding = mad_bit_read(&ptr, 12);
Chris@1305 447
Chris@1305 448 sv_frame_t delayToDrop = DEFAULT_DECODER_DELAY + delay;
Chris@1305 449 sv_frame_t paddingToDrop = padding - DEFAULT_DECODER_DELAY;
Chris@1305 450 if (paddingToDrop < 0) paddingToDrop = 0;
Chris@1305 451
Chris@1305 452 SVDEBUG << "MP3FileReader: LAME encoder delay = " << delay
Chris@1305 453 << ", padding = " << padding << endl;
Chris@1305 454
Chris@1305 455 SVDEBUG << "MP3FileReader: Will be trimming " << delayToDrop
Chris@1306 456 << " samples from start and " << paddingToDrop
Chris@1306 457 << " from end" << endl;
Chris@1305 458
Chris@1307 459 CodedAudioFileReader::setFramesToTrim(delayToDrop, paddingToDrop);
Chris@1305 460
Chris@1305 461 } else {
Chris@1305 462 SVDEBUG << "MP3FileReader: Xing frame has no LAME metadata" << endl;
Chris@1305 463 }
Chris@1305 464
Chris@1305 465 return MAD_FLOW_IGNORE;
Chris@1305 466
Chris@1288 467 } else {
Chris@1305 468 return MAD_FLOW_CONTINUE;
Chris@1288 469 }
Chris@1288 470 }
Chris@1288 471
Chris@1288 472 enum mad_flow
Chris@1288 473 MP3FileReader::output_callback(void *dp,
Chris@1288 474 struct mad_header const *header,
Chris@1288 475 struct mad_pcm *pcm)
Chris@148 476 {
Chris@148 477 DecoderData *data = (DecoderData *)dp;
Chris@148 478 return data->reader->accept(header, pcm);
Chris@148 479 }
Chris@148 480
Chris@148 481 enum mad_flow
Chris@148 482 MP3FileReader::accept(struct mad_header const *header,
Chris@1343 483 struct mad_pcm *pcm)
Chris@148 484 {
Chris@148 485 int channels = pcm->channels;
Chris@148 486 int frames = pcm->length;
Chris@1288 487
Chris@148 488 if (header) {
Chris@1038 489 m_bitrateNum = m_bitrateNum + double(header->bitrate);
Chris@148 490 m_bitrateDenom ++;
Chris@148 491 }
Chris@148 492
Chris@148 493 if (frames < 1) return MAD_FLOW_CONTINUE;
Chris@148 494
Chris@148 495 if (m_channelCount == 0) {
Chris@297 496
Chris@297 497 m_fileRate = pcm->samplerate;
Chris@148 498 m_channelCount = channels;
Chris@297 499
Chris@1354 500 SVDEBUG << "MP3FileReader::accept: file rate = " << pcm->samplerate
Chris@1354 501 << ", channel count = " << channels << ", about to init "
Chris@1354 502 << "decode cache" << endl;
Chris@1354 503
Chris@297 504 initialiseDecodeCache();
Chris@297 505
Chris@297 506 if (m_cacheMode == CacheInTemporaryFile) {
Chris@690 507 // SVDEBUG << "MP3FileReader::accept: channel count " << m_channelCount << ", file rate " << m_fileRate << ", about to start serialised section" << endl;
Chris@297 508 startSerialised("MP3FileReader::Decode");
Chris@297 509 }
Chris@148 510 }
Chris@148 511
Chris@148 512 if (m_bitrateDenom > 0) {
Chris@148 513 double bitrate = m_bitrateNum / m_bitrateDenom;
Chris@148 514 double duration = double(m_fileSize * 8) / bitrate;
Chris@148 515 double elapsed = double(m_frameCount) / m_sampleRate;
Chris@375 516 double percent = 100;
Chris@375 517 if (duration > 0.0) percent = ((elapsed * 100.0) / duration);
Chris@357 518 int p = int(percent);
Chris@357 519 if (p < 1) p = 1;
Chris@357 520 if (p > 99) p = 99;
Chris@392 521 if (m_completion != p && m_reporter) {
Chris@357 522 m_completion = p;
Chris@392 523 m_reporter->setProgress(m_completion);
Chris@148 524 }
Chris@148 525 }
Chris@148 526
Chris@1290 527 if (m_cancelled) {
Chris@1290 528 SVDEBUG << "MP3FileReader: Decoding cancelled" << endl;
Chris@1290 529 return MAD_FLOW_STOP;
Chris@1290 530 }
Chris@148 531
Chris@148 532 if (!isDecodeCacheInitialised()) {
Chris@1354 533 SVDEBUG << "MP3FileReader::accept: fallback case: file rate = " << pcm->samplerate
Chris@1354 534 << ", channel count = " << channels << ", about to init "
Chris@1354 535 << "decode cache" << endl;
Chris@148 536 initialiseDecodeCache();
Chris@148 537 }
Chris@148 538
Chris@1290 539 if (m_sampleBufferSize < size_t(frames)) {
Chris@1290 540 if (!m_sampleBuffer) {
Chris@1290 541 m_sampleBuffer = new float *[channels];
Chris@297 542 for (int c = 0; c < channels; ++c) {
Chris@1582 543 m_sampleBuffer[c] = nullptr;
Chris@297 544 }
Chris@297 545 }
Chris@297 546 for (int c = 0; c < channels; ++c) {
Chris@1290 547 delete[] m_sampleBuffer[c];
Chris@1290 548 m_sampleBuffer[c] = new float[frames];
Chris@297 549 }
Chris@1290 550 m_sampleBufferSize = frames;
Chris@297 551 }
Chris@148 552
Chris@297 553 int activeChannels = int(sizeof(pcm->samples) / sizeof(pcm->samples[0]));
Chris@297 554
Chris@297 555 for (int ch = 0; ch < channels; ++ch) {
Chris@297 556
Chris@297 557 for (int i = 0; i < frames; ++i) {
Chris@297 558
Chris@1343 559 mad_fixed_t sample = 0;
Chris@1343 560 if (ch < activeChannels) {
Chris@1343 561 sample = pcm->samples[ch][i];
Chris@1343 562 }
Chris@1343 563 float fsample = float(sample) / float(MAD_F_ONE);
Chris@297 564
Chris@1290 565 m_sampleBuffer[ch][i] = fsample;
Chris@1343 566 }
Chris@148 567 }
Chris@148 568
Chris@1290 569 addSamplesToDecodeCache(m_sampleBuffer, frames);
Chris@148 570
Chris@1288 571 ++m_mp3FrameCount;
Chris@1288 572
Chris@148 573 return MAD_FLOW_CONTINUE;
Chris@148 574 }
Chris@148 575
Chris@148 576 enum mad_flow
Chris@1288 577 MP3FileReader::error_callback(void *dp,
Chris@1288 578 struct mad_stream *stream,
Chris@1288 579 struct mad_frame *)
Chris@148 580 {
Chris@1284 581 DecoderData *data = (DecoderData *)dp;
Chris@1290 582
Chris@1312 583 sv_frame_t ix = stream->this_frame - data->start;
Chris@1312 584
Chris@1290 585 if (stream->error == MAD_ERROR_LOSTSYNC &&
Chris@1312 586 (data->finished || ix >= data->length)) {
Chris@1310 587 // We are at end of file, losing sync is expected behaviour,
Chris@1310 588 // don't report it
Chris@1290 589 return MAD_FLOW_CONTINUE;
Chris@1290 590 }
Chris@1290 591
Chris@1284 592 if (!data->reader->m_decodeErrorShown) {
Chris@1284 593 char buffer[256];
Chris@1284 594 snprintf(buffer, 255,
cannam@1334 595 "MP3 decoding error 0x%04x (%s) at byte offset %lld",
Chris@1335 596 stream->error, mad_stream_errorstr(stream), (long long int)ix);
Chris@1284 597 SVCERR << "Warning: in file \"" << data->reader->m_path << "\": "
Chris@1284 598 << buffer << " (continuing; will not report any further decode errors for this file)" << endl;
Chris@1284 599 data->reader->m_decodeErrorShown = true;
Chris@1279 600 }
Chris@148 601
Chris@148 602 return MAD_FLOW_CONTINUE;
Chris@148 603 }
Chris@148 604
Chris@157 605 void
Chris@290 606 MP3FileReader::getSupportedExtensions(std::set<QString> &extensions)
Chris@157 607 {
Chris@157 608 extensions.insert("mp3");
Chris@157 609 }
Chris@157 610
Chris@316 611 bool
Chris@316 612 MP3FileReader::supportsExtension(QString extension)
Chris@316 613 {
Chris@316 614 std::set<QString> extensions;
Chris@316 615 getSupportedExtensions(extensions);
Chris@316 616 return (extensions.find(extension.toLower()) != extensions.end());
Chris@316 617 }
Chris@316 618
Chris@316 619 bool
Chris@316 620 MP3FileReader::supportsContentType(QString type)
Chris@316 621 {
Chris@316 622 return (type == "audio/mpeg");
Chris@316 623 }
Chris@316 624
Chris@316 625 bool
Chris@317 626 MP3FileReader::supports(FileSource &source)
Chris@316 627 {
Chris@316 628 return (supportsExtension(source.getExtension()) ||
Chris@316 629 supportsContentType(source.getContentType()));
Chris@316 630 }
Chris@316 631
Chris@316 632
Chris@148 633 #endif