summaryrefslogtreecommitdiffstats
path: root/src/sources/soundsourcemediafoundation.h
blob: 1f02bcea4b6dcee54c0673f19b79f56ab184fc82 (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
#pragma once

#include <mfidl.h>
#include <mfreadwrite.h>

#include "sources/soundsourceprovider.h"
#include "util/readaheadsamplebuffer.h"

namespace mixxx {

class StreamUnitConverter final {
    const static SINT kStreamUnitsPerSecond = 1000 * 1000 * 10; // frame length = 100 ns

  public:
    StreamUnitConverter()
            : m_pAudioSource(nullptr),
              m_fromSampleFramesToStreamUnits(0),
              m_fromStreamUnitsToSampleFrames(0) {
    }
    explicit StreamUnitConverter(const AudioSource* pAudioSource)
            : m_pAudioSource(pAudioSource),
              m_fromSampleFramesToStreamUnits(double(kStreamUnitsPerSecond) / double(pAudioSource->getSignalInfo().getSampleRate())),
              m_fromStreamUnitsToSampleFrames(double(pAudioSource->getSignalInfo().getSampleRate()) / double(kStreamUnitsPerSecond)) {
        // The stream units should actually be much shorter than
        // sample frames to minimize jitter and rounding. Even a
        // frame at 192 kHz has a length of about 5000 ns >> 100 ns.
        DEBUG_ASSERT(m_fromSampleFramesToStreamUnits > 50);
        DEBUG_ASSERT(m_fromStreamUnitsToSampleFrames < 0.02);
    }

    LONGLONG fromFrameIndex(SINT frameIndex) const {
        DEBUG_ASSERT(m_fromSampleFramesToStreamUnits > 0);
        // Used for seeking, so we need to round down to hit the
        // corresponding stream unit where the given stream unit
        // starts. The reader will skip samples until it reaches
        // the actual target position for reading.
        const SINT frameIndexOffset = frameIndex - m_pAudioSource->frameIndexMin();
        return static_cast<LONGLONG>(floor(frameIndexOffset * m_fromSampleFramesToStreamUnits));
    }

    SINT toFrameIndex(LONGLONG streamPos) const {
        DEBUG_ASSERT(m_fromStreamUnitsToSampleFrames > 0);
        // The stream reports positions in units of 100ns. We have
        // to round(!) this value to obtain the actual position in
        // sample frames.
        const SINT frameIndexOffset = static_cast<SINT>(round(streamPos * m_fromStreamUnitsToSampleFrames));
        return m_pAudioSource->frameIndexMin() + frameIndexOffset;
    }

  private:
    const AudioSource* m_pAudioSource;
    double m_fromSampleFramesToStreamUnits;
    double m_fromStreamUnitsToSampleFrames;
};

class SoundSourceMediaFoundation : public SoundSource {
  public:
    static const QString kDisplayName;

    explicit SoundSourceMediaFoundation(const QUrl& url);
    ~SoundSourceMediaFoundation() override;

    void close() override;

  protected:
    ReadableSampleFrames readSampleFramesClamped(
            const WritableSampleFrames& sampleFrames) override;

  private:
    OpenResult tryOpen(
            OpenMode mode,
            const AudioSource::OpenParams& params) override;

    bool configureAudioStream(const AudioSource::OpenParams& params);
    bool readProperties();

    void seekSampleFrame(SINT frameIndex);

    HRESULT m_hrCoInitialize;
    HRESULT m_hrMFStartup;

    IMFSourceReader* m_pSourceReader;

    StreamUnitConverter m_streamUnitConverter;

    SINT m_currentFrameIndex;

    ReadAheadSampleBuffer m_sampleBuffer;
};

class SoundSourceProviderMediaFoundation : public SoundSourceProvider {
  public:
    static const QString kDisplayName;
    static const QStringList kSupportedFileExtensions;

    QString getDisplayName() const override {
        return kDisplayName;
    }

    QStringList getSupportedFileExtensions() const override {
        return kSupportedFileExtensions;
    }

    SoundSourceProviderPriority getPriorityHint(
            const QString& supportedFileExtension) const override;

    SoundSourcePointer newSoundSource(const QUrl& url) override;
};

} // namespace mixxx