MicrophoneSystemComponent_Mac.mm 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #import <Foundation/Foundation.h>
  9. #import <AudioToolbox/AudioToolbox.h>
  10. #include "MicrophoneSystemComponent.h"
  11. #include "SimpleDownsample.h"
  12. #include <AzCore/std/smart_ptr/unique_ptr.h>
  13. #include <AzCore/EBus/EBus.h>
  14. #include <AudioRingBuffer.h>
  15. #include <IAudioInterfacesCommonData.h>
  16. #if defined(USE_LIBSAMPLERATE)
  17. #include <samplerate.h>
  18. #endif // USE_LIBSAMPLERATE
  19. namespace Audio
  20. {
  21. class MicrophoneSystemComponentMac : public MicrophoneSystemComponent::Implementation
  22. {
  23. public:
  24. bool InitializeDevice() override
  25. {
  26. m_isCapturing = false;
  27. OSStatus status;
  28. AudioComponentDescription desc;
  29. desc.componentType = kAudioUnitType_Output;
  30. desc.componentSubType = kAudioUnitSubType_HALOutput;
  31. desc.componentFlags = 0;
  32. desc.componentFlagsMask = 0;
  33. desc.componentManufacturer = kAudioUnitManufacturer_Apple;
  34. AudioComponent inputComponent = AudioComponentFindNext(nullptr, &desc);
  35. status = AudioComponentInstanceNew(inputComponent, &m_audioUnit);
  36. if(status != 0)
  37. {
  38. AZ_Error("MacOSMicrophone", false, "Error creating audio component: %d", status);
  39. return false;
  40. }
  41. // enable input
  42. AZ::u32 flag = 1;
  43. status = AudioUnitSetProperty(m_audioUnit,
  44. kAudioOutputUnitProperty_EnableIO,
  45. kAudioUnitScope_Input,
  46. kInputBus,
  47. &flag,
  48. sizeof(flag));
  49. if(status != 0)
  50. {
  51. AZ_Error("MacOSMicrophone", false, "Error enabling audio input IO: %d", status);
  52. return false;
  53. }
  54. // disable output
  55. flag = 0;
  56. status = AudioUnitSetProperty(m_audioUnit,
  57. kAudioOutputUnitProperty_EnableIO,
  58. kAudioUnitScope_Output,
  59. kOutputBus,
  60. &flag,
  61. sizeof(flag));
  62. if(status != 0)
  63. {
  64. AZ_Error("MacOSMicrophone", false, "Error enabling audio input IO: %d", status);
  65. return false;
  66. }
  67. // set current input device to default
  68. AudioDeviceID inputDevice;
  69. AZ::u32 size = sizeof(AudioDeviceID);
  70. AudioObjectPropertyAddress theAddress = {
  71. kAudioHardwarePropertyDefaultInputDevice,
  72. kAudioObjectPropertyScopeGlobal,
  73. #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 120000 // Needs to be 120000 instead of __MAC_12_0 because that will not be defined in earlier versions on the SDK.
  74. kAudioObjectPropertyElementMain
  75. #else
  76. kAudioObjectPropertyElementMaster
  77. #endif
  78. };
  79. status = AudioObjectGetPropertyData(kAudioObjectSystemObject,
  80. &theAddress,
  81. 0,
  82. nullptr,
  83. &size,
  84. &inputDevice);
  85. if(status != 0)
  86. {
  87. AZ_Error("MacOSMicrophone", false, "Error getting default microphone: %d", status);
  88. return false;
  89. }
  90. status = AudioUnitSetProperty(m_audioUnit,
  91. kAudioOutputUnitProperty_CurrentDevice,
  92. kAudioUnitScope_Global,
  93. 0,
  94. &inputDevice,
  95. sizeof(inputDevice)
  96. );
  97. if(status != 0)
  98. {
  99. AZ_Error("MacOSMicrophone", false, "Error setting default microphone: %d", status);
  100. return false;
  101. }
  102. // grab the native input format
  103. AudioStreamBasicDescription deviceFormat;
  104. size = sizeof(AudioStreamBasicDescription);
  105. status = AudioUnitGetProperty(m_audioUnit,
  106. kAudioUnitProperty_StreamFormat,
  107. kAudioUnitScope_Input,
  108. kInputBus,
  109. &deviceFormat,
  110. &size);
  111. if(status != 0)
  112. {
  113. AZ_Error("MacOSMicrophone", false, "Error getting microphone input format: %d", status);
  114. return false;
  115. }
  116. m_config.m_sampleRate = deviceFormat.mSampleRate;
  117. m_config.m_numChannels = 1;
  118. m_config.m_bitsPerSample = 16;
  119. m_config.m_sampleType = AudioInputSampleType::Int;
  120. m_config.SetBufferSizeFromFrameCount(8192);
  121. AudioStreamBasicDescription desiredFormat;
  122. desiredFormat.mSampleRate = deviceFormat.mSampleRate;
  123. desiredFormat.mBitsPerChannel = m_config.m_bitsPerSample;
  124. desiredFormat.mChannelsPerFrame = m_config.m_numChannels;
  125. desiredFormat.mBytesPerFrame = 2;
  126. desiredFormat.mBytesPerPacket = 2;
  127. desiredFormat.mFramesPerPacket = 1;
  128. desiredFormat.mFormatID = kAudioFormatLinearPCM;
  129. desiredFormat.mFormatFlags = 0;
  130. status = AudioUnitSetProperty(m_audioUnit,
  131. kAudioUnitProperty_StreamFormat,
  132. kAudioUnitScope_Output,
  133. kInputBus,
  134. &desiredFormat,
  135. sizeof(AudioStreamBasicDescription));
  136. if(status != 0)
  137. {
  138. AZ_Error("MacOSMicrophone", false, "Error setting desired output format: %d", status);
  139. return false;
  140. }
  141. AURenderCallbackStruct callbackStruct;
  142. callbackStruct.inputProc = recordingCallback;
  143. callbackStruct.inputProcRefCon = this;
  144. status = AudioUnitSetProperty(m_audioUnit,
  145. kAudioOutputUnitProperty_SetInputCallback,
  146. kAudioUnitScope_Global,
  147. kInputBus,
  148. &callbackStruct,
  149. sizeof(callbackStruct));
  150. if(status != 0)
  151. {
  152. AZ_Error("MacOSMicrophone", false, "Error setting microphone callback: %d", status);
  153. return false;
  154. }
  155. // This disables buffer allocation as we provide our own
  156. flag = 0;
  157. status = AudioUnitSetProperty(m_audioUnit,
  158. kAudioUnitProperty_ShouldAllocateBuffer,
  159. kAudioUnitScope_Output,
  160. kInputBus,
  161. &flag,
  162. sizeof(flag));
  163. // Initialise
  164. status = AudioUnitInitialize(m_audioUnit);
  165. if(status != 0)
  166. {
  167. AZ_Error("MacOSMicrophone", false, "Error initializing microphone: %d", status);
  168. return false;
  169. }
  170. return true;
  171. }
  172. void ShutdownDevice() override
  173. {
  174. m_isCapturing = false;
  175. AudioUnitUninitialize(m_audioUnit);
  176. }
  177. bool StartSession() override
  178. {
  179. m_captureData.reset(aznew Audio::RingBuffer<AZ::s16>(m_config.GetSampleCountFromBufferSize()));
  180. OSStatus status = AudioOutputUnitStart(m_audioUnit);
  181. if(status != 0)
  182. {
  183. AZ_Error("MacOSMicrophone", false, "Error starting microphone: %d", status);
  184. return false;
  185. }
  186. m_isCapturing = true;
  187. return true;
  188. }
  189. void EndSession() override
  190. {
  191. OSStatus status = AudioOutputUnitStop(m_audioUnit);
  192. if(status != 0)
  193. {
  194. AZ_Error("MacODMicrophone", false, "Error stopping microphone: %d", status);
  195. }
  196. m_isCapturing = false;
  197. if(m_captureData)
  198. {
  199. m_captureData.reset();
  200. }
  201. }
  202. bool IsCapturing() override
  203. {
  204. return m_isCapturing;
  205. }
  206. SAudioInputConfig GetFormatConfig() const override
  207. {
  208. return m_config;
  209. }
  210. AZStd::size_t GetData(void** outputData, AZStd::size_t numFrames, const SAudioInputConfig& targetConfig, bool shouldDeinterleave) override
  211. {
  212. #if defined(USE_LIBSAMPLERATE)
  213. // pending port of LIBSAMPLERATE to MacOS
  214. return {};
  215. #else
  216. bool changeSampleType = (targetConfig.m_sampleType != m_config.m_sampleType);
  217. bool changeSampleRate = (targetConfig.m_sampleRate != m_config.m_sampleRate);
  218. bool changeNumChannels = (targetConfig.m_numChannels != m_config.m_numChannels);
  219. if (changeSampleType || changeNumChannels)
  220. {
  221. // Without the SRC library, any change is unsupported!
  222. return {};
  223. }
  224. else if (changeSampleRate)
  225. {
  226. if(targetConfig.m_sampleRate > m_config.m_sampleRate)
  227. {
  228. AZ_Error("MacOSMicrophone", false, "Target sample rate is larger than source sample rate, this is not supported");
  229. return {};
  230. }
  231. auto sourceBuffer = new AZ::s16[numFrames];
  232. AZStd::size_t targetSize = GetDownsampleSize(numFrames, m_config.m_sampleRate, targetConfig.m_sampleRate);
  233. auto targetBuffer = new AZ::s16[targetSize];
  234. numFrames = m_captureData->ConsumeData(reinterpret_cast<void**>(&sourceBuffer), numFrames, m_config.m_numChannels, false);
  235. if(numFrames > 0)
  236. {
  237. Downsample(sourceBuffer, numFrames, m_config.m_sampleRate, targetBuffer, targetSize, targetConfig.m_sampleRate);
  238. // swap target data to output
  239. ::memcpy(*outputData, targetBuffer, numFrames * targetConfig.m_numChannels * (targetConfig.m_bitsPerSample >> 3));
  240. }
  241. delete [] sourceBuffer;
  242. delete [] targetBuffer;
  243. return numFrames;
  244. }
  245. else
  246. {
  247. // No change to the data from Input to Output
  248. return m_captureData->ConsumeData(outputData, numFrames, m_config.m_numChannels, shouldDeinterleave);
  249. }
  250. #endif
  251. return {};
  252. }
  253. void ProcessAudio(AudioBufferList* bufferList)
  254. {
  255. AudioBuffer sourceBuffer = bufferList->mBuffers[0];
  256. m_captureData->AddData((AZ::s16*)sourceBuffer.mData, sourceBuffer.mDataByteSize / 2, m_config.m_numChannels);
  257. }
  258. AudioComponentInstance m_audioUnit;
  259. private:
  260. static OSStatus recordingCallback(void* inRefCon,
  261. AudioUnitRenderActionFlags* ioActionFlags,
  262. const AudioTimeStamp* inTimeStamp,
  263. AZ::u32 inBusNumber,
  264. AZ::u32 inNumberFrames,
  265. AudioBufferList* ioData) {
  266. auto impl = reinterpret_cast<MicrophoneSystemComponentMac*>(inRefCon);
  267. // Samples are 16 bits = 2 bytes.
  268. // 1 frame includes only 1 sample
  269. // one channel as mono was selected in setup
  270. AudioBuffer buffer;
  271. buffer.mNumberChannels = 1;
  272. buffer.mDataByteSize = inNumberFrames * 2;
  273. buffer.mData = new AZ::u8[buffer.mDataByteSize];
  274. // Put buffer in a AudioBufferList
  275. AudioBufferList bufferList;
  276. bufferList.mNumberBuffers = 1;
  277. bufferList.mBuffers[0] = buffer;
  278. // Obtain recorded samples
  279. AudioUnitRender(impl->m_audioUnit,
  280. ioActionFlags,
  281. inTimeStamp,
  282. inBusNumber,
  283. inNumberFrames,
  284. &bufferList);
  285. impl->ProcessAudio(&bufferList);
  286. // release the malloc'ed data in the buffer we created earlier
  287. delete [] (AZ::u8*)bufferList.mBuffers[0].mData;
  288. return noErr;
  289. }
  290. Audio::SAudioInputConfig m_config;
  291. bool m_isCapturing = false;
  292. AZStd::unique_ptr<Audio::RingBufferBase> m_captureData = nullptr;
  293. const AudioUnitElement kOutputBus = 0;
  294. const AudioUnitElement kInputBus = 1;
  295. };
  296. ///////////////////////////////////////////////////////////////////////////////////////////////
  297. MicrophoneSystemComponent::Implementation* MicrophoneSystemComponent::Implementation::Create()
  298. {
  299. return aznew MicrophoneSystemComponentMac();
  300. }
  301. }