// Microphone path used by voice chat (Vivox/Chromium): AVFoundation audio authorization, the // input device's channels, and an AUHAL input unit delivering recorded frames through // AudioUnitRender(bus 1). Samples are only counted, never stored or printed. #include #include #include #include #include #include #include #include #include #include extern "C" void* const AVMediaTypeAudio; extern "C" void* const AVMediaTypeVideo; static AudioUnit unit; static std::atomic callbacks{0}, renderErrors{0}, nonIncreasing{0}, zeroFrames{0}, badBuffers{0}; static std::atomic totalFrames{0}; static double lastSample=-1; static bool planar; static unsigned interleavedChannels=1; static float samples[2][2*4096]; static OSStatus input(void*,AudioUnitRenderActionFlags* flags,const AudioTimeStamp* time,UInt32 bus,UInt32 frames,AudioBufferList*) { ++callbacks; if(!frames || frames>4096) {++zeroFrames;return noErr;} if(!(time->mFlags&kAudioTimeStampSampleTimeValid) || time->mSampleTime<=lastSample) ++nonIncreasing; lastSample=time->mSampleTime; unsigned char storage[sizeof(AudioBufferList)+sizeof(AudioBuffer)]; auto* list=(AudioBufferList*)storage; list->mNumberBuffers=planar?2:1; for(unsigned b=0;bmNumberBuffers;++b) { list->mBuffers[b].mNumberChannels=planar?1:interleavedChannels; list->mBuffers[b].mDataByteSize=frames*sizeof(float)*list->mBuffers[b].mNumberChannels; list->mBuffers[b].mData=samples[b]; } AudioUnitRenderActionFlags renderFlags=0; OSStatus status=AudioUnitRender(unit,&renderFlags,time,bus,frames,list); if(status) ++renderErrors; else for(unsigned b=0;bmNumberBuffers;++b) if(list->mBuffers[b].mDataByteSize!=frames*sizeof(float)*list->mBuffers[b].mNumberChannels) ++badBuffers; totalFrames+=frames; return noErr; } static int authorization() { Class device=objc_getClass("AVCaptureDevice"); if(!device) {printf("AUDIO_INPUT_AUTH class=0\n");return 1;} SEL status=sel_registerName("authorizationStatusForMediaType:"); auto query=(long(*)(id,SEL,void*))objc_msgSend; long audio=query((id)device,status,AVMediaTypeAudio),video=query((id)device,status,AVMediaTypeVideo); // macOS calls the handler on another queue, never on the requesting thread inside the call. // Shared with the handler's thread, so atomic; read only after the handler signals. static std::atomic granted{-1}; static std::atomic synchronous{false}; const pthread_t caller=pthread_self(); dispatch_semaphore_t done=dispatch_semaphore_create(0); auto request=(void(*)(id,SEL,void*,void(^)(BOOL)))objc_msgSend; request((id)device,sel_registerName("requestAccessForMediaType:completionHandler:"),AVMediaTypeAudio,^(BOOL value) { synchronous=pthread_equal(pthread_self(),caller)!=0;granted=value;dispatch_semaphore_signal(done); }); long waited=dispatch_semaphore_wait(done,dispatch_time(DISPATCH_TIME_NOW,2*NSEC_PER_SEC)); printf("AUDIO_INPUT_AUTH audio=%ld video=%ld request_granted=%d async=%d\n",audio,video,(int)granted,!waited && !synchronous); fflush(stdout); return audio!=3 || video!=2 || waited || granted!=1 || synchronous; } static int device(AudioDeviceID* out) { AudioObjectPropertyAddress p={kAudioHardwarePropertyDefaultInputDevice,kAudioObjectPropertyScopeGlobal,kAudioObjectPropertyElementMaster}; AudioDeviceID id=kAudioObjectUnknown;UInt32 size=sizeof(id); OSStatus status=AudioObjectGetPropertyData(kAudioObjectSystemObject,&p,0,nullptr,&size,&id); if(status || id==kAudioObjectUnknown) {printf("AUDIO_INPUT_DEVICE status=%d\n",status);return 1;} unsigned channels[2]={0,0}; for(unsigned i=0;i<2;++i) { AudioObjectPropertyAddress c={kAudioDevicePropertyStreamConfiguration, i?kAudioObjectPropertyScopeOutput:kAudioObjectPropertyScopeInput,kAudioObjectPropertyElementMaster}; unsigned char storage[256]={};size=sizeof(storage); if(AudioObjectGetPropertyData(id,&c,0,nullptr,&size,storage)) return 2; auto* list=(AudioBufferList*)storage; for(unsigned b=0;bmNumberBuffers;++b) channels[i]+=list->mBuffers[b].mNumberChannels; } AudioObjectPropertyAddress s={kAudioDevicePropertyStreams,kAudioObjectPropertyScopeInput,kAudioObjectPropertyElementMaster}; UInt32 streams=0; status=AudioObjectGetPropertyDataSize(id,&s,0,nullptr,&streams); AudioObjectPropertyAddress d={kAudioHardwarePropertyDevices,kAudioObjectPropertyScopeGlobal,kAudioObjectPropertyElementMaster}; AudioDeviceID devices[16];size=sizeof(devices); bool listed=false; if(!AudioObjectGetPropertyData(kAudioObjectSystemObject,&d,0,nullptr,&size,devices)) for(unsigned i=0;i