![]() |
Mixxx
|
00001 00002 #include <math.h> 00003 00004 #include <QtDebug> 00005 #include <QFileInfo> 00006 00007 #include "controlobject.h" 00008 #include "controlobjectthread.h" 00009 00010 #include "cachingreader.h" 00011 #include "trackinfoobject.h" 00012 #include "soundsourceproxy.h" 00013 #include "sampleutil.h" 00014 00015 00016 // There's a little math to this, but not much: 48khz stereo audio is 384kb/sec 00017 // if using float samples. We want the chunk size to be a power of 2 so it's 00018 // easier to memory align, and roughly 1/2 - 1/4th of a second of audio. 2**17 00019 // and 2**16 are nice candidates. 2**16 is 170ms of audio, which is well above 00020 // (hopefully) the latencies people are seeing. at 10ms latency, one chunk is 00021 // enough for 17 callbacks. We may need to tweak this later. 00022 00023 // Must be divisible by 8, 4, and 2. Just pick a power of 2. 00024 #define CHUNK_LENGTH 65536 00025 //#define CHUNK_LENGTH 524288 00026 00027 const int CachingReader::kChunkLength = CHUNK_LENGTH; 00028 const int CachingReader::kSamplesPerChunk = CHUNK_LENGTH / sizeof(CSAMPLE); 00029 00030 CachingReader::CachingReader(const char* _group, 00031 ConfigObject<ConfigValue>* _config) : 00032 m_pGroup(_group), 00033 m_pConfig(_config), 00034 m_chunkReadRequestFIFO(1024), 00035 m_readerStatusFIFO(1024), 00036 m_readerStatus(INVALID), 00037 m_mruChunk(NULL), 00038 m_lruChunk(NULL), 00039 m_pRawMemoryBuffer(NULL), 00040 m_pCurrentSoundSource(NULL), 00041 m_iTrackSampleRate(0), 00042 m_iTrackNumSamples(0), 00043 m_iTrackNumSamplesCallbackSafe(0), 00044 m_pSample(NULL) { 00045 initialize(); 00046 } 00047 00048 CachingReader::~CachingReader() { 00049 m_freeChunks.clear(); 00050 m_allocatedChunks.clear(); 00051 m_lruChunk = m_mruChunk = NULL; 00052 00053 for (int i=0; i < m_chunks.size(); i++) { 00054 Chunk* c = m_chunks[i]; 00055 delete c; 00056 } 00057 00058 delete [] m_pSample; 00059 00060 delete [] m_pRawMemoryBuffer; 00061 m_pRawMemoryBuffer = NULL; 00062 00063 delete m_pCurrentSoundSource; 00064 } 00065 00066 void CachingReader::initialize() { 00067 int memory_to_use = 5000000; // 5mb, TODO 00068 Q_ASSERT(memory_to_use >= kChunkLength); 00069 00070 // Only allocate as many bytes as we will actually use. 00071 memory_to_use -= (memory_to_use % kChunkLength); 00072 00073 m_pSample = new SAMPLE[kSamplesPerChunk]; 00074 00075 Q_ASSERT(kSamplesPerChunk * sizeof(CSAMPLE) == kChunkLength); 00076 00077 int total_chunks = memory_to_use / kChunkLength; 00078 00079 qDebug() << "CachingReader using" << memory_to_use << "bytes."; 00080 00081 int rawMemoryBufferLength = kSamplesPerChunk * total_chunks; 00082 m_pRawMemoryBuffer = new CSAMPLE[rawMemoryBufferLength]; 00083 00084 m_allocatedChunks.reserve(total_chunks); 00085 00086 CSAMPLE* bufferStart = m_pRawMemoryBuffer; 00087 00088 // Divide up the allocated raw memory buffer into total_chunks 00089 // chunks. Initialize each chunk to hold nothing and add it to the free 00090 // list. 00091 for (int i=0; i < total_chunks; i++) { 00092 Chunk* c = new Chunk; 00093 c->chunk_number = -1; 00094 c->length = 0; 00095 c->data = bufferStart; 00096 c->next_lru = NULL; 00097 c->prev_lru = NULL; 00098 00099 m_chunks.push_back(c); 00100 m_freeChunks.push_back(c); 00101 00102 bufferStart += kSamplesPerChunk; 00103 } 00104 } 00105 00106 // static 00107 Chunk* CachingReader::removeFromLRUList(Chunk* chunk, Chunk* head) { 00108 Q_ASSERT(chunk); 00109 00110 // Remove chunk from the doubly-linked list. 00111 Chunk* next = chunk->next_lru; 00112 Chunk* prev = chunk->prev_lru; 00113 00114 if (next) { 00115 next->prev_lru = prev; 00116 } 00117 00118 if (prev) { 00119 prev->next_lru = next; 00120 } 00121 00122 chunk->next_lru = NULL; 00123 chunk->prev_lru = NULL; 00124 00125 if (chunk == head) 00126 return next; 00127 00128 return head; 00129 } 00130 00131 // static 00132 Chunk* CachingReader::insertIntoLRUList(Chunk* chunk, Chunk* head) { 00133 Q_ASSERT(chunk); 00134 00135 // Chunk is the new head of the list, so connect the head as the next from 00136 // chunk. 00137 chunk->next_lru = head; 00138 chunk->prev_lru = NULL; 00139 00140 // If there are any elements in the list, point their prev pointer back at 00141 // chunk since it is the new head 00142 if (head) { 00143 head->prev_lru = chunk; 00144 } 00145 00146 // Chunk is the new head 00147 return chunk; 00148 } 00149 00150 00151 void CachingReader::freeChunk(Chunk* pChunk) { 00152 int removed = m_allocatedChunks.remove(pChunk->chunk_number); 00153 00154 // We'll tolerate not being in allocatedChunks because sometime you free a 00155 // chunk right after you allocated it. 00156 Q_ASSERT(removed <= 1); 00157 00158 // If this is the LRU chunk then set its previous LRU chunk to the LRU 00159 if (m_lruChunk == pChunk) { 00160 m_lruChunk = pChunk->prev_lru; 00161 } 00162 00163 m_mruChunk = removeFromLRUList(pChunk, m_mruChunk); 00164 00165 pChunk->chunk_number = -1; 00166 pChunk->length = 0; 00167 m_freeChunks.push_back(pChunk); 00168 } 00169 00170 void CachingReader::freeAllChunks() { 00171 m_allocatedChunks.clear(); 00172 m_mruChunk = NULL; 00173 00174 QSet<Chunk*> reserved = QSet<Chunk*>::fromList(m_chunksBeingRead.values()); 00175 00176 for (int i=0; i < m_chunks.size(); i++) { 00177 Chunk* c = m_chunks[i]; 00178 if (reserved.contains(c)) { 00179 continue; 00180 } 00181 if (!m_freeChunks.contains(c)) { 00182 c->chunk_number = -1; 00183 c->length = 0; 00184 c->next_lru = NULL; 00185 c->prev_lru = NULL; 00186 m_freeChunks.push_back(c); 00187 } 00188 } 00189 } 00190 00191 Chunk* CachingReader::allocateChunk() { 00192 if (m_freeChunks.count() == 0) 00193 return NULL; 00194 return m_freeChunks.takeFirst(); 00195 } 00196 00197 Chunk* CachingReader::allocateChunkExpireLRU() { 00198 Chunk* chunk = allocateChunk(); 00199 if (chunk == NULL) { 00200 Q_ASSERT(m_lruChunk); 00201 //qDebug() << "Expiring LRU" << m_lruChunk << m_lruChunk->chunk_number; 00202 freeChunk(m_lruChunk); 00203 chunk = allocateChunk(); 00204 Q_ASSERT(chunk); 00205 } 00206 //qDebug() << "allocateChunkExpireLRU" << chunk; 00207 return chunk; 00208 } 00209 00210 Chunk* CachingReader::lookupChunk(int chunk_number) { 00211 // Defaults to NULL if it's not in the hash. 00212 Chunk* chunk = NULL; 00213 00214 if (m_allocatedChunks.contains(chunk_number)) { 00215 chunk = m_allocatedChunks.value(chunk_number); 00216 00217 // Make sure we're all in agreement here. 00218 Q_ASSERT(chunk_number == chunk->chunk_number); 00219 00220 00221 // If this is the LRU chunk then set the previous LRU to the new LRU 00222 if (chunk == m_lruChunk && chunk->prev_lru != NULL) { 00223 m_lruChunk = chunk->prev_lru; 00224 } 00225 // Remove the chunk from the list and insert it at the head. 00226 m_mruChunk = removeFromLRUList(chunk, m_mruChunk); 00227 m_mruChunk = insertIntoLRUList(chunk, m_mruChunk); 00228 } 00229 00230 return chunk; 00231 } 00232 00233 void CachingReader::processChunkReadRequest(ChunkReadRequest* request, 00234 ReaderStatusUpdate* update) { 00235 int chunk_number = request->chunk->chunk_number; 00236 //qDebug() << "Processing ChunkReadRequest for" << chunk_number; 00237 update->status = CHUNK_READ_INVALID; 00238 update->chunk = request->chunk; 00239 update->chunk->length = 0; 00240 00241 if (m_pCurrentSoundSource == NULL || chunk_number < 0) { 00242 return; 00243 } 00244 00245 // Stereo samples 00246 int sample_position = sampleForChunk(chunk_number); 00247 int samples_remaining = m_iTrackNumSamples - sample_position; 00248 int samples_to_read = math_min(kSamplesPerChunk, samples_remaining); 00249 00250 // Bogus chunk number 00251 if (samples_to_read <= 0) { 00252 update->status = CHUNK_READ_EOF; 00253 return; 00254 } 00255 00256 m_pCurrentSoundSource->seek(sample_position); 00257 int samples_read = m_pCurrentSoundSource->read(samples_to_read, 00258 m_pSample); 00259 00260 // If we've run out of music, the SoundSource can return 0 samples. 00261 // Remember that SoundSourc->getLength() (which is m_iTrackNumSamples) can 00262 // lie to us about the length of the song! 00263 if (samples_read <= 0) { 00264 update->status = CHUNK_READ_EOF; 00265 return; 00266 } 00267 00268 // TODO(XXX) This loop can't be done with a memcpy, but could be done with 00269 // SSE. 00270 CSAMPLE* buffer = request->chunk->data; 00271 //qDebug() << "Reading into " << buffer; 00272 SampleUtil::convert(buffer, m_pSample, samples_read); 00273 update->status = CHUNK_READ_SUCCESS; 00274 update->chunk->length = samples_read; 00275 } 00276 00277 void CachingReader::newTrack(TrackPointer pTrack) { 00278 m_trackQueueMutex.lock(); 00279 m_trackQueue.enqueue(pTrack); 00280 m_trackQueueMutex.unlock(); 00281 } 00282 00283 void CachingReader::process() { 00284 ReaderStatusUpdate status; 00285 while (m_readerStatusFIFO.read(&status, 1) == 1) { 00286 // qDebug() << "Got ReaderStatusUpdate:" << status.status 00287 // << (status.chunk ? status.chunk->chunk_number : -1); 00288 if (status.status == TRACK_NOT_LOADED) { 00289 m_readerStatus = status.status; 00290 } else if (status.status == TRACK_LOADED) { 00291 freeAllChunks(); 00292 m_readerStatus = status.status; 00293 m_iTrackNumSamplesCallbackSafe = status.trackNumSamples; 00294 } else if (status.status == CHUNK_READ_SUCCESS) { 00295 Chunk* pChunk = status.chunk; 00296 Q_ASSERT(pChunk != NULL); 00297 Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number); 00298 if (pChunk2 != pChunk) { 00299 qDebug() << "Mismatch in requested chunk to read!"; 00300 } 00301 00302 Chunk* pAlreadyExisting = lookupChunk(pChunk->chunk_number); 00303 // If this chunk is already in the cache, then we just freshened 00304 // it. Free this chunk. 00305 if (pAlreadyExisting != NULL) { 00306 qDebug() << "CHUNK" << pChunk->chunk_number << "ALREADY EXISTS!"; 00307 freeChunk(pChunk); 00308 } else { 00309 //qDebug() << "Inserting chunk" << pChunk << pChunk->chunk_number; 00310 m_allocatedChunks.insert(pChunk->chunk_number, pChunk); 00311 00312 // Insert the chunk into the LRU list 00313 m_mruChunk = insertIntoLRUList(pChunk, m_mruChunk); 00314 00315 // If this chunk has no next LRU then it is the LRU. This only 00316 // happens if this is the first allocated chunk. 00317 if (pChunk->next_lru == NULL) { 00318 m_lruChunk = pChunk; 00319 } 00320 } 00321 } else if (status.status == CHUNK_READ_EOF) { 00322 Chunk* pChunk = status.chunk; 00323 Q_ASSERT(pChunk != NULL); 00324 Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number); 00325 if (pChunk2 != pChunk) { 00326 qDebug() << "Mismatch in requested chunk to read!"; 00327 } 00328 freeChunk(pChunk); 00329 } else if (status.status == CHUNK_READ_INVALID) { 00330 qDebug() << "WARNING: READER THREAD RECEIVED INVALID CHUNK READ"; 00331 Chunk* pChunk = status.chunk; 00332 Q_ASSERT(pChunk != NULL); 00333 Chunk* pChunk2 = m_chunksBeingRead.take(pChunk->chunk_number); 00334 if (pChunk2 != pChunk) { 00335 qDebug() << "Mismatch in requested chunk to read!"; 00336 } 00337 freeChunk(pChunk); 00338 } 00339 } 00340 } 00341 00342 int CachingReader::read(int sample, int num_samples, CSAMPLE* buffer) { 00343 int zerosWritten = 0; 00344 // Check for bogus sample numbers 00345 //Q_ASSERT(sample >= 0); 00346 QString temp = QString("Sample = %1").arg(sample); 00347 QByteArray tempBA = QString(temp).toUtf8(); 00348 Q_ASSERT_X(sample % 2 == 0,"CachingReader::read",tempBA); 00349 Q_ASSERT(num_samples >= 0); 00350 00351 // If asked to read 0 samples, don't do anything. (this is a perfectly 00352 // reasonable request that happens sometimes. If no track is loaded, don't 00353 // do anything. 00354 if (num_samples == 0 || m_readerStatus != TRACK_LOADED) { 00355 return 0; 00356 } 00357 00358 // Process messages from the reader thread. 00359 process(); 00360 00361 // TODO: is it possible to move this code out of caching reader 00362 // and into enginebuffer? It doesn't quite make sense here, although 00363 // it makes preroll completely transparent to the rest of the code 00364 00365 //if we're in preroll... 00366 if (sample < 0) { 00367 if (sample + num_samples <= 0) { 00368 //everything is zeros, easy 00369 memset(buffer, 0, sizeof(*buffer) * num_samples); 00370 return num_samples; 00371 } else { 00372 //some of the buffer is zeros, some is from the file 00373 memset(buffer, 0, sizeof(*buffer) * (0 - sample)); 00374 buffer += (0 - sample); 00375 num_samples = sample + num_samples; 00376 zerosWritten = (0 - sample); 00377 sample = 0; 00378 //continue processing the rest of the chunks normally 00379 } 00380 } 00381 00382 int start_sample = math_min(m_iTrackNumSamplesCallbackSafe, 00383 sample); 00384 int start_chunk = chunkForSample(start_sample); 00385 int end_sample = math_min(m_iTrackNumSamplesCallbackSafe, 00386 sample + num_samples - 1); 00387 int end_chunk = chunkForSample(end_sample); 00388 00389 int samples_remaining = num_samples; 00390 int current_sample = sample; 00391 00392 // Sanity checks 00393 Q_ASSERT(start_chunk <= end_chunk); 00394 00395 for (int chunk_num = start_chunk; chunk_num <= end_chunk; chunk_num++) { 00396 Chunk* current = lookupChunk(chunk_num); 00397 00398 // If the chunk is not in cache, then we must return an error. 00399 if (current == NULL) { 00400 qDebug() << "Couldn't get chunk " << chunk_num 00401 << " in read() of [" << sample << "," << sample+num_samples 00402 << "] chunks " << start_chunk << "-" << end_chunk; 00403 00404 // Something is wrong. Break out of the loop, that should fill the 00405 // samples requested with zeroes. 00406 break; 00407 } 00408 00409 int chunk_start_sample = sampleForChunk(chunk_num); 00410 int chunk_offset = current_sample - chunk_start_sample; 00411 int chunk_remaining_samples = current->length - chunk_offset; 00412 00413 // More sanity checks 00414 Q_ASSERT(current_sample >= chunk_start_sample); 00415 Q_ASSERT(current_sample % 2 == 0); 00416 00417 if (start_chunk != chunk_num) { 00418 Q_ASSERT(chunk_start_sample == current_sample); 00419 } 00420 00421 Q_ASSERT(samples_remaining >= 0); 00422 // It is completely possible that chunk_remaining_samples is less than 00423 // zero. If the caller is trying to read from beyond the end of the 00424 // file, then this can happen. We should tolerate it. 00425 00426 int samples_to_read = math_max(0, math_min(samples_remaining, 00427 chunk_remaining_samples)); 00428 00429 // samples_to_read should be non-negative and even 00430 Q_ASSERT(samples_to_read >= 0); 00431 Q_ASSERT(samples_to_read % 2 == 0); 00432 00433 CSAMPLE *data = current->data + chunk_offset; 00434 00435 // If we did not decide to read any samples from this chunk then that 00436 // means we have exhausted all the samples in the song. 00437 if (samples_to_read == 0) { 00438 break; 00439 } 00440 00441 // TODO(rryan) do a test and see if using memcpy is faster than gcc 00442 // optimizing the for loop 00443 memcpy(buffer, data, sizeof(*buffer) * samples_to_read); 00444 // for (int i=0; i < samples_to_read; i++) { 00445 // buffer[i] = data[i]; 00446 // } 00447 00448 buffer += samples_to_read; 00449 current_sample += samples_to_read; 00450 samples_remaining -= samples_to_read; 00451 } 00452 00453 // If we didn't supply all the samples requested, that probably means we're 00454 // at the end of the file, or something is wrong. Provide zeroes and pretend 00455 // all is well. The caller can't be bothered to check how long the file is. 00456 // TODO(XXX) memset 00457 for (int i=0; i<samples_remaining; i++) { 00458 buffer[i] = 0.0f; 00459 } 00460 samples_remaining = 0; 00461 00462 Q_ASSERT(samples_remaining == 0); 00463 return zerosWritten + num_samples - samples_remaining; 00464 } 00465 00466 void CachingReader::hintAndMaybeWake(QList<Hint>& hintList) { 00467 // If no file is loaded, skip. 00468 if (m_readerStatus != TRACK_LOADED) { 00469 return; 00470 } 00471 00472 QListIterator<Hint> iterator(hintList); 00473 00474 // To prevent every bit of code having to guess how many samples 00475 // forward it makes sense to keep in memory, the hinter can provide 00476 // either 0 for a forward hint or -1 for a backward hint. We should 00477 // be calculating an appropriate number of samples to go backward as 00478 // some function of the latency, but for now just leave this as a 00479 // constant. 2048 is a pretty good number of samples because 25ms 00480 // latency corresponds to 1102.5 mono samples and we need double 00481 // that for stereo samples. 00482 const int default_samples = 2048; 00483 00484 QSet<int> chunksToFreshen; 00485 while (iterator.hasNext()) { 00486 // Copy, don't use reference. 00487 Hint hint = iterator.next(); 00488 00489 if (hint.length == 0) { 00490 hint.length = default_samples; 00491 } else if (hint.length == -1) { 00492 hint.sample -= default_samples; 00493 hint.length = default_samples; 00494 if (hint.sample < 0) { 00495 hint.length += hint.sample; 00496 hint.sample = 0; 00497 } 00498 } 00499 Q_ASSERT(hint.length >= 0); 00500 int start_sample = math_max(0, math_min( 00501 m_iTrackNumSamplesCallbackSafe, hint.sample)); 00502 int start_chunk = chunkForSample(start_sample); 00503 int end_sample = math_max(0, math_min( 00504 m_iTrackNumSamplesCallbackSafe, hint.sample + hint.length - 1)); 00505 int end_chunk = chunkForSample(end_sample); 00506 00507 for (int current = start_chunk; current <= end_chunk; ++current) { 00508 chunksToFreshen.insert(current); 00509 } 00510 } 00511 00512 // For every chunk that the hints indicated, check if it is in the cache. If 00513 // any are not, then wake. 00514 bool shouldWake = false; 00515 QSetIterator<int> setIterator(chunksToFreshen); 00516 while (setIterator.hasNext()) { 00517 int chunk = setIterator.next(); 00518 00519 // This will cause the chunk to be 'freshened' in the cache. The 00520 // chunk will be moved to the end of the LRU list. 00521 if (!m_chunksBeingRead.contains(chunk) && lookupChunk(chunk) == NULL) { 00522 shouldWake = true; 00523 Chunk* pChunk = allocateChunkExpireLRU(); 00524 Q_ASSERT(pChunk != NULL); 00525 00526 m_chunksBeingRead.insert(chunk, pChunk); 00527 ChunkReadRequest request; 00528 pChunk->chunk_number = chunk; 00529 request.chunk = pChunk; 00530 // qDebug() << "Requesting read of chunk" << chunk << "into" << pChunk; 00531 // qDebug() << "Requesting read into " << request.chunk->data; 00532 if (m_chunkReadRequestFIFO.write(&request, 1) != 1) { 00533 qDebug() << "ERROR: Could not submit read request for " 00534 << chunk; 00535 } 00536 //qDebug() << "Checking chunk " << chunk << " shouldWake:" << shouldWake << " chunksToRead" << m_chunksToRead.size(); 00537 } 00538 } 00539 00540 // If there are chunks to be read, wake up. 00541 if (shouldWake) { 00542 wake(); 00543 } 00544 } 00545 00546 void CachingReader::run() { 00547 // Notify the EngineWorkerScheduler that the work we scheduled is starting. 00548 emit(workStarting(this)); 00549 00550 m_trackQueueMutex.lock(); 00551 TrackPointer pLoadTrack = TrackPointer(); 00552 if (!m_trackQueue.isEmpty()) { 00553 pLoadTrack = m_trackQueue.takeLast(); 00554 m_trackQueue.clear(); 00555 } 00556 m_trackQueueMutex.unlock(); 00557 00558 if (pLoadTrack) { 00559 loadTrack(pLoadTrack); 00560 } else { 00561 // Read the requested chunks. 00562 ChunkReadRequest request; 00563 ReaderStatusUpdate status; 00564 while (m_chunkReadRequestFIFO.read(&request, 1) == 1) { 00565 processChunkReadRequest(&request, &status); 00566 m_readerStatusFIFO.writeBlocking(&status, 1); 00567 } 00568 } 00569 00570 // Notify the EngineWorkerScheduler that the work we did is done. 00571 emit(workDone(this)); 00572 } 00573 00574 void CachingReader::wake() { 00575 //qDebug() << m_pGroup << "CachingReader::wake()"; 00576 emit(workReady(this)); 00577 } 00578 00579 void CachingReader::loadTrack(TrackPointer pTrack) { 00580 //qDebug() << m_pGroup << "CachingReader::loadTrack() lock acquired for load."; 00581 00582 ReaderStatusUpdate status; 00583 status.status = TRACK_LOADED; 00584 status.chunk = NULL; 00585 status.trackNumSamples = 0; 00586 00587 if (m_pCurrentSoundSource != NULL) { 00588 delete m_pCurrentSoundSource; 00589 m_pCurrentSoundSource = NULL; 00590 } 00591 m_iTrackSampleRate = 0; 00592 m_iTrackNumSamples = 0; 00593 00594 QString filename = pTrack->getLocation(); 00595 00596 if (filename.isEmpty() || !pTrack->exists()) { 00597 // Must unlock before emitting to avoid deadlock 00598 qDebug() << m_pGroup << "CachingReader::loadTrack() load failed for\"" 00599 << filename << "\", unlocked reader lock"; 00600 status.status = TRACK_NOT_LOADED; 00601 m_readerStatusFIFO.writeBlocking(&status, 1); 00602 emit(trackLoadFailed( 00603 pTrack, QString("The file '%1' could not be found.").arg(filename))); 00604 return; 00605 } 00606 00607 m_pCurrentSoundSource = new SoundSourceProxy(pTrack); 00608 bool open(m_pCurrentSoundSource->open() == OK); //Open the song for reading 00609 m_iTrackSampleRate = m_pCurrentSoundSource->getSampleRate(); 00610 m_iTrackNumSamples = status.trackNumSamples = 00611 m_pCurrentSoundSource->length(); 00612 00613 if (!open || m_iTrackNumSamples == 0 || m_iTrackSampleRate == 0) { 00614 // Must unlock before emitting to avoid deadlock 00615 qDebug() << m_pGroup << "CachingReader::loadTrack() load failed for\"" 00616 << filename << "\", file invalid, unlocked reader lock"; 00617 status.status = TRACK_NOT_LOADED; 00618 m_readerStatusFIFO.writeBlocking(&status, 1); 00619 emit(trackLoadFailed( 00620 pTrack, QString("The file '%1' could not be loaded.").arg(filename))); 00621 return; 00622 } 00623 00624 m_readerStatusFIFO.writeBlocking(&status, 1); 00625 00626 // Clear the chunks to read list. 00627 ChunkReadRequest request; 00628 while (m_chunkReadRequestFIFO.read(&request, 1) == 1) { 00629 qDebug() << "Skipping read request for " << request.chunk->chunk_number; 00630 status.status = CHUNK_READ_INVALID; 00631 status.chunk = request.chunk; 00632 m_readerStatusFIFO.writeBlocking(&status, 1); 00633 } 00634 00635 // Emit that the track is loaded. 00636 emit(trackLoaded(pTrack, m_iTrackSampleRate, m_iTrackNumSamples)); 00637 }