summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/library/relocatedtrack.h11
-rw-r--r--src/sources/audiosource.cpp49
-rw-r--r--src/sources/audiosource.h63
-rw-r--r--src/sources/soundsourcem4a.h8
-rw-r--r--src/sources/soundsourceproxy.cpp111
5 files changed, 158 insertions, 84 deletions
diff --git a/src/library/relocatedtrack.h b/src/library/relocatedtrack.h
index e2293ce0a6..3899876327 100644
--- a/src/library/relocatedtrack.h
+++ b/src/library/relocatedtrack.h
@@ -19,11 +19,12 @@ class RelocatedTrack final {
DEBUG_ASSERT(m_missingTrackRef.isValid());
DEBUG_ASSERT(m_missingTrackRef.hasId());
DEBUG_ASSERT(m_missingTrackRef.hasLocation());
- DEBUG_ASSERT(m_addedTrackRef.isValid());
- // NOTE(uklotzde, 2019-12-16):
- // m_addedTrackRef might not have a valid TrackId
- // if the relocated track has not been added as a
- // new track to the internal collection before.
+ // NOTE: m_addedTrackRef might not be valid!
+ // It might not have a valid TrackId if the relocated track has
+ // not been added as a new track to the internal collection before.
+ // It may also not have a valid canonical location, e.g. when
+ // relinking directories and the track is missing at both the old
+ // and the new location.
DEBUG_ASSERT(m_addedTrackRef.hasLocation());
DEBUG_ASSERT(updatedTrackRef().isValid());
DEBUG_ASSERT(updatedTrackRef().hasId());
diff --git a/src/sources/audiosource.cpp b/src/sources/audiosource.cpp
index 91ab264d91..d603ff7b0c 100644
--- a/src/sources/audiosource.cpp
+++ b/src/sources/audiosource.cpp
@@ -8,6 +8,12 @@ namespace {
const Logger kLogger("AudioSource");
+// Maximum number of sample frames to verify that decoding the audio
+// stream works.
+// NOTE(2020-05-01): A single frame is sufficient to reliably detect
+// the broken FAAD2 v2.9.1 library.
+const SINT kVerifyReadableMaxFrameCount = 1;
+
} // anonymous namespace
AudioSource::AudioSource(QUrl url)
@@ -132,7 +138,9 @@ bool AudioSource::initBitrateOnce(audio::Bitrate bitrate) {
return true;
}
-bool AudioSource::verifyReadable() const {
+bool AudioSource::verifyReadable() {
+ // No early return desired! All tests should be performed, even
+ // if some fail.
bool result = true;
DEBUG_ASSERT(m_signalInfo.getSampleLayout());
if (!m_signalInfo.getChannelCount().isValid()) {
@@ -170,13 +178,44 @@ bool AudioSource::verifyReadable() const {
// to decode audio data!
}
}
+ if (!result) {
+ // Invalid or inconsistent properties detected. We can abort
+ // at this point and do not need to perform any read tests.
+ return false;
+ }
if (frameIndexRange().empty()) {
kLogger.warning()
- << "No audio data available";
- // Don't set the result to false, even if reading from an
- // empty source is pointless!
+ << "No audio data available, i.e. stream is empty";
+ // Don't return false, even if reading from an empty source
+ // is pointless. It is still a valid audio stream.
+ return true;
}
- return result;
+ // Try to read some test frames to ensure that decoding actually works!
+ //
+ // Counterexample: The broken FAAD version 2.9.1 is able to open a file
+ // but then fails to decode any sample frames.
+ const SINT numSampleFrames =
+ math_min(kVerifyReadableMaxFrameCount, frameIndexRange().length());
+ SampleBuffer sampleBuffer(
+ m_signalInfo.frames2samples(numSampleFrames));
+ WritableSampleFrames writableSampleFrames(
+ frameIndexRange().splitAndShrinkFront(numSampleFrames),
+ SampleBuffer::WritableSlice(sampleBuffer));
+ auto readableSampleFrames = readSampleFrames(writableSampleFrames);
+ DEBUG_ASSERT(
+ readableSampleFrames.frameIndexRange() <=
+ writableSampleFrames.frameIndexRange());
+ if (readableSampleFrames.frameIndexRange() <
+ writableSampleFrames.frameIndexRange()) {
+ kLogger.warning()
+ << "Read test failed:"
+ << "expected ="
+ << writableSampleFrames.frameIndexRange()
+ << ", actual ="
+ << readableSampleFrames.frameIndexRange();
+ return false;
+ }
+ return true;
}
WritableSampleFrames AudioSource::clampWritableSampleFrames(
diff --git a/src/sources/audiosource.h b/src/sources/audiosource.h
index 5ca3ec752b..0118148eb0 100644
--- a/src/sources/audiosource.h
+++ b/src/sources/audiosource.h
@@ -151,32 +151,50 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour
// left and right channel respectively.
static constexpr audio::SampleLayout kSampleLayout = mixxx::kEngineSampleLayout;
+ /// Defines how thoroughly the stream properties should be verified
+ /// when opening an audio stream.
enum class OpenMode {
- // In Strict mode the opening operation should be aborted
- // as soon as any inconsistencies are detected.
+ /// In Strict mode the opening operation should be aborted
+ /// as soon as any inconsistencies of the stream properties
+ /// are detected when setting up the decoding.
Strict,
- // Opening in Permissive mode is used only after opening
- // in Strict mode has been aborted by all available
- // SoundSource implementations.
+
+ /// Opening in Permissive mode is used only after opening
+ /// in Strict mode has been aborted by all available
+ /// SoundSource implementations.
+ ///
+ /// Example: Assume safe default values if mandatory stream
+ /// properties could not be determined when setting up the
+ /// decoding.
Permissive,
};
+ /// Result of opening an audio stream.
enum class OpenResult {
+ /// The file has been opened successfully and should be readable.
Succeeded,
- // If a SoundSource is not able to open a file because of
- // internal errors of if the format of the content is not
- // supported it should return Aborted. This gives SoundSources
- // with a lower priority the chance to open the same file.
- // Example: A SoundSourceProvider has been registered for
- // files with a certain extension, but the corresponding
- // SoundSource does only support a subset of all possible
- // data formats that might be stored in files with this
- // extension.
+
+ /// If a SoundSource is not able to open a file because of
+ /// internal errors of if the format of the content is not
+ /// supported it should return Aborted.
+ ///
+ /// Returing this error result gives other decoders with a
+ /// lower priority the chance to open the same file.
+ /// Example: A SoundSourceProvider has been registered for
+ /// files with a certain extension, but the corresponding
+ /// SoundSource does only support a subset of all possible
+ /// data formats that might be stored in files with this
+ /// extension.
+
Aborted,
- // If a SoundSource return Failed while opening a file
- // the entire operation will fail immediately. No other
- // sources with lower priority will be given the chance
- // to open the same file.
+ /// If a SoundSource return Failed while opening a file the
+ /// file itself is supposed to be corrupt.
+ ///
+ /// If this happens during OpenMode::Strict then other decoders
+ /// with a lower priority are still given a chance to try
+ /// opening the file. In OpenMode::Permissive the first
+ /// SoundSource that returns Failed will declare this file
+ /// corrupt.
Failed,
};
@@ -286,10 +304,11 @@ class AudioSource : public UrlResource, public virtual /*implements*/ IAudioSour
return getSignalInfo().frames2secs(frameLength());
}
- // Verifies various properties to ensure that the audio data is
- // actually readable. Warning messages are logged for properties
- // with invalid values for diagnostic purposes.
- bool verifyReadable() const;
+ /// Verifies various properties to ensure that the audio data is
+ /// actually readable. Warning messages are logged for properties
+ /// with invalid values for diagnostic purposes. Also performs a
+ /// basic read test.
+ bool verifyReadable();
ReadableSampleFrames readSampleFrames(
WritableSampleFrames sampleFrames);
diff --git a/src/sources/soundsourcem4a.h b/src/sources/soundsourcem4a.h
index 33c2b6c3df..d13cba6875 100644
--- a/src/sources/soundsourcem4a.h
+++ b/src/sources/soundsourcem4a.h
@@ -18,6 +18,14 @@
namespace mixxx {
+/// Decode M4A (AAC in MP4) files using FAAD2.
+///
+/// NOTE(2020-05-01): Decoding in version v2.9.1 of the library
+/// is broken and any attempt to open files will fail.
+///
+/// https://github.com/mixxxdj/mixxx/pull/2738
+/// https://github.com/knik0/faad2/commit/a8dc3f8ce67f4069cfa4d5cf0fcc2c6e8ef2c2aa
+/// https://github.com/knik0/faad2/commit/920ec985a74c6f88fe507181df07a0cd7e51d519
class SoundSourceM4A : public SoundSource {
public:
explicit SoundSourceM4A(const QUrl& url);
diff --git a/src/sources/soundsourceproxy.cpp b/src/sources/soundsourceproxy.cpp
index 6bfcc3ae21..3826e8ec26 100644
--- a/src/sources/soundsourceproxy.cpp
+++ b/src/sources/soundsourceproxy.cpp
@@ -481,68 +481,75 @@ QImage SoundSourceProxy::importCoverImage() const {
mixxx::AudioSourcePointer SoundSourceProxy::openAudioSource(const mixxx::AudioSource::OpenParams& params) {
DEBUG_ASSERT(m_pTrack);
auto openMode = mixxx::SoundSource::OpenMode::Strict;
+ int attemptCount = 0;
while (m_pSoundSource && !m_pAudioSource) {
- // NOTE(uklotzde): Log unconditionally (with debug level) to
- // identify files in the log file that might have caused a
- // crash while importing metadata or decoding audio subsequently.
- kLogger.debug() << "Opening file"
- << getUrl().toString()
- << "with provider"
- << getSoundSourceProvider()->getName()
- << "using mode"
- << openMode;
+ ++attemptCount;
const mixxx::SoundSource::OpenResult openResult =
m_pSoundSource->open(openMode, params);
- if ((openResult == mixxx::SoundSource::OpenResult::Aborted) ||
- ((openMode == mixxx::SoundSource::OpenMode::Strict) && (openResult == mixxx::SoundSource::OpenResult::Failed))) {
- kLogger.warning() << "Unable to open file"
- << getUrl().toString()
- << "with provider"
- << getSoundSourceProvider()->getName()
- << "using mode"
- << openMode;
- // Continue with the next SoundSource provider
- nextSoundSourceProvider();
- if (!getSoundSourceProvider() && (openMode == mixxx::SoundSource::OpenMode::Strict)) {
- // No provider was able to open the source in Strict mode.
- // Retry to open the file in Permissive mode starting with
- // the first provider...
- m_soundSourceProviderRegistrationIndex = 0;
- openMode = mixxx::SoundSource::OpenMode::Permissive;
- }
- initSoundSource();
- continue; // try again
- }
- if ((openResult == mixxx::SoundSource::OpenResult::Succeeded) && m_pSoundSource->verifyReadable()) {
- m_pAudioSource = mixxx::AudioSourceTrackProxy::create(m_pTrack, m_pSoundSource);
- DEBUG_ASSERT(m_pAudioSource);
- if (m_pAudioSource->frameIndexRange().empty()) {
- kLogger.warning() << "File is empty"
- << getUrl().toString();
- }
- // Overwrite metadata with actual audio properties
- if (m_pTrack) {
- m_pTrack->updateAudioPropertiesFromStream(
- m_pAudioSource->getStreamInfo());
+ if (openResult == mixxx::SoundSource::OpenResult::Succeeded) {
+ if (m_pSoundSource->verifyReadable()) {
+ m_pAudioSource = mixxx::AudioSourceTrackProxy::create(m_pTrack, m_pSoundSource);
+ DEBUG_ASSERT(m_pAudioSource);
+ // Overwrite metadata with actual audio properties
+ if (m_pTrack) {
+ m_pTrack->updateAudioPropertiesFromStream(
+ m_pAudioSource->getStreamInfo());
+ }
+ return m_pAudioSource;
}
+ kLogger.warning()
+ << "Failed to read file"
+ << getUrl().toString()
+ << "with provider"
+ << getSoundSourceProvider()->getName();
+ m_pSoundSource->close(); // cleanup
} else {
- kLogger.warning() << "Failed to open file"
- << getUrl().toString()
- << "with provider"
- << getSoundSourceProvider()->getName();
- if (openResult == mixxx::SoundSource::OpenResult::Succeeded) {
- m_pSoundSource->close(); // cleanup
+ kLogger.warning()
+ << "Failed to open file"
+ << getUrl().toString()
+ << "with provider"
+ << getSoundSourceProvider()->getName()
+ << "using mode"
+ << openMode;
+ if (openMode == mixxx::SoundSource::OpenMode::Permissive) {
+ if (openResult == mixxx::SoundSource::OpenResult::Failed) {
+ // Do NOT retry with the next SoundSource provider if the file
+ // itself seems to be the cause when opening still fails during
+ // the 2nd (= permissive) round.
+ DEBUG_ASSERT(!m_pAudioSource);
+ return m_pAudioSource;
+ } else {
+ // Continue and give other providers the chance to open the file
+ // in turn.
+ DEBUG_ASSERT(openResult == mixxx::SoundSource::OpenResult::Aborted);
+ }
+ } else {
+ // Continue and give other providers the chance to open the file
+ // in turn, independent of why opening the file failed.
+ DEBUG_ASSERT(openMode == mixxx::SoundSource::OpenMode::Strict);
}
- // Do NOT retry with the next SoundSource provider if the file
- // itself is the cause!
- DEBUG_ASSERT(!m_pAudioSource);
}
- return m_pAudioSource; // either success or failure
+ // Continue with the next SoundSource provider
+ nextSoundSourceProvider();
+ if (!getSoundSourceProvider() &&
+ (openMode == mixxx::SoundSource::OpenMode::Strict)) {
+ // No provider was able to open the source in Strict mode.
+ // Retry to open the file in Permissive mode starting with
+ // the first provider...
+ m_soundSourceProviderRegistrationIndex = 0;
+ openMode = mixxx::SoundSource::OpenMode::Permissive;
+ }
+ initSoundSource();
+ // try again
}
// All available providers have returned OpenResult::Aborted when
// getting here. m_pSoundSource might already be invalid/null!
- kLogger.warning() << "Unable to decode file"
- << getUrl().toString();
+ kLogger.warning()
+ << "Giving up to open file"
+ << getUrl().toString()
+ << "after"
+ << attemptCount
+ << "unsuccessful attempts";
DEBUG_ASSERT(!m_pAudioSource);
return m_pAudioSource;
}