SoundSource::OpenResult SoundSourceFLAC::tryOpen(const AudioSourceConfig& /*audioSrcCfg*/) { DEBUG_ASSERT(!m_file.isOpen()); if (!m_file.open(QIODevice::ReadOnly)) { qWarning() << "Failed to open FLAC file:" << m_file.fileName(); return OpenResult::FAILED; } m_decoder = FLAC__stream_decoder_new(); if (m_decoder == nullptr) { qWarning() << "Failed to create FLAC decoder!"; return OpenResult::FAILED; } FLAC__stream_decoder_set_md5_checking(m_decoder, false); const FLAC__StreamDecoderInitStatus initStatus( FLAC__stream_decoder_init_stream(m_decoder, FLAC_read_cb, FLAC_seek_cb, FLAC_tell_cb, FLAC_length_cb, FLAC_eof_cb, FLAC_write_cb, FLAC_metadata_cb, FLAC_error_cb, this)); if (initStatus != FLAC__STREAM_DECODER_INIT_STATUS_OK) { qWarning() << "Failed to initialize FLAC decoder:" << initStatus; return OpenResult::FAILED; } if (!FLAC__stream_decoder_process_until_end_of_metadata(m_decoder)) { qWarning() << "Failed to process FLAC metadata:" << FLAC__stream_decoder_get_state(m_decoder); return OpenResult::FAILED; } m_curFrameIndex = getMinFrameIndex(); return OpenResult::SUCCEEDED; }
SoundSource::OpenResult SoundSourceOpus::tryOpen(const AudioSourceConfig& /*audioSrcCfg*/) { // From opus/opusfile.h // On Windows, this string must be UTF-8 (to allow access to // files whose names cannot be represented in the current // MBCS code page). // All other systems use the native character encoding. #ifdef _WIN32 QByteArray qBAFilename = getLocalFileName().toUtf8(); #else QByteArray qBAFilename = getLocalFileName().toLocal8Bit(); #endif int errorCode = 0; DEBUG_ASSERT(!m_pOggOpusFile); m_pOggOpusFile = op_open_file(qBAFilename.constData(), &errorCode); if (!m_pOggOpusFile) { qWarning() << "Failed to open OggOpus file:" << getUrlString() << "errorCode" << errorCode; return OpenResult::FAILED; } if (!op_seekable(m_pOggOpusFile)) { qWarning() << "SoundSourceOpus:" << "Stream in" << getUrlString() << "is not seekable"; return OpenResult::UNSUPPORTED_FORMAT; } const int channelCount = op_channel_count(m_pOggOpusFile, kCurrentStreamLink); if (0 < channelCount) { setChannelCount(channelCount); } else { qWarning() << "Failed to read channel configuration of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } const ogg_int64_t pcmTotal = op_pcm_total(m_pOggOpusFile, kEntireStreamLink); if (0 <= pcmTotal) { setFrameCount(pcmTotal); } else { qWarning() << "Failed to read total length of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } const opus_int32 bitrate = op_bitrate(m_pOggOpusFile, kEntireStreamLink); if (0 < bitrate) { setBitrate(bitrate / 1000); } else { qWarning() << "Failed to determine bitrate of OggOpus file:" << getUrlString(); return OpenResult::FAILED; } setSamplingRate(kSamplingRate); m_curFrameIndex = getMinFrameIndex(); return OpenResult::SUCCEEDED; }
SoundSourceWV::SoundSourceWV(const QUrl& url) : SoundSourcePlugin(url, "wv"), m_wpc(nullptr), m_sampleScaleFactor(CSAMPLE_ZERO), m_pWVFile(nullptr), m_pWVCFile(nullptr), m_curFrameIndex(getMinFrameIndex()) { }
SoundSourceFLAC::SoundSourceFLAC(const QUrl& url) : SoundSource(url, "flac"), m_file(getLocalFileName()), m_decoder(nullptr), m_maxBlocksize(0), m_bitsPerSample(kBitsPerSampleDefault), m_sampleScaleFactor(CSAMPLE_ZERO), m_curFrameIndex(getMinFrameIndex()) { }
SoundSourceM4A::SoundSourceM4A(const QUrl& url) : SoundSourcePlugin(url, "m4a"), m_hFile(MP4_INVALID_FILE_HANDLE), m_trackId(MP4_INVALID_TRACK_ID), m_maxSampleBlockId(MP4_INVALID_SAMPLE_ID), m_curSampleBlockId(MP4_INVALID_SAMPLE_ID), m_inputBufferLength(0), m_inputBufferOffset(0), m_hDecoder(NULL), m_curFrameIndex(getMinFrameIndex()) { }
SoundSourceMp3::SoundSourceMp3(const QUrl& url) : SoundSource(url, "mp3"), m_file(getLocalFileName()), m_fileSize(0), m_pFileData(nullptr), m_avgSeekFrameCount(0), m_curFrameIndex(getMinFrameIndex()), m_madSynthCount(0) { m_seekFrameList.reserve(kSeekFrameListCapacity); initDecoding(); }
SINT SoundSourceMp3::restartDecoding( const SeekFrameType& seekFrame) { qDebug() << "restartDecoding @" << seekFrame.frameIndex; // Discard decoded output m_madSynthCount = 0; if (getMinFrameIndex() == seekFrame.frameIndex) { mad_frame_finish(&m_madFrame); mad_synth_finish(&m_madSynth); } mad_stream_finish(&m_madStream); mad_stream_init(&m_madStream); mad_stream_options(&m_madStream, MAD_OPTION_IGNORECRC); if (getMinFrameIndex() == seekFrame.frameIndex) { mad_synth_init(&m_madSynth); mad_frame_init(&m_madFrame); } // Fill input buffer mad_stream_buffer(&m_madStream, seekFrame.pInputData, m_fileSize - (seekFrame.pInputData - m_pFileData)); if (getMinFrameIndex() < seekFrame.frameIndex) { // Muting is done here to eliminate potential pops/clicks // from skipping Rob Leslie explains why here: // http://www.mars.org/mailman/public/mad-dev/2001-August/000321.html mad_frame_mute(&m_madFrame); mad_synth_mute(&m_madSynth); } if (!decodeFrameHeader(&m_madFrame.header, &m_madStream, false)) { if (!isStreamValid(m_madStream)) { // Failure -> Seek to EOF return getFrameCount(); } } return seekFrame.frameIndex; }
SoundSourceM4A::SoundSourceM4A(const QUrl& url) : SoundSourcePlugin(url, "m4a"), m_hFile(MP4_INVALID_FILE_HANDLE), m_trackId(MP4_INVALID_TRACK_ID), m_framesPerSampleBlock(MP4_INVALID_DURATION), m_numberOfPrefetchSampleBlocks(0), m_maxSampleBlockId(MP4_INVALID_SAMPLE_ID), m_curSampleBlockId(MP4_INVALID_SAMPLE_ID), m_inputBufferLength(0), m_inputBufferOffset(0), m_hDecoder(nullptr), m_curFrameIndex(getMinFrameIndex()) { }
void SoundSourceWV::close() { if (m_wpc) { WavpackCloseFile(m_wpc); m_wpc = nullptr; } if (m_pWVFile) { m_pWVFile->close(); delete m_pWVFile; m_pWVFile = nullptr; } if (m_pWVCFile) { m_pWVCFile->close(); delete m_pWVCFile; m_pWVCFile = nullptr; } m_curFrameIndex = getMinFrameIndex(); }
SINT SoundSourceMp3::findSeekFrameIndex( SINT frameIndex) const { // Check preconditions DEBUG_ASSERT(0 < m_avgSeekFrameCount); DEBUG_ASSERT(!m_seekFrameList.empty()); DEBUG_ASSERT(getMinFrameIndex() == m_seekFrameList.front().frameIndex); DEBUG_ASSERT(getMaxFrameIndex() == m_seekFrameList.back().frameIndex); SINT lowerBound = 0; SINT upperBound = m_seekFrameList.size(); DEBUG_ASSERT(lowerBound < upperBound); // Initial guess based on average frame size SINT seekFrameIndex = frameIndex / m_avgSeekFrameCount; if (seekFrameIndex >= upperBound) { seekFrameIndex = upperBound - 1; } while ((upperBound - lowerBound) > 1) { DEBUG_ASSERT(seekFrameIndex >= lowerBound); DEBUG_ASSERT(seekFrameIndex < upperBound); DEBUG_ASSERT(m_seekFrameList[lowerBound].frameIndex <= frameIndex); if (m_seekFrameList[seekFrameIndex].frameIndex <= frameIndex) { lowerBound = seekFrameIndex; } else { upperBound = seekFrameIndex; } // Next guess halfway between lower and upper bound seekFrameIndex = lowerBound + (upperBound - lowerBound) / 2; } // Check postconditions DEBUG_ASSERT(seekFrameIndex == lowerBound); DEBUG_ASSERT(SINT(m_seekFrameList.size()) > seekFrameIndex); DEBUG_ASSERT(m_seekFrameList[seekFrameIndex].frameIndex <= frameIndex); DEBUG_ASSERT(((seekFrameIndex + 1) >= SINT(m_seekFrameList.size())) || (m_seekFrameList[seekFrameIndex + 1].frameIndex > frameIndex)); return seekFrameIndex; }
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; }
bool SoundSourceM4A::openDecoder() { DEBUG_ASSERT(m_hDecoder == nullptr); // not already opened m_hDecoder = NeAACDecOpen(); if (m_hDecoder == nullptr) { qWarning() << "Failed to open the AAC decoder!"; return false; } NeAACDecConfigurationPtr pDecoderConfig = NeAACDecGetCurrentConfiguration( m_hDecoder); pDecoderConfig->outputFormat = FAAD_FMT_FLOAT; if ((kChannelCountMono == m_audioSrcCfg.getChannelCount()) || (kChannelCountStereo == m_audioSrcCfg.getChannelCount())) { pDecoderConfig->downMatrix = 1; } else { pDecoderConfig->downMatrix = 0; } pDecoderConfig->defObjectType = LC; if (!NeAACDecSetConfiguration(m_hDecoder, pDecoderConfig)) { qWarning() << "Failed to configure AAC decoder!"; return false; } u_int8_t* configBuffer = nullptr; u_int32_t configBufferSize = 0; if (!MP4GetTrackESConfiguration(m_hFile, m_trackId, &configBuffer, &configBufferSize)) { // Failed to get mpeg-4 audio config... this is ok. // NeAACDecInit2() will simply use default values instead. qWarning() << "Failed to read the MP4 audio configuration." << "Continuing with default values."; } SAMPLERATE_TYPE samplingRate; unsigned char channelCount; if (0 > NeAACDecInit2(m_hDecoder, configBuffer, configBufferSize, &samplingRate, &channelCount)) { free(configBuffer); qWarning() << "Failed to initialize the AAC decoder!"; return false; } else { free(configBuffer); } // Calculate how many sample blocks we need to decode in advance // of a random seek in order to get the recommended number of // prefetch frames m_numberOfPrefetchSampleBlocks = (kNumberOfPrefetchFrames + (m_framesPerSampleBlock - 1)) / m_framesPerSampleBlock; setChannelCount(channelCount); setSamplingRate(samplingRate); setFrameCount(((m_maxSampleBlockId - kSampleBlockIdMin) + 1) * m_framesPerSampleBlock); // Resize temporary buffer for decoded sample data const SINT sampleBufferCapacity = frames2samples(m_framesPerSampleBlock); m_sampleBuffer.resetCapacity(sampleBufferCapacity); // Discard all buffered samples m_inputBufferLength = 0; // Invalidate current position(s) for the following seek operation m_curSampleBlockId = MP4_INVALID_SAMPLE_ID; m_curFrameIndex = getMaxFrameIndex(); // (Re-)Start decoding at the beginning of the file seekSampleFrame(getMinFrameIndex()); return m_curFrameIndex == getMinFrameIndex(); }
SINT SoundSourceFLAC::seekSampleFrame(SINT frameIndex) { DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); DEBUG_ASSERT(isValidFrameIndex(frameIndex)); // Seek to the new position SINT seekFrameIndex = frameIndex; int retryCount = 0; // NOTE(uklotzde): This loop avoids unnecessary seek operations. // If the file is decoded from the beginning to the end during // continuous playback no seek operations are necessary. This // may hide rare seek errors that we have observed in some "flaky" // FLAC files. The retry strategy implemented by this loop tries // to solve these issues when randomly seeking through such a file. while ((seekFrameIndex != m_curFrameIndex) && (retryCount <= kSeekErrorMaxRetryCount)){ // Discard decoded sample data before seeking m_sampleBuffer.reset(); // Invalidate the current position m_curFrameIndex = getMaxFrameIndex(); if (FLAC__stream_decoder_seek_absolute(m_decoder, seekFrameIndex)) { // Success: Set the new position m_curFrameIndex = seekFrameIndex; DEBUG_ASSERT(FLAC__STREAM_DECODER_SEEK_ERROR != FLAC__stream_decoder_get_state(m_decoder)); } else { // Failure qWarning() << "Seek error at" << seekFrameIndex << "in" << m_file.fileName(); if (FLAC__STREAM_DECODER_SEEK_ERROR == FLAC__stream_decoder_get_state(m_decoder)) { // Flush the input stream of the decoder according to the // documentation of FLAC__stream_decoder_seek_absolute() if (!FLAC__stream_decoder_flush(m_decoder)) { qWarning() << "Failed to flush input buffer of the FLAC decoder after seek failure in" << m_file.fileName(); // Invalidate the current position again... m_curFrameIndex = getMaxFrameIndex(); // ...and abort return m_curFrameIndex; } } if (getMinFrameIndex() < seekFrameIndex) { // The next seek position should start at a preceding sample block. // By subtracting max. blocksize from the current seek position it // is guaranteed that the targeted sample blocks of subsequent seek // operations will differ. DEBUG_ASSERT(0 < m_maxBlocksize); seekFrameIndex -= m_maxBlocksize; if (seekFrameIndex < getMinFrameIndex()) { seekFrameIndex = getMinFrameIndex(); } } else { // We have already reached the beginning of the file // and cannot move the seek position backward any // further! break; // exit loop } } } while (m_curFrameIndex != seekFrameIndex); DEBUG_ASSERT(isValidFrameIndex(m_curFrameIndex)); if (frameIndex > m_curFrameIndex) { // Adjust the current position skipSampleFrames(frameIndex - m_curFrameIndex); } return m_curFrameIndex; }
SoundSourceOpus::SoundSourceOpus(QUrl url) : SoundSource(url, "opus"), m_pOggOpusFile(NULL), m_curFrameIndex(getMinFrameIndex()) { }
Result SoundSourceM4A::tryOpen(const AudioSourceConfig& audioSrcCfg) { DEBUG_ASSERT(MP4_INVALID_FILE_HANDLE == m_hFile); /* open MP4 file, check for >= ver 1.9.1 */ #if MP4V2_PROJECT_version_hex <= 0x00010901 m_hFile = MP4Read(getLocalFileNameBytes().constData(), 0); #else m_hFile = MP4Read(getLocalFileNameBytes().constData()); #endif if (MP4_INVALID_FILE_HANDLE == m_hFile) { qWarning() << "Failed to open file for reading:" << getUrlString(); return ERR; } m_trackId = findFirstAudioTrackId(m_hFile); if (MP4_INVALID_TRACK_ID == m_trackId) { qWarning() << "No AAC track found:" << getUrlString(); return ERR; } const MP4SampleId numberOfSamples = MP4GetTrackNumberOfSamples(m_hFile, m_trackId); if (0 >= numberOfSamples) { qWarning() << "Failed to read number of samples from file:" << getUrlString(); return ERR; } m_maxSampleBlockId = kSampleBlockIdMin + (numberOfSamples - 1); // Determine the maximum input size (in bytes) of a // sample block for the selected track. const u_int32_t maxSampleBlockInputSize = MP4GetTrackMaxSampleSize(m_hFile, m_trackId); m_inputBuffer.resize(maxSampleBlockInputSize, 0); DEBUG_ASSERT(NULL == m_hDecoder); // not already opened m_hDecoder = NeAACDecOpen(); if (!m_hDecoder) { qWarning() << "Failed to open the AAC decoder!"; return ERR; } NeAACDecConfigurationPtr pDecoderConfig = NeAACDecGetCurrentConfiguration( m_hDecoder); pDecoderConfig->outputFormat = FAAD_FMT_FLOAT; if ((kChannelCountMono == audioSrcCfg.channelCountHint) || (kChannelCountStereo == audioSrcCfg.channelCountHint)) { pDecoderConfig->downMatrix = 1; } else { pDecoderConfig->downMatrix = 0; } pDecoderConfig->defObjectType = LC; if (!NeAACDecSetConfiguration(m_hDecoder, pDecoderConfig)) { qWarning() << "Failed to configure AAC decoder!"; return ERR; } u_int8_t* configBuffer = NULL; u_int32_t configBufferSize = 0; if (!MP4GetTrackESConfiguration(m_hFile, m_trackId, &configBuffer, &configBufferSize)) { /* failed to get mpeg-4 audio config... this is ok. * NeAACDecInit2() will simply use default values instead. */ qWarning() << "Failed to read the MP4 audio configuration." << "Continuing with default values."; } SAMPLERATE_TYPE sampleRate; unsigned char channelCount; if (0 > NeAACDecInit2(m_hDecoder, configBuffer, configBufferSize, &sampleRate, &channelCount)) { free(configBuffer); qWarning() << "Failed to initialize the AAC decoder!"; return ERR; } else { free(configBuffer); } setChannelCount(channelCount); setFrameRate(sampleRate); setFrameCount(getFrameCountForSampleBlockId(m_maxSampleBlockId)); // Resize temporary buffer for decoded sample data const SINT sampleBufferCapacity = frames2samples(kFramesPerSampleBlock); m_sampleBuffer.resetCapacity(sampleBufferCapacity); // Invalidate current position to enforce the following // seek operation m_curFrameIndex = getMaxFrameIndex(); // (Re-)Start decoding at the beginning of the file seekSampleFrame(getMinFrameIndex()); return OK; }
Result SoundSourceM4A::tryOpen(const AudioSourceConfig& audioSrcCfg) { DEBUG_ASSERT(MP4_INVALID_FILE_HANDLE == m_hFile); // open MP4 file, check for >= ver 1.9.1 // From mp4v2/file.h: // * On Windows, this should be a UTF-8 encoded string. // * On other platforms, it should be an 8-bit encoding that is // * appropriate for the platform, locale, file system, etc. // * (prefer to use UTF-8 when possible). #if MP4V2_PROJECT_version_hex <= 0x00010901 m_hFile = MP4Read(getLocalFileName().toUtf8().constData(), 0); #else m_hFile = MP4Read(getLocalFileName().toUtf8().constData()); #endif if (MP4_INVALID_FILE_HANDLE == m_hFile) { qWarning() << "Failed to open file for reading:" << getUrlString(); return ERR; } m_trackId = findFirstAudioTrackId(m_hFile); if (MP4_INVALID_TRACK_ID == m_trackId) { qWarning() << "No AAC track found:" << getUrlString(); return ERR; } // Read fixed sample duration. If the sample duration is not // fixed (that is, if the number of frames per sample block varies // through the file), the call returns MP4_INVALID_DURATION. We // can't currently handle these. m_framesPerSampleBlock = MP4GetTrackFixedSampleDuration(m_hFile, m_trackId); if (MP4_INVALID_DURATION == m_framesPerSampleBlock) { qWarning() << "Unable to decode tracks with non-fixed sample durations: " << getUrlString(); return ERR; } const MP4SampleId numberOfSamples = MP4GetTrackNumberOfSamples(m_hFile, m_trackId); if (0 >= numberOfSamples) { qWarning() << "Failed to read number of samples from file:" << getUrlString(); return ERR; } m_maxSampleBlockId = kSampleBlockIdMin + (numberOfSamples - 1); // Determine the maximum input size (in bytes) of a // sample block for the selected track. const u_int32_t maxSampleBlockInputSize = MP4GetTrackMaxSampleSize(m_hFile, m_trackId); m_inputBuffer.resize(maxSampleBlockInputSize, 0); DEBUG_ASSERT(nullptr == m_hDecoder); // not already opened m_hDecoder = NeAACDecOpen(); if (!m_hDecoder) { qWarning() << "Failed to open the AAC decoder!"; return ERR; } NeAACDecConfigurationPtr pDecoderConfig = NeAACDecGetCurrentConfiguration( m_hDecoder); pDecoderConfig->outputFormat = FAAD_FMT_FLOAT; if ((kChannelCountMono == audioSrcCfg.channelCountHint) || (kChannelCountStereo == audioSrcCfg.channelCountHint)) { pDecoderConfig->downMatrix = 1; } else { pDecoderConfig->downMatrix = 0; } pDecoderConfig->defObjectType = LC; if (!NeAACDecSetConfiguration(m_hDecoder, pDecoderConfig)) { qWarning() << "Failed to configure AAC decoder!"; return ERR; } u_int8_t* configBuffer = nullptr; u_int32_t configBufferSize = 0; if (!MP4GetTrackESConfiguration(m_hFile, m_trackId, &configBuffer, &configBufferSize)) { /* failed to get mpeg-4 audio config... this is ok. * NeAACDecInit2() will simply use default values instead. */ qWarning() << "Failed to read the MP4 audio configuration." << "Continuing with default values."; } SAMPLERATE_TYPE sampleRate; unsigned char channelCount; if (0 > NeAACDecInit2(m_hDecoder, configBuffer, configBufferSize, &sampleRate, &channelCount)) { free(configBuffer); qWarning() << "Failed to initialize the AAC decoder!"; return ERR; } else { free(configBuffer); } // Calculate how many sample blocks we need to decode in advance // of a random seek in order to get the recommended number of // prefetch frames m_numberOfPrefetchSampleBlocks = (kNumberOfPrefetchFrames + (m_framesPerSampleBlock - 1)) / m_framesPerSampleBlock; setChannelCount(channelCount); setFrameRate(sampleRate); setFrameCount(((m_maxSampleBlockId - kSampleBlockIdMin) + 1) * m_framesPerSampleBlock); // Resize temporary buffer for decoded sample data const SINT sampleBufferCapacity = frames2samples(m_framesPerSampleBlock); m_sampleBuffer.resetCapacity(sampleBufferCapacity); // Invalidate current position to enforce the following // seek operation m_curFrameIndex = getMaxFrameIndex(); // (Re-)Start decoding at the beginning of the file seekSampleFrame(getMinFrameIndex()); return OK; }
SoundSourceOpus::SoundSourceOpus(const QUrl& url) : SoundSource(url, "opus"), m_pOggOpusFile(nullptr), m_curFrameIndex(getMinFrameIndex()) { }