AUHAL: accept interleaved recording formats on element 1

Vivox sets interleaved Float32 stereo on output scope element 1 and
AUBase's canonical-only rule rejected it with -10868, so voice chat never
started recording (issue #75 trace: 20 attempts, all failing there).
A real AUHAL converts any linear PCM; element 1 now accepts native-endian
float32 or 8/16/32-bit integers in either layout, which PulseAudio records
and RenderBus copies. Playback (element 0) keeps the canonical rule.

The input check gains a Vivox-style run: interleaved stereo 44.1 kHz with
the device set on element 1.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
lucannez64andClaude Opus 5.5 committed 2026-10-19 00:13:16 +02:00
1 parent 52b0fa905d
commit f8b875ddf1
2 files changed
+66 -29

No files matched your search

+21 -12
View File
@@ -20,7 +20,8 @@ static std::atomic<unsigned> callbacks{0}, renderErrors{0}, nonIncreasing{0}, ze
static std::atomic<unsigned long long> totalFrames{0};
static double lastSample=-1;
static bool planar;
static float samples[2][4096];
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;
@@ -31,15 +32,15 @@ static OSStatus input(void*,AudioUnitRenderActionFlags* flags,const AudioTimeSta
auto* list=(AudioBufferList*)storage;
list->mNumberBuffers=planar?2:1;
for(unsigned b=0;b<list->mNumberBuffers;++b) {
list->mBuffers[b].mNumberChannels=1;
list->mBuffers[b].mDataByteSize=frames*sizeof(float);
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;b<list->mNumberBuffers;++b)
if(list->mBuffers[b].mDataByteSize!=frames*sizeof(float)) ++badBuffers;
if(list->mBuffers[b].mDataByteSize!=frames*sizeof(float)*list->mBuffers[b].mNumberChannels) ++badBuffers;
totalFrames+=frames;
return noErr;
}
@@ -95,19 +96,24 @@ static int device(AudioDeviceID* out) {
return channels[0]!=2 || channels[1]!=0 || status || !streams || !listed;
}
static int record(AudioDeviceID id,bool nonInterleaved,unsigned milliseconds) {
static int record(AudioDeviceID id,bool nonInterleaved,unsigned channels,unsigned milliseconds) {
AudioComponentDescription description={kAudioUnitType_Output,kAudioUnitSubType_HALOutput,kAudioUnitManufacturer_Apple,0,0};
AudioComponent component=AudioComponentFindNext(nullptr,&description);
if(!component || AudioComponentInstanceNew(component,&unit) || !unit) return 1;
planar=nonInterleaved;callbacks=0;renderErrors=0;nonIncreasing=0;zeroFrames=0;badBuffers=0;totalFrames=0;lastSample=-1;
planar=nonInterleaved;interleavedChannels=channels;callbacks=0;renderErrors=0;nonIncreasing=0;zeroFrames=0;badBuffers=0;totalFrames=0;lastSample=-1;
UInt32 on=1,off=0;
if(AudioUnitSetProperty(unit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Input,1,&on,sizeof(on))) return 2;
if(AudioUnitSetProperty(unit,kAudioOutputUnitProperty_EnableIO,kAudioUnitScope_Output,0,&off,sizeof(off))) return 3;
if(AudioUnitSetProperty(unit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,0,&id,sizeof(id))) return 4;
// Vivox (the interleaved stereo run) sets the capture device on element 1.
const AudioUnitElement deviceElement=channels==2?1:0;
if(AudioUnitSetProperty(unit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,deviceElement,&id,sizeof(id))) return 4;
AudioDeviceID current=0;UInt32 size=sizeof(current);
if(AudioUnitGetProperty(unit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,0,&current,&size) || current!=id) return 5;
if(AudioUnitGetProperty(unit,kAudioOutputUnitProperty_CurrentDevice,kAudioUnitScope_Global,deviceElement,&current,&size) || current!=id) return 5;
AudioStreamBasicDescription format=nonInterleaved
? AudioStreamBasicDescription{44100,kAudioFormatLinearPCM,kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked|kAudioFormatFlagIsNonInterleaved,4,1,4,2,32,0}
: channels==2
// Vivox's recording format: interleaved Float32 stereo at the device rate.
? AudioStreamBasicDescription{44100,kAudioFormatLinearPCM,kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked,8,1,8,2,32,0}
: AudioStreamBasicDescription{48000,kAudioFormatLinearPCM,kAudioFormatFlagIsFloat|kAudioFormatFlagIsPacked,4,1,4,1,32,0};
OSStatus status=AudioUnitSetProperty(unit,kAudioUnitProperty_StreamFormat,kAudioUnitScope_Output,1,&format,sizeof(format));
if(status) {printf("AUDIO_INPUT_FORMAT status=%d\n",status);return 6;}
@@ -126,8 +132,8 @@ static int record(AudioDeviceID id,bool nonInterleaved,unsigned milliseconds) {
AudioUnitUninitialize(unit);
AudioComponentInstanceDispose(unit);
double seconds=double(totalFrames)/format.mSampleRate;
printf("AUDIO_INPUT_RECORD planar=%d rate=%.0f callbacks=%u frames=%llu seconds=%.3f render_errors=%u bad_buffers=%u "
"nonincreasing=%u zero_frames=%u stop_status=%d quiet_after_stop=%d\n",nonInterleaved,format.mSampleRate,count,
printf("AUDIO_INPUT_RECORD planar=%d channels=%u rate=%.0f callbacks=%u frames=%llu seconds=%.3f render_errors=%u bad_buffers=%u "
"nonincreasing=%u zero_frames=%u stop_status=%d quiet_after_stop=%d\n",nonInterleaved,(unsigned)format.mChannelsPerFrame,format.mSampleRate,count,
(unsigned long long)totalFrames,seconds,(unsigned)renderErrors,(unsigned)badBuffers,(unsigned)nonIncreasing,
(unsigned)zeroFrames,stopped,quiet);
fflush(stdout);
@@ -141,12 +147,15 @@ int main() {
failures+=authorization()!=0;
AudioDeviceID id=kAudioObjectUnknown;
if(device(&id)) return 100+failures;
int result=record(id,false,1000);
int result=record(id,false,1,1000);
if(result) printf("AUDIO_INPUT_RECORD failed step=%d\n",result);
failures+=result!=0;
result=record(id,true,400);
result=record(id,true,2,400);
if(result) printf("AUDIO_INPUT_RECORD failed step=%d planar=1\n",result);
failures+=result!=0;
result=record(id,false,2,400);
if(result) printf("AUDIO_INPUT_RECORD failed step=%d interleaved_stereo=1\n",result);
failures+=result!=0;
printf("AUDIO_INPUT_CHECK failures=%d\n",failures);
return failures;
}