2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "cores/AudioEngine/Sinks/AESinkDARWINIOS.h"
22 #include "cores/AudioEngine/Utils/AEUtil.h"
23 #include "cores/AudioEngine/Utils/AERingBuffer.h"
24 #include "cores/AudioEngine/Sinks/osx/CoreAudioHelpers.h"
25 #include "osx/DarwinUtils.h"
26 #include "utils/log.h"
27 #include "utils/StringUtils.h"
28 #include "threads/Condition.h"
29 #include "windowing/WindowingFactory.h"
32 #include <AudioToolbox/AudioToolbox.h>
34 #define CA_MAX_CHANNELS 8
35 static enum AEChannel CAChannelMap[CA_MAX_CHANNELS + 1] = {
36 AE_CH_FL , AE_CH_FR , AE_CH_BL , AE_CH_BR , AE_CH_FC , AE_CH_LFE , AE_CH_SL , AE_CH_SR ,
40 /***************************************************************************************/
41 /***************************************************************************************/
42 #if DO_440HZ_TONE_TEST
43 static void SineWaveGeneratorInitWithFrequency(SineWaveGenerator *ctx, double frequency, double samplerate)
46 // frequency in cycles per second
47 // 2*PI radians per sine wave cycle
48 // sample rate in samples per second
51 // cycles radians seconds radians
52 // ------ * ------- * ------- = -------
53 // second cycle sample sample
54 ctx->currentPhase = 0.0;
55 ctx->phaseIncrement = frequency * 2*M_PI / samplerate;
58 static int16_t SineWaveGeneratorNextSampleInt16(SineWaveGenerator *ctx)
60 int16_t sample = INT16_MAX * sinf(ctx->currentPhase);
62 ctx->currentPhase += ctx->phaseIncrement;
63 // Keep the value between 0 and 2*M_PI
64 while (ctx->currentPhase > 2*M_PI)
65 ctx->currentPhase -= 2*M_PI;
69 static float SineWaveGeneratorNextSampleFloat(SineWaveGenerator *ctx)
71 float sample = MAXFLOAT * sinf(ctx->currentPhase);
73 ctx->currentPhase += ctx->phaseIncrement;
74 // Keep the value between 0 and 2*M_PI
75 while (ctx->currentPhase > 2*M_PI)
76 ctx->currentPhase -= 2*M_PI;
82 /***************************************************************************************/
83 /***************************************************************************************/
90 bool open(AudioStreamBasicDescription outputFormat);
98 unsigned int write(uint8_t *data, unsigned int byte_count);
99 unsigned int chunkSize() { return m_bufferDuration * m_sampleRate; }
100 unsigned int getRealisedSampleRate() { return m_outputFormat.mSampleRate; }
101 static Float64 getCoreAudioRealisedSampleRate();
104 void setCoreAudioBuffersize();
105 bool setCoreAudioInputFormat();
106 void setCoreAudioPreferredSampleRate();
108 bool checkAudioRoute();
109 bool checkSessionProperties();
110 bool activateAudioSession();
111 void deactivateAudioSession();
114 static void sessionPropertyCallback(void *inClientData,
115 AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData);
117 static void sessionInterruptionCallback(void *inClientData, UInt32 inInterruption);
119 static OSStatus renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
120 const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames,
121 AudioBufferList *ioData);
126 AudioUnit m_audioUnit;
127 AudioStreamBasicDescription m_outputFormat;
128 AERingBuffer *m_buffer;
131 Float32 m_outputVolume;
132 Float32 m_outputLatency;
133 Float32 m_bufferDuration;
135 unsigned int m_sampleRate;
136 unsigned int m_frameSize;
137 unsigned int m_frames;
140 bool m_playing_saved;
141 volatile bool m_started;
144 CAAudioUnitSink::CAAudioUnitSink()
145 : m_initialized(false)
149 , m_playing_saved(false)
154 CAAudioUnitSink::~CAAudioUnitSink()
159 bool CAAudioUnitSink::open(AudioStreamBasicDescription outputFormat)
163 m_outputFormat = outputFormat;
164 m_outputLatency = 0.0;
165 m_bufferDuration= 0.0;
166 m_outputVolume = 1.0;
167 m_sampleRate = (unsigned int)outputFormat.mSampleRate;
168 m_frameSize = outputFormat.mChannelsPerFrame * outputFormat.mBitsPerChannel / 8;
170 /* TODO: Reduce the size of this buffer, pre-calculate the size based on how large
171 the buffers are that CA calls us with in the renderCallback - perhaps call
172 the checkSessionProperties() before running this? */
173 m_buffer = new AERingBuffer(16384);
178 bool CAAudioUnitSink::close()
180 deactivateAudioSession();
189 bool CAAudioUnitSink::play(bool mute)
193 if (activateAudioSession())
195 CAAudioUnitSink::mute(mute);
196 m_playing = !AudioOutputUnitStart(m_audioUnit);
203 bool CAAudioUnitSink::mute(bool mute)
210 bool CAAudioUnitSink::pause()
213 m_playing = AudioOutputUnitStop(m_audioUnit);
218 double CAAudioUnitSink::getDelay()
220 double delay = (double)m_buffer->GetReadSize() / m_frameSize;
221 delay /= m_sampleRate;
222 delay += m_bufferDuration + m_outputLatency;
227 double CAAudioUnitSink::cacheSize()
229 return (double)m_buffer->GetMaxSize() / (double)(m_frameSize * m_sampleRate);
232 CCriticalSection mutex;
233 XbmcThreads::ConditionVariable condVar;
235 unsigned int CAAudioUnitSink::write(uint8_t *data, unsigned int frames)
237 if (m_buffer->GetWriteSize() < frames * m_frameSize)
238 { // no space to write - wait for a bit
239 CSingleLock lock(mutex);
240 unsigned int timeout = 900 * frames / m_sampleRate;
244 // we are using a timer here for beeing sure for timeouts
245 // condvar can be woken spuriously as signaled
246 XbmcThreads::EndTime timer(timeout);
247 condVar.wait(lock, timeout);
248 if (!m_started && timer.IsTimePast())
252 unsigned int write_frames = std::min(frames, m_buffer->GetWriteSize() / m_frameSize);
254 m_buffer->Write(data, write_frames * m_frameSize);
259 void CAAudioUnitSink::drain()
261 unsigned int bytes = m_buffer->GetReadSize();
264 CSingleLock lock(mutex);
265 condVar.wait(mutex, 900 * bytes / (m_sampleRate * m_frameSize));
266 bytes = m_buffer->GetReadSize();
270 void CAAudioUnitSink::setCoreAudioBuffersize()
272 #if !TARGET_IPHONE_SIMULATOR
273 OSStatus status = noErr;
274 // set the buffer size, this affects the number of samples
275 // that get rendered every time the audio callback is fired.
276 Float32 preferredBufferSize = 512 * m_outputFormat.mChannelsPerFrame / m_outputFormat.mSampleRate;
277 CLog::Log(LOGNOTICE, "%s setting buffer duration to %f", __PRETTY_FUNCTION__, preferredBufferSize);
278 status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareIOBufferDuration,
279 sizeof(preferredBufferSize), &preferredBufferSize);
281 CLog::Log(LOGWARNING, "%s preferredBufferSize couldn't be set (error: %d)", __PRETTY_FUNCTION__, (int)status);
285 bool CAAudioUnitSink::setCoreAudioInputFormat()
287 // Set the output stream format
288 UInt32 ioDataSize = sizeof(AudioStreamBasicDescription);
289 OSStatus status = AudioUnitSetProperty(m_audioUnit, kAudioUnitProperty_StreamFormat,
290 kAudioUnitScope_Input, 0, &m_outputFormat, ioDataSize);
293 CLog::Log(LOGERROR, "%s error setting stream format on audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
299 void CAAudioUnitSink::setCoreAudioPreferredSampleRate()
301 Float64 preferredSampleRate = m_outputFormat.mSampleRate;
302 CLog::Log(LOGNOTICE, "%s requesting hw samplerate %f", __PRETTY_FUNCTION__, preferredSampleRate);
303 OSStatus status = AudioSessionSetProperty(kAudioSessionProperty_PreferredHardwareSampleRate,
304 sizeof(preferredSampleRate), &preferredSampleRate);
306 CLog::Log(LOGWARNING, "%s preferredSampleRate couldn't be set (error: %d)", __PRETTY_FUNCTION__, (int)status);
309 Float64 CAAudioUnitSink::getCoreAudioRealisedSampleRate()
311 Float64 outputSampleRate = 0.0;
312 UInt32 ioDataSize = sizeof(outputSampleRate);
313 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareSampleRate,
314 &ioDataSize, &outputSampleRate) != noErr)
315 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareSampleRate", __FUNCTION__);
316 return outputSampleRate;
319 bool CAAudioUnitSink::setupAudio()
321 OSStatus status = noErr;
322 if (m_setup && m_audioUnit)
325 // Audio Session Setup
326 UInt32 sessionCategory = kAudioSessionCategory_MediaPlayback;
327 status = AudioSessionSetProperty(kAudioSessionProperty_AudioCategory,
328 sizeof(sessionCategory), &sessionCategory);
331 CLog::Log(LOGERROR, "%s error setting sessioncategory (error: %d)", __PRETTY_FUNCTION__, (int)status);
335 AudioSessionAddPropertyListener(kAudioSessionProperty_AudioRouteChange,
336 sessionPropertyCallback, this);
338 AudioSessionAddPropertyListener(kAudioSessionProperty_CurrentHardwareOutputVolume,
339 sessionPropertyCallback, this);
341 if (AudioSessionSetActive(true) != noErr)
345 // Describe a default output unit.
346 AudioComponentDescription description = {};
347 description.componentType = kAudioUnitType_Output;
348 description.componentSubType = kAudioUnitSubType_RemoteIO;
349 description.componentManufacturer = kAudioUnitManufacturer_Apple;
352 AudioComponent component;
353 component = AudioComponentFindNext(NULL, &description);
354 status = AudioComponentInstanceNew(component, &m_audioUnit);
357 CLog::Log(LOGERROR, "%s error creating audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
361 setCoreAudioPreferredSampleRate();
363 // Get the output samplerate for knowing what was setup in reality
364 Float64 realisedSampleRate = getCoreAudioRealisedSampleRate();
365 if (m_outputFormat.mSampleRate != realisedSampleRate)
367 CLog::Log(LOGNOTICE, "%s couldn't set requested samplerate %d, coreaudio will resample to %d instead", __PRETTY_FUNCTION__, (int)m_outputFormat.mSampleRate, (int)realisedSampleRate);
368 // if we don't ca to resample - but instead let activeae resample -
369 // reflect the realised samplerate to the outputformat here
370 // well maybe it is handy in the future - as of writing this
371 // ca was about 6 times faster then activeae ;)
372 //m_outputFormat.mSampleRate = realisedSampleRate;
373 //m_sampleRate = realisedSampleRate;
376 setCoreAudioBuffersize();
377 if (!setCoreAudioInputFormat())
380 // Attach a render callback on the unit
381 AURenderCallbackStruct callbackStruct = {};
382 callbackStruct.inputProc = renderCallback;
383 callbackStruct.inputProcRefCon = this;
384 status = AudioUnitSetProperty(m_audioUnit,
385 kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input,
386 0, &callbackStruct, sizeof(callbackStruct));
389 CLog::Log(LOGERROR, "%s error setting render callback for audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
393 status = AudioUnitInitialize(m_audioUnit);
396 CLog::Log(LOGERROR, "%s error initializing audioUnit (error: %d)", __PRETTY_FUNCTION__, (int)status);
400 checkSessionProperties();
403 std::string formatString;
404 CLog::Log(LOGNOTICE, "%s setup audio format: %s", __PRETTY_FUNCTION__, StreamDescriptionToString(m_outputFormat, formatString));
409 bool CAAudioUnitSink::checkAudioRoute()
411 // why do we need to know the audio route ?
413 UInt32 propertySize = sizeof(CFStringRef);
414 if (AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &route) != noErr)
420 bool CAAudioUnitSink::checkSessionProperties()
425 ioDataSize = sizeof(m_outputVolume);
426 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputVolume,
427 &ioDataSize, &m_outputVolume) != noErr)
428 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareOutputVolume", __FUNCTION__);
430 ioDataSize = sizeof(m_outputLatency);
431 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareOutputLatency,
432 &ioDataSize, &m_outputLatency) != noErr)
433 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareOutputLatency", __FUNCTION__);
435 ioDataSize = sizeof(m_bufferDuration);
436 if (AudioSessionGetProperty(kAudioSessionProperty_CurrentHardwareIOBufferDuration,
437 &ioDataSize, &m_bufferDuration) != noErr)
438 CLog::Log(LOGERROR, "%s: error getting CurrentHardwareIOBufferDuration", __FUNCTION__);
440 CLog::Log(LOGDEBUG, "%s: volume = %f, latency = %f, buffer = %f", __FUNCTION__, m_outputVolume, m_outputLatency, m_bufferDuration);
444 bool CAAudioUnitSink::activateAudioSession()
450 OSStatus osstat = AudioSessionInitialize(NULL, kCFRunLoopDefaultMode, sessionInterruptionCallback, this);
451 if (osstat == kAudioSessionNoError || osstat == kAudioSessionAlreadyInitialized)
452 m_initialized = true;
455 CLog::Log(LOGERROR, "%s error initializing audio session (error: %d)", __PRETTY_FUNCTION__, (int)osstat);
459 if (checkAudioRoute() && setupAudio())
466 void CAAudioUnitSink::deactivateAudioSession()
471 AudioUnitUninitialize(m_audioUnit);
472 AudioComponentInstanceDispose(m_audioUnit), m_audioUnit = NULL;
473 AudioSessionSetActive(false);
474 AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_AudioRouteChange,
475 sessionPropertyCallback, this);
476 AudioSessionRemovePropertyListenerWithUserData(kAudioSessionProperty_CurrentHardwareOutputVolume,
477 sessionPropertyCallback, this);
484 void CAAudioUnitSink::sessionPropertyCallback(void *inClientData,
485 AudioSessionPropertyID inID, UInt32 inDataSize, const void *inData)
487 CAAudioUnitSink *sink = (CAAudioUnitSink*)inClientData;
489 if (inID == kAudioSessionProperty_AudioRouteChange)
491 if (sink->checkAudioRoute())
492 sink->checkSessionProperties();
494 else if (inID == kAudioSessionProperty_CurrentHardwareOutputVolume)
496 if (inData && inDataSize == 4)
497 sink->m_outputVolume = *(float*)inData;
501 void CAAudioUnitSink::sessionInterruptionCallback(void *inClientData, UInt32 inInterruption)
503 CAAudioUnitSink *sink = (CAAudioUnitSink*)inClientData;
505 if (inInterruption == kAudioSessionBeginInterruption)
507 CLog::Log(LOGDEBUG, "Bgn interuption");
508 sink->m_playing_saved = sink->m_playing;
511 else if (inInterruption == kAudioSessionEndInterruption)
513 CLog::Log(LOGDEBUG, "End interuption");
514 if (sink->m_playing_saved)
516 sink->m_playing_saved = false;
517 sink->play(sink->m_mute);
522 OSStatus CAAudioUnitSink::renderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags,
523 const AudioTimeStamp *inTimeStamp, UInt32 inOutputBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData)
525 CAAudioUnitSink *sink = (CAAudioUnitSink*)inRefCon;
527 sink->m_started = true;
529 for (unsigned int i = 0; i < ioData->mNumberBuffers; i++)
531 // buffers come from CA already zero'd, so just copy what is wanted
532 unsigned int wanted = ioData->mBuffers[i].mDataByteSize;
533 unsigned int bytes = std::min(sink->m_buffer->GetReadSize(), wanted);
534 sink->m_buffer->Read((unsigned char*)ioData->mBuffers[i].mData, bytes);
536 CLog::Log(LOGERROR, "%s: %sFLOW (%i vs %i) bytes", __FUNCTION__, bytes > wanted ? "OVER" : "UNDER", bytes, wanted);
538 *ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence;
540 // tell the sink we're good for more data
546 /***************************************************************************************/
547 /***************************************************************************************/
548 static void EnumerateDevices(AEDeviceInfoList &list)
550 CAEDeviceInfo device;
552 device.m_deviceName = "default";
553 device.m_displayName = "Default";
554 device.m_displayNameExtra = "";
555 #if defined(TARGET_DARWIN_IOS_ATV2)
556 device.m_deviceType = AE_DEVTYPE_IEC958;
557 device.m_dataFormats.push_back(AE_FMT_AC3);
558 device.m_dataFormats.push_back(AE_FMT_DTS);
560 // TODO screen changing on ios needs to call
561 // devices changed once this is available in activae
562 if (g_Windowing.GetCurrentScreen() > 0)
564 device.m_deviceType = AE_DEVTYPE_IEC958; //allow passthrough for tvout
565 device.m_dataFormats.push_back(AE_FMT_AC3);
566 device.m_dataFormats.push_back(AE_FMT_DTS);
569 device.m_deviceType = AE_DEVTYPE_PCM;
573 CAEChannelInfo channel_info;
574 for (UInt32 chan = 0; chan < 2; ++chan)
576 if (!device.m_channels.HasChannel(CAChannelMap[chan]))
577 device.m_channels += CAChannelMap[chan];
578 channel_info += CAChannelMap[chan];
581 // there are more supported ( one of those 2 gets resampled
582 // by coreaudio anyway) - but for keeping it save ignore
584 device.m_sampleRates.push_back(44100);
585 device.m_sampleRates.push_back(48000);
587 device.m_dataFormats.push_back(AE_FMT_S16LE);
588 //device.m_dataFormats.push_back(AE_FMT_S24LE3);
589 //device.m_dataFormats.push_back(AE_FMT_S32LE);
590 // AE_FMT_FLOAT is 3% slower on atv2
591 // then S16LE - so leave it out for now
592 //device.m_dataFormats.push_back(AE_FMT_FLOAT);
594 CLog::Log(LOGDEBUG, "EnumerateDevices:Device(%s)" , device.m_deviceName.c_str());
596 list.push_back(device);
599 /***************************************************************************************/
600 /***************************************************************************************/
601 AEDeviceInfoList CAESinkDARWINIOS::m_devices;
603 CAESinkDARWINIOS::CAESinkDARWINIOS()
608 CAESinkDARWINIOS::~CAESinkDARWINIOS()
612 bool CAESinkDARWINIOS::Initialize(AEAudioFormat &format, std::string &device)
615 bool forceRaw = false;
617 std::string devicelower = device;
618 StringUtils::ToLower(devicelower);
619 for (size_t i = 0; i < m_devices.size(); i++)
621 if (devicelower.find(m_devices[i].m_deviceName) != std::string::npos)
623 m_info = m_devices[i];
632 AudioStreamBasicDescription audioFormat = {};
634 // AE_FMT_FLOAT is 3% slower on atv2
635 // then S16LE - so leave it out for now
636 // just leave the code commented in here
637 // as it might come handy at some point maybe ...
638 //if (format.m_dataFormat == AE_FMT_FLOAT)
639 // audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsFloat;
640 //else// this will be selected when AE wants AC3 or DTS or anything other then float
642 audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsSignedInteger;
643 if (AE_IS_RAW(format.m_dataFormat))
645 format.m_dataFormat = AE_FMT_S16LE;
648 format.m_channelLayout = m_info.m_channels;
649 format.m_frameSize = format.m_channelLayout.Count() * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3);
652 audioFormat.mFormatID = kAudioFormatLinearPCM;
653 switch(format.m_sampleRate)
660 audioFormat.mSampleRate = 44100;
672 audioFormat.mSampleRate = 48000;
676 if (forceRaw)//make sure input and output samplerate match for preventing resampling
677 audioFormat.mSampleRate = CAAudioUnitSink::getCoreAudioRealisedSampleRate();
679 audioFormat.mFramesPerPacket = 1;
680 audioFormat.mChannelsPerFrame= 2;// ios only supports 2 channels
681 audioFormat.mBitsPerChannel = CAEUtil::DataFormatToBits(format.m_dataFormat);
682 audioFormat.mBytesPerFrame = format.m_frameSize;
683 audioFormat.mBytesPerPacket = audioFormat.mBytesPerFrame * audioFormat.mFramesPerPacket;
684 audioFormat.mFormatFlags |= kLinearPCMFormatFlagIsPacked;
686 #if DO_440HZ_TONE_TEST
687 SineWaveGeneratorInitWithFrequency(&m_SineWaveGenerator, 440.0, audioFormat.mSampleRate);
690 m_audioSink = new CAAudioUnitSink;
691 m_audioSink->open(audioFormat);
693 format.m_frames = m_audioSink->chunkSize();
694 format.m_frameSamples = format.m_frames * audioFormat.mChannelsPerFrame;
695 // reset to the realised samplerate
696 format.m_sampleRate = m_audioSink->getRealisedSampleRate();
699 m_volume_changed = false;
700 m_audioSink->play(false);
705 void CAESinkDARWINIOS::Deinitialize()
711 bool CAESinkDARWINIOS::IsCompatible(const AEAudioFormat &format, const std::string &device)
713 return ((m_format.m_sampleRate == format.m_sampleRate) &&
714 (m_format.m_dataFormat == format.m_dataFormat) &&
715 (m_format.m_channelLayout == format.m_channelLayout));
718 double CAESinkDARWINIOS::GetDelay()
721 return m_audioSink->getDelay();
725 double CAESinkDARWINIOS::GetCacheTotal()
728 return m_audioSink->cacheSize();
732 unsigned int CAESinkDARWINIOS::AddPackets(uint8_t *data, unsigned int frames, bool hasAudio, bool blocking)
735 #if DO_440HZ_TONE_TEST
736 if (m_format.m_dataFormat == AE_FMT_FLOAT)
738 float *samples = (float*)data;
739 for (unsigned int j = 0; j < frames ; j++)
741 float sample = SineWaveGeneratorNextSampleFloat(&m_SineWaveGenerator);
749 int16_t *samples = (int16_t*)data;
750 for (unsigned int j = 0; j < frames ; j++)
752 int16_t sample = SineWaveGeneratorNextSampleInt16(&m_SineWaveGenerator);
759 return m_audioSink->write(data, frames);
763 void CAESinkDARWINIOS::Drain()
766 m_audioSink->drain();
769 bool CAESinkDARWINIOS::HasVolume()
774 void CAESinkDARWINIOS::SetVolume(float scale)
776 // CoreAudio uses fixed steps, reverse scale back to percent
777 float gain = CAEUtil::ScaleToGain(scale);
778 m_volume = CAEUtil::GainToPercent(gain);
779 m_volume_changed = true;
782 void CAESinkDARWINIOS::EnumerateDevicesEx(AEDeviceInfoList &list, bool force)
785 EnumerateDevices(m_devices);