SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); if (frameIndex >= getMaxFrameIndex()) { // EOF m_curFrameIndex = getMaxFrameIndex(); return m_curFrameIndex; } // NOTE(uklotzde): Resetting the decoder near to the beginning // of the stream when seeking backwards produces invalid sample // values! As a consequence the seeking test fails. if (isValidSampleBlockId(m_curSampleBlockId) && (frameIndex <= kNumberOfPrefetchFrames)) { // Workaround: Reset the decoder when seeking near to the beginning // of the stream while decoding. reopenDecoder(); skipSampleFrames(frameIndex); } if (frameIndex == m_curFrameIndex) { return m_curFrameIndex; } MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / m_framesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < m_curFrameIndex) || // seeking backwards? !isValidSampleBlockId(m_curSampleBlockId) || // invalid seek position? (sampleBlockId > (m_curSampleBlockId + m_numberOfPrefetchSampleBlocks))) { // jumping forward? // Restart decoding one or more blocks of samples backwards // from the calculated starting block to avoid audible glitches. // Implementation note: The type MP4SampleId is unsigned so we // need to be careful when subtracting! if ((kSampleBlockIdMin + m_numberOfPrefetchSampleBlocks) < sampleBlockId) { sampleBlockId -= m_numberOfPrefetchSampleBlocks; } else { sampleBlockId = kSampleBlockIdMin; } restartDecoding(sampleBlockId); DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Skip (= decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount < prefetchFrameCount) { qWarning() << "Failed to prefetch sample data while seeking" << skipFrameCount << "<" << prefetchFrameCount; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); return m_curFrameIndex; }
SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Handle trivial case if (m_curFrameIndex == frameIndex) { // Nothing to do return m_curFrameIndex; } // Handle edge case if (getMaxFrameIndex() <= frameIndex) { // EOF reached m_curFrameIndex = getMaxFrameIndex(); return m_curFrameIndex; } MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / m_framesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < m_curFrameIndex) || // seeking backwards? !isValidSampleBlockId(m_curSampleBlockId) || // invalid seek position? (sampleBlockId > (m_curSampleBlockId + m_numberOfPrefetchSampleBlocks))) { // jumping forward? // Restart decoding one or more blocks of samples backwards // from the calculated starting block to avoid audible glitches. // Implementation note: The type MP4SampleId is unsigned so we // need to be careful when subtracting! if ((kSampleBlockIdMin + m_numberOfPrefetchSampleBlocks) < sampleBlockId) { sampleBlockId -= m_numberOfPrefetchSampleBlocks; } else { sampleBlockId = kSampleBlockIdMin; } restartDecoding(sampleBlockId); DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Skip (= decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount < prefetchFrameCount) { qWarning() << "Failed to prefetch sample data while seeking" << skipFrameCount << "<" << prefetchFrameCount; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); return m_curFrameIndex; }
SINT SoundSourceM4A::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(frameIndex)); if (m_curFrameIndex != frameIndex) { MP4SampleId sampleBlockId = kSampleBlockIdMin + (frameIndex / kFramesPerSampleBlock); DEBUG_ASSERT(isValidSampleBlockId(sampleBlockId)); if ((frameIndex < m_curFrameIndex) || // seeking backwards? !isValidSampleBlockId(m_curSampleBlockId) || // invalid seek position? (sampleBlockId > (m_curSampleBlockId + kNumberOfPrefetchSampleBlocks))) { // jumping forward? // Restart decoding one or more blocks of samples backwards // from the calculated starting block to avoid audible glitches. // Implementation note: The type MP4SampleId is unsigned so we // need to be careful when subtracting! if ((kSampleBlockIdMin + kNumberOfPrefetchSampleBlocks) < sampleBlockId) { sampleBlockId -= kNumberOfPrefetchSampleBlocks; } else { sampleBlockId = kSampleBlockIdMin; } restartDecoding(sampleBlockId); DEBUG_ASSERT(m_curSampleBlockId == sampleBlockId); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Prefetch (decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount != prefetchFrameCount) { qWarning() << "Failed to skip over prefetched sample frames after seeking @" << m_curFrameIndex; return m_curFrameIndex; // abort } } DEBUG_ASSERT(m_curFrameIndex == frameIndex); return m_curFrameIndex; }
SINT SoundSourceMp3::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Handle trivial case if (m_curFrameIndex == frameIndex) { // Nothing to do return m_curFrameIndex; } // Handle edge case if (getMaxFrameIndex() <= frameIndex) { // EOF reached m_curFrameIndex = getMaxFrameIndex(); return m_curFrameIndex; } SINT seekFrameIndex = findSeekFrameIndex( frameIndex); DEBUG_ASSERT(SINT(m_seekFrameList.size()) > seekFrameIndex); const SINT curSeekFrameIndex = findSeekFrameIndex( m_curFrameIndex); DEBUG_ASSERT(SINT(m_seekFrameList.size()) > curSeekFrameIndex); // some consistency checks DEBUG_ASSERT((curSeekFrameIndex >= seekFrameIndex) || (m_curFrameIndex < frameIndex)); DEBUG_ASSERT((curSeekFrameIndex <= seekFrameIndex) || (m_curFrameIndex > frameIndex)); if ((getMaxFrameIndex() <= m_curFrameIndex) || // out of range (frameIndex < m_curFrameIndex) || // seek backward (seekFrameIndex > (curSeekFrameIndex + kMp3SeekFramePrefetchCount))) { // jump forward // Adjust the seek frame index for prefetching // Implementation note: The type SINT is unsigned so // need to be careful when subtracting! if (kMp3SeekFramePrefetchCount < seekFrameIndex) { // Restart decoding kMp3SeekFramePrefetchCount seek frames // before the expected sync position seekFrameIndex -= kMp3SeekFramePrefetchCount; } else { // Restart decoding at the beginning of the audio stream seekFrameIndex = 0; } m_curFrameIndex = restartDecoding(m_seekFrameList[seekFrameIndex]); if (getMaxFrameIndex() <= m_curFrameIndex) { // out of range -> abort return m_curFrameIndex; } DEBUG_ASSERT(findSeekFrameIndex(m_curFrameIndex) == seekFrameIndex); } // Decoding starts before the actual target position DEBUG_ASSERT(m_curFrameIndex <= frameIndex); // Skip (= decode and discard) all samples up to the target position const SINT prefetchFrameCount = frameIndex - m_curFrameIndex; const SINT skipFrameCount = skipSampleFrames(prefetchFrameCount); DEBUG_ASSERT(skipFrameCount <= prefetchFrameCount); if (skipFrameCount < prefetchFrameCount) { qWarning() << "Failed to prefetch sample data while seeking" << skipFrameCount << "<" << prefetchFrameCount; } DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); return m_curFrameIndex; }
SoundSource::OpenResult SoundSourceMp3::tryOpen(const AudioSourceConfig& /*audioSrcCfg*/) { DEBUG_ASSERT(!hasValidChannelCount()); DEBUG_ASSERT(!hasValidSamplingRate()); DEBUG_ASSERT(!m_file.isOpen()); if (!m_file.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open file:" << m_file.fileName(); return OpenResult::FAILED; } // Get a pointer to the file using memory mapped IO m_fileSize = m_file.size(); m_pFileData = m_file.map(0, m_fileSize); // NOTE(uklotzde): If the file disappears unexpectedly while mapped // a SIGBUS error might occur that is not handled and will terminate // Mixxx immediately. This behavior is documented in the manpage of // mmap(). It has already appeared due to hardware errors and is // described in the following bug report: // https://bugs.launchpad.net/mixxx/+bug/1452005 // Transfer it to the mad stream-buffer: mad_stream_options(&m_madStream, MAD_OPTION_IGNORECRC); mad_stream_buffer(&m_madStream, m_pFileData, m_fileSize); DEBUG_ASSERT(m_pFileData == m_madStream.this_frame); DEBUG_ASSERT(m_seekFrameList.empty()); m_avgSeekFrameCount = 0; m_curFrameIndex = getMinFrameIndex(); int headerPerSamplingRate[kSamplingRateCount]; for (int i = 0; i < kSamplingRateCount; ++i) { headerPerSamplingRate[i] = 0; } // Decode all the headers and calculate audio properties unsigned long sumBitrate = 0; mad_header madHeader; mad_header_init(&madHeader); SINT maxChannelCount = getChannelCount(); do { if (!decodeFrameHeader(&madHeader, &m_madStream, true)) { if (isStreamValid(m_madStream)) { // Skip frame continue; } else { // Abort decoding break; } } // Grab data from madHeader const unsigned int madSampleRate = madHeader.samplerate; // TODO(XXX): Replace DEBUG_ASSERT with static_assert // MAD must not change its enum values! DEBUG_ASSERT(MAD_UNITS_8000_HZ == 8000); const mad_units madUnits = static_cast<mad_units>(madSampleRate); const long madFrameLength = mad_timer_count(madHeader.duration, madUnits); if (0 >= madFrameLength) { qWarning() << "Skipping MP3 frame with invalid length" << madFrameLength << "in:" << m_file.fileName(); // Skip frame continue; } const SINT madChannelCount = MAD_NCHANNELS(&madHeader); if (isValidChannelCount(maxChannelCount) && (madChannelCount != maxChannelCount)) { qWarning() << "Differing number of channels" << madChannelCount << "<>" << maxChannelCount << "in some MP3 frame headers:" << m_file.fileName(); } maxChannelCount = math_max(madChannelCount, maxChannelCount); const int samplingRateIndex = getIndexBySamplingRate(madSampleRate); if (samplingRateIndex >= kSamplingRateCount) { qWarning() << "Invalid sample rate:" << m_file.fileName() << madSampleRate; // Abort mad_header_finish(&madHeader); return OpenResult::FAILED; } // Count valid frames separated by its sampling rate headerPerSamplingRate[samplingRateIndex]++; addSeekFrame(m_curFrameIndex, m_madStream.this_frame); // Accumulate data from the header sumBitrate += madHeader.bitrate; // Update current stream position m_curFrameIndex += madFrameLength; DEBUG_ASSERT(m_madStream.this_frame); DEBUG_ASSERT(0 <= (m_madStream.this_frame - m_pFileData)); } while (quint64(m_madStream.this_frame - m_pFileData) < m_fileSize); mad_header_finish(&madHeader); if (MAD_ERROR_NONE != m_madStream.error) { // Unreachable code for recoverable errors DEBUG_ASSERT(!MAD_RECOVERABLE(m_madStream.error)); if (MAD_ERROR_BUFLEN != m_madStream.error) { qWarning() << "Unrecoverable MP3 header error:" << mad_stream_errorstr(&m_madStream); // Abort return OpenResult::FAILED; } } if (m_seekFrameList.empty()) { // This is not a working MP3 file. qWarning() << "SSMP3: This is not a working MP3 file:" << m_file.fileName(); // Abort return OpenResult::FAILED; } int mostCommonSamplingRateIndex = kSamplingRateCount; // invalid int mostCommonSamplingRateCount = 0; int differentRates = 0; for (int i = 0; i < kSamplingRateCount; ++i) { // Find most common sampling rate if (mostCommonSamplingRateCount < headerPerSamplingRate[i]) { mostCommonSamplingRateCount = headerPerSamplingRate[i]; mostCommonSamplingRateIndex = i; differentRates++; } } if (differentRates > 1) { qWarning() << "Differing sampling rate in some headers:" << m_file.fileName(); for (int i = 0; i < kSamplingRateCount; ++i) { if (0 < headerPerSamplingRate[i]) { qWarning() << headerPerSamplingRate[i] << "MP3 headers with sampling rate" << getSamplingRateByIndex(i); } } qWarning() << "MP3 files with varying sample rate are not supported!"; qWarning() << "Since this happens most likely due to a corrupt file"; qWarning() << "Mixxx tries to plays it with the most common sample rate for this file"; } if (mostCommonSamplingRateIndex < kSamplingRateCount) { setSamplingRate(getSamplingRateByIndex(mostCommonSamplingRateIndex)); } else { qWarning() << "No single valid sampling rate in header"; // Abort return OpenResult::FAILED; } // Initialize the AudioSource setChannelCount(maxChannelCount); setFrameCount(m_curFrameIndex); // Calculate average values m_avgSeekFrameCount = getFrameCount() / m_seekFrameList.size(); const unsigned long avgBitrate = sumBitrate / m_seekFrameList.size(); setBitrate(avgBitrate / 1000); // Terminate m_seekFrameList addSeekFrame(m_curFrameIndex, 0); // Reset positions m_curFrameIndex = getMinFrameIndex(); // Restart decoding at the beginning of the audio stream m_curFrameIndex = restartDecoding(m_seekFrameList.front()); if (m_curFrameIndex != m_seekFrameList.front().frameIndex) { qWarning() << "Failed to start decoding:" << m_file.fileName(); // Abort return OpenResult::FAILED; } return OpenResult::SUCCEEDED; }