summaryrefslogtreecommitdiffstats
path: root/src/sources/soundsourcesndfile.cpp
blob: bff34af04fab900ef390db8ded748af1f9c48d5e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#include "sources/soundsourcesndfile.h"

#include <QDir>

#include "util/logger.h"
#include "util/semanticversion.h"

namespace mixxx {

namespace {

const Logger kLogger("SoundSourceSndFile");

const QStringList kSupportedFileExtensions = {
        QStringLiteral("aif"),
        QStringLiteral("aiff"),
        // ALAC/CAF has been added in version 1.0.26
        // NOTE(uklotzde, 2015-05-26): Unfortunately ALAC in M4A containers
        // is still not supported https://github.com/mixxxdj/mixxx/pull/904#issuecomment-221928362
        QStringLiteral("caf"),
        QStringLiteral("flac"),
        QStringLiteral("ogg"),
        QStringLiteral("wav"),
};

// SoundSourceProxyTest fails for version >= 1.0.30 and OGG files
// https://github.com/libsndfile/libsndfile/issues/643
const mixxx::SemanticVersion kVersionStringWithBrokenOggDecoding(1, 0, 30);

QStringList getSupportedFileExtensionsFiltered() {
    auto supportedFileExtensions = kSupportedFileExtensions;
    QString libsndfileVersion = sf_version_string();
    int separatorIndex = libsndfileVersion.lastIndexOf("-");
    auto semver = mixxx::SemanticVersion(libsndfileVersion.right(separatorIndex));
    if (semver >= kVersionStringWithBrokenOggDecoding) {
        kLogger.info()
                << "Disabling OGG decoding for"
                << libsndfileVersion;
        supportedFileExtensions.removeAll(QStringLiteral("ogg"));
    }
    return supportedFileExtensions;
};

} // anonymous namespace

//static
const QString SoundSourceProviderSndFile::kDisplayName = QStringLiteral("libsndfile");

SoundSourceProviderSndFile::SoundSourceProviderSndFile()
        : m_supportedFileExtensions(getSupportedFileExtensionsFiltered()) {
}

QStringList SoundSourceProviderSndFile::getSupportedFileExtensions() const {
    return m_supportedFileExtensions;
}

SoundSourceProviderPriority SoundSourceProviderSndFile::getPriorityHint(
        const QString& supportedFileExtension) const {
    if (supportedFileExtension.startsWith(QStringLiteral("aif")) ||
            supportedFileExtension == QLatin1String("wav")) {
        // Default decoder for AIFF and WAV
        return SoundSourceProviderPriority::Default;
    } else {
        // Otherwise only used as fallback
        return SoundSourceProviderPriority::Lower;
    }
}

SoundSourceSndFile::SoundSourceSndFile(const QUrl& url)
        : SoundSource(url),
          m_pSndFile(nullptr),
          m_curFrameIndex(0) {
}

SoundSourceSndFile::~SoundSourceSndFile() {
    close();
}

SoundSource::OpenResult SoundSourceSndFile::tryOpen(
        OpenMode /*mode*/,
        const OpenParams& /*config*/) {
    DEBUG_ASSERT(!m_pSndFile);
    SF_INFO sfInfo;
    memset(&sfInfo, 0, sizeof(sfInfo));
#ifdef __WINDOWS__
    // Note: we cannot use QString::toStdWString since QT 4 is compiled with
    // '/Zc:wchar_t-' flag and QT 5 not
    const QString localFileName(QDir::toNativeSeparators(getLocalFileName()));
    const ushort* const fileNameUtf16 = localFileName.utf16();
    static_assert(sizeof(wchar_t) == sizeof(ushort), "QString::utf16(): wchar_t and ushort have different sizes");
    m_pSndFile = sf_wchar_open(
            reinterpret_cast<wchar_t*>(const_cast<ushort*>(fileNameUtf16)),
            SFM_READ,
            &sfInfo);
#else
    m_pSndFile = sf_open(QFile::encodeName(getLocalFileName()), SFM_READ, &sfInfo);
#endif

    switch (sf_error(m_pSndFile)) {
    case SF_ERR_NO_ERROR:
        DEBUG_ASSERT(m_pSndFile != nullptr);
        break; // continue
    case SF_ERR_UNRECOGNISED_FORMAT:
        return OpenResult::Aborted;
    default:
        const QString errorMsg(sf_strerror(m_pSndFile));
        if (errorMsg.toLower().indexOf("unknown format") != -1) {
            // NOTE(uklotzde 2016-05-11): This actually happens when
            // trying to open a file with a supported file extension
            // that contains data in an unsupported format!
            return OpenResult::Aborted;
        } else {
            kLogger.warning() << "Error opening libsndfile file:"
                              << getUrlString()
                              << errorMsg;
            return OpenResult::Failed;
        }
    }

    initChannelCountOnce(sfInfo.channels);
    initSampleRateOnce(sfInfo.samplerate);
    initFrameIndexRangeOnce(IndexRange::forward(0, sfInfo.frames));

    m_curFrameIndex = frameIndexMin();

    return OpenResult::Succeeded;
}

void SoundSourceSndFile::close() {
    if (m_pSndFile != nullptr) {
        const int closeResult = sf_close(m_pSndFile);
        if (0 == closeResult) {
            m_pSndFile = nullptr;
            m_curFrameIndex = frameIndexMin();
        } else {
            kLogger.warning() << "Failed to close file:" << closeResult
                              << sf_strerror(m_pSndFile)
                              << getUrlString();
        }
    }
}

ReadableSampleFrames SoundSourceSndFile::readSampleFramesClamped(
        const WritableSampleFrames& writableSampleFrames) {
    const SINT firstFrameIndex = writableSampleFrames.frameIndexRange().start();

    if (m_curFrameIndex != firstFrameIndex) {
        const sf_count_t seekResult = sf_seek(m_pSndFile, firstFrameIndex, SEEK_SET);
        if (seekResult == firstFrameIndex) {
            m_curFrameIndex = seekResult;
        } else {
            kLogger.warning() << "Failed to seek libsnd file:" << seekResult
                              << sf_strerror(m_pSndFile);
            m_curFrameIndex = sf_seek(m_pSndFile, 0, SEEK_CUR);
            return ReadableSampleFrames(IndexRange::between(m_curFrameIndex, m_curFrameIndex));
        }
    }
    DEBUG_ASSERT(m_curFrameIndex == firstFrameIndex);

    const SINT numberOfFramesTotal = writableSampleFrames.frameLength();

    const sf_count_t readCount =
            sf_readf_float(m_pSndFile, writableSampleFrames.writableData(), numberOfFramesTotal);
    if (readCount >= 0) {
        DEBUG_ASSERT(readCount <= numberOfFramesTotal);
        const auto resultRange = IndexRange::forward(m_curFrameIndex, readCount);
        m_curFrameIndex += readCount;
        return ReadableSampleFrames(
                resultRange,
                SampleBuffer::ReadableSlice(
                        writableSampleFrames.writableData(),
                        getSignalInfo().frames2samples(readCount)));
    } else {
        kLogger.warning() << "Failed to read from libsnd file:"
                          << readCount
                          << sf_strerror(m_pSndFile);
        return ReadableSampleFrames(
                IndexRange::between(
                        m_curFrameIndex,
                        m_curFrameIndex));
    }
}

} // namespace mixxx