mirror of
https://github.com/lucannez64/darling-lol.git
synced 2026-10-26 19:47:42 +00:00
83 lines
5.0 KiB
C++
83 lines
5.0 KiB
C++
#include <AudioToolbox/AUGraph.h>
|
|
#include <AudioToolbox/AudioUnitParameters.h>
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <unistd.h>
|
|
#include <initializer_list>
|
|
#include <atomic>
|
|
|
|
static std::atomic<unsigned> calls{0};
|
|
static std::atomic<bool> stopFromCallback{false};
|
|
static std::atomic<int> callbackStopResult{1};
|
|
static AUGraph callbackGraph=nullptr;
|
|
static OSStatus input(void*, AudioUnitRenderActionFlags*, const AudioTimeStamp*,
|
|
UInt32, UInt32 frames, AudioBufferList* data) {
|
|
++calls;
|
|
if (stopFromCallback.exchange(false)) {
|
|
callbackStopResult.store(AUGraphStop(callbackGraph));
|
|
}
|
|
if (data->mNumberBuffers != 2) return -1;
|
|
for (unsigned b=0; b<2; ++b)
|
|
for (unsigned f=0; f<frames; ++f) ((float*)data->mBuffers[b].mData)[f] = b ? .2f : .1f;
|
|
return 0;
|
|
}
|
|
#define CHECK(call) do { OSStatus s=(call); if(s) { printf("FAIL line=%d status=%d\n",__LINE__,s); return 1; } } while(0)
|
|
int main() {
|
|
setbuf(stdout,nullptr);
|
|
printf("AUDIO_GRAPH_CHECK started\n");
|
|
AUGraph graph=nullptr;
|
|
CHECK(NewAUGraph(&graph));
|
|
AudioComponentDescription mix={kAudioUnitType_Mixer,kAudioUnitSubType_MatrixMixer,kAudioUnitManufacturer_Apple,0,0};
|
|
AudioComponentDescription out={kAudioUnitType_Output,kAudioUnitSubType_DefaultOutput,kAudioUnitManufacturer_Apple,0,0};
|
|
AUNode m,o; CHECK(AUGraphAddNode(graph,&mix,&m)); CHECK(AUGraphAddNode(graph,&out,&o));
|
|
CHECK(AUGraphOpen(graph));
|
|
AudioUnit mixer,output; CHECK(AUGraphNodeInfo(graph,m,nullptr,&mixer)); CHECK(AUGraphNodeInfo(graph,o,nullptr,&output));
|
|
AudioStreamBasicDescription format={44100,kAudioFormatLinearPCM,
|
|
kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked|kAudioFormatFlagIsNonInterleaved,4,1,4,2,32,0};
|
|
CHECK(AudioUnitSetProperty(mixer,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Input,0,&format,sizeof(format)));
|
|
CHECK(AudioUnitSetProperty(mixer,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output,0,&format,sizeof(format)));
|
|
CHECK(AudioUnitSetProperty(output,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Input,0,&format,sizeof(format)));
|
|
UInt32 buses=1; CHECK(AudioUnitSetProperty(mixer,kAudioUnitProperty_ElementCount,kAudioUnitScope_Input,0,&buses,sizeof(buses)));
|
|
UInt32 frames=4096; CHECK(AudioUnitSetProperty(mixer,kAudioUnitProperty_MaximumFramesPerSlice,kAudioUnitScope_Global,0,&frames,sizeof(frames)));
|
|
AURenderCallbackStruct cb={input,nullptr}; CHECK(AUGraphSetNodeInputCallback(graph,m,0,&cb));
|
|
CHECK(AUGraphConnectNodeInput(graph,m,0,o,0)); CHECK(AUGraphInitialize(graph));
|
|
// Exercise the exact packed global elements used by the game.
|
|
for (UInt32 e : {0xffffffffu,0xffff0000u,0xffff0001u,0x0000ffffu,0x0001ffffu})
|
|
CHECK(AudioUnitSetParameter(mixer,kMatrixMixerParam_Volume,kAudioUnitScope_Global,e,1,0));
|
|
CHECK(AudioUnitSetParameter(mixer,kMatrixMixerParam_Volume,kAudioUnitScope_Global,0x00000001,.5f,0));
|
|
CHECK(AudioUnitSetParameter(mixer,kMatrixMixerParam_Volume,kAudioUnitScope_Global,0x00010000,.25f,0));
|
|
float left[16]={},right[16]={};
|
|
struct {UInt32 n; AudioBuffer b[2];} data={2,{{1,sizeof(left),left},{1,sizeof(right),right}}};
|
|
AudioTimeStamp time={}; time.mFlags=kAudioTimeStampSampleTimeValid;
|
|
AudioUnitRenderActionFlags flags=0;
|
|
CHECK(AudioUnitRender(mixer,&flags,&time,0,16,(AudioBufferList*)&data));
|
|
for(unsigned f=0;f<16;++f) if(std::fabs(left[f]-.05f)>1e-6 || std::fabs(right[f]-.05f)>1e-6) {
|
|
printf("FAIL matrix left=%f right=%f calls=%u flags=%u\n",left[f],right[f],calls.load(),flags); return 2;
|
|
}
|
|
CHECK(AudioUnitSetParameter(mixer,kMatrixMixerParam_Volume,kAudioUnitScope_Global,0xffffffff,0,0));
|
|
time.mSampleTime=16; CHECK(AudioUnitRender(mixer,&flags,&time,0,16,(AudioBufferList*)&data));
|
|
if (!(flags & kAudioUnitRenderAction_OutputIsSilence) || left[0] || right[0]) return 3;
|
|
CHECK(AudioUnitSetParameter(mixer,kMatrixMixerParam_Volume,kAudioUnitScope_Global,0xffffffff,.05f,0));
|
|
for(unsigned cycle=0;cycle<2;++cycle) {
|
|
unsigned before=calls;
|
|
CHECK(AUGraphStart(graph)); usleep(250000); CHECK(AUGraphStop(graph));
|
|
if(calls<=before) {printf("FAIL no output callbacks\n");return 4;}
|
|
}
|
|
callbackGraph=graph;
|
|
for(unsigned cycle=0;cycle<20;++cycle) {
|
|
callbackStopResult=1;
|
|
CHECK(AUGraphStart(graph));
|
|
stopFromCallback=true;
|
|
for(unsigned wait=0;wait<200 && callbackStopResult.load()==1;++wait) usleep(10000);
|
|
if(callbackStopResult.load()!=noErr) {
|
|
printf("FAIL stop inside render callback cycle=%u status=%d\n",cycle,callbackStopResult.load()); return 5;
|
|
}
|
|
// A stopped IOProc must not be invoked again, including queued PA work.
|
|
unsigned stoppedCalls=calls.load(); usleep(50000);
|
|
if(calls.load()!=stoppedCalls) {printf("FAIL callbacks after stop\n");return 6;}
|
|
}
|
|
CHECK(AUGraphUninitialize(graph)); CHECK(AUGraphClose(graph)); CHECK(DisposeAUGraph(graph));
|
|
printf("PASS graph, matrix routing, mute, output start/stop/restart and 20 callback-initiated stops callbacks=%u\n",calls.load());
|
|
return 0;
|
|
}
|