Audacity 3.2.0
DeviceManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity - A Digital Audio Editor
4 Copyright 1999-2010 Audacity Team
5 Michael Chinen
6
7******************************************************************/
8
9
10#include "DeviceManager.h"
11
12#include <wx/log.h>
13#include <thread>
14
15
16
17#include "portaudio.h"
18#ifdef __WXMSW__
19#include "pa_win_wasapi.h"
20#endif
21
22#ifdef USE_PORTMIXER
23#include "portmixer.h"
24#endif
25
26#include "AudioIOBase.h"
27
28#include "DeviceChange.h" // for HAVE_DEVICE_CHANGE
29
31
34{
35 return &dm;
36}
37
38const std::vector<DeviceSourceMap> &DeviceManager::GetInputDeviceMaps()
39{
40 if (!m_inited)
41 Init();
43}
44const std::vector<DeviceSourceMap> &DeviceManager::GetOutputDeviceMaps()
45{
46 if (!m_inited)
47 Init();
49}
50
51
53{
54 wxString ret;
55 ret = map->deviceString;
56 if (map->totalSources > 1)
57 ret += wxT(": ") + map->sourceString;
58
59 return ret;
60}
61
63{
64 if (hostIndex < 0 || hostIndex >= Pa_GetHostApiCount()) {
65 return NULL;
66 }
67
68 const struct PaHostApiInfo *apiinfo = Pa_GetHostApiInfo(hostIndex); // get info on API
69 std::vector<DeviceSourceMap> & maps = isInput ? mInputDeviceSourceMaps : mOutputDeviceSourceMaps;
70 size_t i;
71 int targetDevice = isInput ? apiinfo->defaultInputDevice : apiinfo->defaultOutputDevice;
72
73 for (i = 0; i < maps.size(); i++) {
74 if (maps[i].deviceIndex == targetDevice)
75 return &maps[i];
76 }
77
78 wxLogDebug(wxT("GetDefaultDevice() no default device"));
79 return NULL;
80}
81
83{
84 return GetDefaultDevice(hostIndex, 0);
85}
87{
88 return GetDefaultDevice(hostIndex, 1);
89}
90
91//--------------- Device Enumeration --------------------------
92
93//Port Audio requires we open the stream with a callback or a lot of devices will fail
94//as this means open in blocking mode, so we use a dummy one.
96 const void *WXUNUSED(input), void * WXUNUSED(output),
97 unsigned long WXUNUSED(frameCount),
98 const PaStreamCallbackTimeInfo* WXUNUSED(timeInfo),
99 PaStreamCallbackFlags WXUNUSED(statusFlags),
100 void *WXUNUSED(userData) )
101{
102 return 0;
103}
104
105static void FillHostDeviceInfo(DeviceSourceMap *map, const PaDeviceInfo *info, int deviceIndex, int isInput)
106{
107 wxString hostapiName = wxSafeConvertMB2WX(Pa_GetHostApiInfo(info->hostApi)->name);
108 wxString infoName = wxSafeConvertMB2WX(info->name);
109
110 map->deviceIndex = deviceIndex;
111 map->hostIndex = info->hostApi;
112 map->deviceString = infoName;
113 map->hostString = hostapiName;
114 map->numChannels = isInput ? info->maxInputChannels : info->maxOutputChannels;
115}
116
117static void AddSourcesFromStream(int deviceIndex, const PaDeviceInfo *info, std::vector<DeviceSourceMap> *maps, PaStream *stream)
118{
119#ifdef USE_PORTMIXER
120 int i;
121#endif
122 DeviceSourceMap map;
123
124 map.sourceIndex = -1;
125 map.totalSources = 0;
126 // Only inputs have sources, so we call FillHostDeviceInfo with a 1 to indicate this
127 FillHostDeviceInfo(&map, info, deviceIndex, 1);
128
129#ifdef USE_PORTMIXER
130 PxMixer *portMixer = Px_OpenMixer(stream, deviceIndex, -1, 0);
131 if (!portMixer) {
132 maps->push_back(map);
133 return;
134 }
135
136 //if there is only one source, we don't need to concatenate the source
137 //or enumerate, because it is something meaningless like 'master'
138 //(as opposed to 'mic in' or 'line in'), and the user doesn't have any choice.
139 //note that some devices have no input sources at all but are still valid.
140 //the behavior we do is the same for 0 and 1 source cases.
141 map.totalSources = Px_GetNumInputSources(portMixer);
142#endif
143
144 if (map.totalSources <= 1) {
145 map.sourceIndex = 0;
146 maps->push_back(map);
147 }
148#ifdef USE_PORTMIXER
149 else {
150 //open up a stream with the device so portmixer can get the info out of it.
151 for (i = 0; i < map.totalSources; i++) {
152 map.sourceIndex = i;
153 map.sourceString = wxString(wxSafeConvertMB2WX(Px_GetInputSourceName(portMixer, i)));
154 maps->push_back(map);
155 }
156 }
157 Px_CloseMixer(portMixer);
158#endif
159}
160
161static bool IsInputDeviceAMapperDevice(const PaDeviceInfo *info)
162{
163 // For Windows only, portaudio returns the default mapper object
164 // as the first index after a NEW hostApi index is detected (true for MME and DS)
165 // this is a bit of a hack, but there's no other way to find out which device is a mapper,
166 // I've looked at string comparisons, but if the system is in a different language this breaks.
167#ifdef __WXMSW__
168 static int lastHostApiTypeId = -1;
169 int hostApiTypeId = Pa_GetHostApiInfo(info->hostApi)->type;
170 if(hostApiTypeId != lastHostApiTypeId &&
171 (hostApiTypeId == paMME || hostApiTypeId == paDirectSound)) {
172 lastHostApiTypeId = hostApiTypeId;
173 return true;
174 }
175#endif
176
177 return false;
178}
179
180static void AddSources(int deviceIndex, int rate, std::vector<DeviceSourceMap> *maps, int isInput)
181{
182 int error = 0;
183 DeviceSourceMap map;
184 const PaDeviceInfo *info = Pa_GetDeviceInfo(deviceIndex);
185
186 // This tries to open the device with the samplerate worked out above, which
187 // will be the highest available for play and record on the device, or
188 // 44.1kHz if the info cannot be fetched.
189
190 PaStream *stream = NULL;
191
192 PaStreamParameters parameters;
193
194 parameters.device = deviceIndex;
195 parameters.sampleFormat = paFloat32;
196 parameters.hostApiSpecificStreamInfo = NULL;
197 parameters.channelCount = 1;
198
199 // If the device is for input, open a stream so we can use portmixer to query
200 // the number of inputs. We skip this for outputs because there are no 'sources'
201 // and some platforms (e.g. XP) have the same device for input and output, (while
202 // Vista/Win7 separate these into two devices with the same names (but different
203 // portaudio indices)
204 // Also, for mapper devices we don't want to keep any sources, so check for it here
205 if (isInput && !IsInputDeviceAMapperDevice(info)) {
206 if (info)
207 parameters.suggestedLatency = info->defaultLowInputLatency;
208 else
209 parameters.suggestedLatency = 10.0;
210
211 error = Pa_OpenStream(&stream,
212 &parameters,
213 NULL,
214 rate, paFramesPerBufferUnspecified,
215 paClipOff | paDitherOff,
217 }
218
219 if (stream && !error) {
220 AddSourcesFromStream(deviceIndex, info, maps, stream);
221 Pa_CloseStream(stream);
222 } else {
223 map.sourceIndex = -1;
224 map.totalSources = 0;
225 FillHostDeviceInfo(&map, info, deviceIndex, isInput);
226 maps->push_back(map);
227 }
228
229 if(error) {
230 wxLogDebug(wxT("PortAudio stream error creating device list: ") +
231 map.hostString + wxT(":") + map.deviceString + wxT(": ") +
232 wxString(wxSafeConvertMB2WX(Pa_GetErrorText((PaError)error))));
233 }
234}
235
239{
240 // get rid of the previous scan info
241 this->mInputDeviceSourceMaps.clear();
242 this->mOutputDeviceSourceMaps.clear();
243
244 // if we are doing a second scan then restart portaudio to get NEW devices
245 if (m_inited) {
246 // check to see if there is a stream open - can happen if monitoring,
247 // but otherwise Rescan() should not be available to the user.
248 auto gAudioIO = AudioIOBase::Get();
249 if (gAudioIO) {
250 if (gAudioIO->IsMonitoring())
251 {
252 using namespace std::chrono;
253 gAudioIO->StopStream();
254 while (gAudioIO->IsBusy())
255 std::this_thread::sleep_for(100ms);
256 }
257 }
258
259 // restart portaudio - this updates the device list
260 // FIXME: TRAP_ERR restarting PortAudio
261 Pa_Terminate();
262 Pa_Initialize();
263 }
264
265 // FIXME: TRAP_ERR PaErrorCode not handled in ReScan()
266 int nDevices = Pa_GetDeviceCount();
267
268 //The hierarchy for devices is Host/device/source.
269 //Some newer systems aggregate this.
270 //So we need to call port mixer for every device to get the sources
271 for (int i = 0; i < nDevices; i++) {
272 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
273 if (info->maxOutputChannels > 0) {
274 AddSources(i, info->defaultSampleRate, &mOutputDeviceSourceMaps, 0);
275 }
276
277 if (info->maxInputChannels > 0) {
278 AddSources(i, info->defaultSampleRate, &mInputDeviceSourceMaps, 1);
279 }
280 }
281
282 // If this was not an initial scan update each device toolbar.
283 if ( m_inited )
285
286 m_inited = true;
287 mRescanTime = std::chrono::steady_clock::now();
288}
289
290
291std::chrono::duration<float> DeviceManager::GetTimeSinceRescan() {
292 auto now = std::chrono::steady_clock::now();
293 auto dur = std::chrono::duration_cast<std::chrono::duration<float>>(now - mRescanTime);
294 return dur;
295}
296
297
298//private constructor - Singleton.
300#if defined(EXPERIMENTAL_DEVICE_CHANGE_HANDLER)
301#if defined(HAVE_DEVICE_CHANGE)
302: DeviceChangeHandler()
303#endif
304#endif
305{
306 m_inited = false;
307 mRescanTime = std::chrono::steady_clock::now();
308}
309
311{
312
313}
314
316{
317 Rescan();
318
319#if defined(EXPERIMENTAL_DEVICE_CHANGE_HANDLER)
320#if defined(HAVE_DEVICE_CHANGE)
321 DeviceChangeHandler::Enable(true);
322#endif
323#endif
324}
325
326#if defined(EXPERIMENTAL_DEVICE_CHANGE_HANDLER)
327#if defined(HAVE_DEVICE_CHANGE)
328void DeviceManager::DeviceChangeNotification()
329{
330 Rescan();
331 return;
332}
333#endif
334#endif
wxT("CloseDown"))
int PaError
Definition: AudioIO.h:45
unsigned long PaStreamCallbackFlags
Definition: AudioIO.h:43
void PaStream
Definition: AudioIOBase.h:23
static bool IsInputDeviceAMapperDevice(const PaDeviceInfo *info)
static void AddSourcesFromStream(int deviceIndex, const PaDeviceInfo *info, std::vector< DeviceSourceMap > *maps, PaStream *stream)
static void AddSources(int deviceIndex, int rate, std::vector< DeviceSourceMap > *maps, int isInput)
wxString MakeDeviceSourceString(const DeviceSourceMap *map)
static void FillHostDeviceInfo(DeviceSourceMap *map, const PaDeviceInfo *info, int deviceIndex, int isInput)
static int DummyPaStreamCallback(const void *WXUNUSED(input), void *WXUNUSED(output), unsigned long WXUNUSED(frameCount), const PaStreamCallbackTimeInfo *WXUNUSED(timeInfo), PaStreamCallbackFlags WXUNUSED(statusFlags), void *WXUNUSED(userData))
static AudioIOBase * Get()
Definition: AudioIOBase.cpp:94
A singleton that manages the audio devices known to Audacity.
Definition: DeviceManager.h:48
DeviceSourceMap * GetDefaultInputDevice(int hostIndex)
const std::vector< DeviceSourceMap > & GetInputDeviceMaps()
const std::vector< DeviceSourceMap > & GetOutputDeviceMaps()
static DeviceManager * Instance()
Gets the singleton instance.
static DeviceManager dm
Definition: DeviceManager.h:91
std::chrono::duration< float > GetTimeSinceRescan()
DeviceSourceMap * GetDefaultDevice(int hostIndex, int isInput)
std::chrono::time_point< std::chrono::steady_clock > mRescanTime
Definition: DeviceManager.h:74
std::vector< DeviceSourceMap > mOutputDeviceSourceMaps
Definition: DeviceManager.h:89
std::vector< DeviceSourceMap > mInputDeviceSourceMaps
Definition: DeviceManager.h:88
DeviceSourceMap * GetDefaultOutputDevice(int hostIndex)
CallbackReturn Publish(const Message &message)
Send a message to connected callbacks.
Definition: Observer.h:207
wxString hostString
Definition: DeviceManager.h:36
wxString sourceString
Definition: DeviceManager.h:34
wxString deviceString
Definition: DeviceManager.h:35