forked from kyleneideck/BackgroundMusic
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBGMPlayThrough.h
186 lines (144 loc) · 8.05 KB
/
BGMPlayThrough.h
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
185
// This file is part of Background Music.
//
// Background Music is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 2 of the
// License, or (at your option) any later version.
//
// Background Music is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Background Music. If not, see <http://www.gnu.org/licenses/>.
//
// BGMPlayThrough.h
// BGMApp
//
// Copyright © 2016 Kyle Neideck
//
// Reads audio from an input device and immediately writes it to an output device. We currently use this class with the input
// device always set to BGMDevice and the output device set to the one selected in the preferences menu.
//
// Apple's CAPlayThrough sample code (https://developer.apple.com/library/mac/samplecode/CAPlayThrough/Introduction/Intro.html)
// has a similar class, but I couldn't get it fast enough to use here. Soundflower also has a similar class
// (https://github.com/mattingalls/Soundflower/blob/master/SoundflowerBed/AudioThruEngine.h) that seems to be based on Apple
// sample code from 2004. This class's main addition is pausing playthrough when idle to save CPU.
//
// Playing audio with this class uses more CPU, mostly in the coreaudiod process, than playing audio normally because we need
// an input IO proc as well as an output one, and BGMDriver is running in addition to the output device's driver. For me, it
// usually adds around 1-2% (as a percentage of total usage -- it doesn't seem to be relative to the CPU used when playing
// audio normally).
//
// This class will hopefully not be needed after CoreAudio's aggregate devices get support for controls, which is planned for
// a future release.
//
#ifndef BGMApp__BGMPlayThrough
#define BGMApp__BGMPlayThrough
// PublicUtility Includes
#include "CARingBuffer.h"
#include "CAHALAudioDevice.h"
#include "CAMutex.h"
// STL Includes
#include <atomic>
// System Includes
#include <mach/semaphore.h>
#pragma clang assume_nonnull begin
class BGMPlayThrough
{
public:
BGMPlayThrough(CAHALAudioDevice inInputDevice, CAHALAudioDevice inOutputDevice);
~BGMPlayThrough();
// Disallow copying
BGMPlayThrough(const BGMPlayThrough&) = delete;
BGMPlayThrough& operator=(const BGMPlayThrough&) = delete;
// Move constructor/assignment
BGMPlayThrough(BGMPlayThrough&& inPlayThrough) { Swap(inPlayThrough); }
BGMPlayThrough& operator=(BGMPlayThrough&& inPlayThrough) { Swap(inPlayThrough); return *this; }
#ifdef __OBJC__
// Only intended as a convenience for Objective-C instance vars
BGMPlayThrough() { }
#endif
private:
void Swap(BGMPlayThrough& inPlayThrough);
void Activate();
void Deactivate();
void AllocateBuffer();
static bool IsBGMDevice(CAHALAudioDevice inDevice);
void CreateIOProcs();
void DestroyIOProcs();
public:
void Start();
// Blocks until the output device has started our IOProc. Returns one of the error constants
// from AudioHardwareBase.h (e.g. kAudioHardwareNoError).
OSStatus WaitForOutputDeviceToStart() noexcept;
private:
void ReleaseThreadsWaitingForOutputToStart() const;
public:
OSStatus Stop();
void StopIfIdle();
private:
static OSStatus BGMDeviceListenerProc(AudioObjectID inObjectID,
UInt32 inNumberAddresses,
const AudioObjectPropertyAddress* inAddresses,
void* __nullable inClientData);
static void HandleBGMDeviceIsRunning(BGMPlayThrough* refCon);
static void HandleBGMDeviceIsRunningSomewhereOtherThanBGMApp(BGMPlayThrough* refCon);
static bool IsRunningSomewhereOtherThanBGMApp(const CAHALAudioDevice& inBGMDevice);
static OSStatus InputDeviceIOProc(AudioObjectID inDevice,
const AudioTimeStamp* inNow,
const AudioBufferList* inInputData,
const AudioTimeStamp* inInputTime,
AudioBufferList* outOutputData,
const AudioTimeStamp* inOutputTime,
void* __nullable inClientData);
static OSStatus OutputDeviceIOProc(AudioObjectID inDevice,
const AudioTimeStamp* inNow,
const AudioBufferList* inInputData,
const AudioTimeStamp* inInputTime,
AudioBufferList* outOutputData,
const AudioTimeStamp* inOutputTime,
void* __nullable inClientData);
// The state of an IO proc. Used by the IO proc to tell other threads when it's finished starting. Used by other
// threads to tell the IO proc to stop itself. (Probably used for other things as well.)
enum class IOState
{
Stopped, Starting, Running, Stopping
};
// The IO procs call this to update their IOState member. Also stops the IO proc if its state has been set to Stopping.
// Returns true if it changes the state.
static bool UpdateIOProcState(const char* __nullable callerName,
std::atomic<IOState>& inState,
AudioDeviceIOProcID __nullable inIOProcID,
CAHALAudioDevice& inDevice,
IOState& outNewState);
static void HandleRingBufferError(CARingBufferError err,
const char* methodName,
const char* callReturningErr);
private:
CARingBuffer mBuffer;
AudioDeviceIOProcID __nullable mInputDeviceIOProcID;
AudioDeviceIOProcID __nullable mOutputDeviceIOProcID;
CAHALAudioDevice mInputDevice { kAudioDeviceUnknown };
CAHALAudioDevice mOutputDevice { kAudioDeviceUnknown };
CAMutex mStateMutex { "Playthrough state" };
// Signalled when the output IO proc runs. We use it to tell BGMDriver when the output device is ready to receive audio data.
semaphore_t mOutputDeviceIOProcSemaphore { SEMAPHORE_NULL };
bool mActive = false;
bool mPlayingThrough = false;
UInt64 mLastNotifiedIOStoppedOnBGMDevice;
std::atomic<IOState> mInputDeviceIOProcState { IOState::Stopped };
std::atomic<IOState> mOutputDeviceIOProcState { IOState::Stopped };
// For debug logging.
UInt64 mToldOutputDeviceToStartAt;
// IO proc vars. (Should only be used inside IO procs.)
// The earliest/latest sample times seen by the IO procs since starting playthrough. -1 for unset.
Float64 mFirstInputSampleTime = -1;
Float64 mLastInputSampleTime = -1;
Float64 mLastOutputSampleTime = -1;
// Subtract this from the output time to get the input time.
Float64 mInToOutSampleOffset;
};
#pragma clang assume_nonnull end
#endif /* BGMApp__BGMPlayThrough */