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