0

I recently designed a Sound recorder on a mac using AudioUnits. It was designed to behave like a video security system, recording continuously, with a graphics display of power levels for playback browsing. I've noticed that every 85 minutes distortion appears for 3 minutes. After a day of elimination it appears that the sound acquisition that occurs before callback is called uses a circular buffer, and the callback's audioUnitRender function extracts from this buffer but with a slightly slower speed, which eventually causes the internal buffer write to wrap around and catch up with audioUnitRender reads. The duplex operation test shows the latency ever increasing, and after 85 minutes you hear about 200-300ms of latency and the noise begins as the render buffer frame has a combination of buffer segments at end and beginning of buffer, i.e long and short latencies. as the pointers drift apart the noise disappears and you hear clean audio with original short latency, then it repeats again 85 mins later. Even with low impact callback processing this still happens. I've seen some posts regarding latency but none regarding clashes, has anyone seen this?

osx 10.9.5, xcode 6.1.1 code details:-

//modes 1=playback, 2=record, 3=both
AudioComponentDescription outputcd = {0}; // 10.6 version  
outputcd.componentType = kAudioUnitType_Output;  
outputcd.componentSubType = kAudioUnitSubType_HALOutput; //allows duplex  
outputcd.componentManufacturer = kAudioUnitManufacturer_Apple;  
AudioComponent comp = AudioComponentFindNext (NULL, &outputcd);  
if (comp == NULL) {printf ("can't get output unit");exit (-1);}  
CheckError (AudioComponentInstanceNew(comp, au),"Couldn't open component for outputUnit");  
//tell input bus that its's input, tell output it's an output  
if(mode==1 || mode==3) r=[self setAudioMode:*au :0];//play  
if(mode==2 || mode==3) r=[self setAudioMode:*au :1];//rec      
// register render callback  
if(mode==1 || mode==3) [self setCallBack:*au :0];  
if(mode==2 || mode==3) [self setCallBack:*au :1];  
// if(mode==2 || mode==3) [self setAllocBuffer:*au];  
// get default stream, change amt of channels  
AudioStreamBasicDescription audioFormat;  
UInt32 k=sizeof(audioFormat);  
r= AudioUnitGetProperty(*au,
                              kAudioUnitProperty_StreamFormat,
                              kAudioUnitScope_Output,
                              1,
                              &audioFormat,
                        &k);  
audioFormat.mChannelsPerFrame=1;  
r= AudioUnitSetProperty(*au,
                        kAudioUnitProperty_StreamFormat,
                        kAudioUnitScope_Output,
                        1,
                        &audioFormat,
                        k);  
//start  
CheckError (AudioUnitInitialize(outputUnit),"Couldn't initialize output unit");  

//record callback  
OSStatus RecProc(void *inRefCon,
             AudioUnitRenderActionFlags *ioActionFlags,
             const AudioTimeStamp *inTimeStamp,
             UInt32 inBusNumber,
             UInt32 inNumberFrames,
             AudioBufferList * ioData)  
{  
myView * mv2=(__bridge myView*)inRefCon;  
AudioBuffer buffer,buffer2;  
OSStatus status;  
buffer.mDataByteSize = inNumberFrames *4 ;// buffer size  
buffer.mNumberChannels = 1; // one channel  
buffer.mData =mv2->rdata;  
buffer2.mDataByteSize = inNumberFrames *4 ;// buffer size  
buffer2.mNumberChannels = 1; // one channel  
buffer2.mData =mv2->rdata2;  
AudioBufferList bufferList;  
bufferList.mNumberBuffers = 2;  
bufferList.mBuffers[0] = buffer;  
bufferList.mBuffers[1] = buffer2;  
status = AudioUnitRender(mv2->outputUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList);  

[mv2 recproc :mv->rdata :mv->rdata2 :inNumberFrames];  
return noErr;  
}  
kirkgcm
  • 49
  • 2
  • 5
  • The glitch cause or error might be in your sample rate setup, inside your audio unit callback function, or in your file writing code, which you didn't show. – hotpaw2 Feb 15 '16 at 16:24
  • I think i've eliminated all of that. my first thought that it was my adpcm compression routines, but i was lucking enough to witness this problem (it takes a lot of patience to wait around for 85 mins) with a scaled down version, no compression, no archiving, just playing what's recorded (with headphones). also note the latency changes, and when the problem occurs you hear the lengthened latency along with the original copy with about 20 ms latency. then the long latency copy and the distortion disappears and audio is clean until it happens again 85 mins later. – kirkgcm Feb 16 '16 at 06:08
  • continued:- this last observations hints that its an internal circular buffer issue. I would throw away a few frames every so often to avoid this is i knew how. maybe missing an audiounitrender call (or adding an extra) per callback would do this but i'm not sure. was hoping someone knew internal be hide the scene details about this As I do believe its in the audio hardware driver (kext) – kirkgcm Feb 16 '16 at 06:10
  • also i am using default audio description, as i audiounitgetproperty what it is, mod just channel count (wanted mono), and set it back, the only issues i had with that was i had to match audiounitrender's buffer params to match (or get error -50). so all is clean for 1 1/2 hrs then crackles for 3 mins, then goes away only to come back again in 1 1/2 hrs. my code does not use any circular buffers btw, just straight linear buffers that were designed twice as big as i knew i needed for safety. – kirkgcm Feb 16 '16 at 06:18

2 Answers2

0

You seem to be using the HAL output unit for pulling input. There might not be a guarantee that the input device and output device sample rates are exactly locked. Any slow slight drift in the sample rate of either device could eventually cause a buffer underflow or overflow.

One solution might be to find and set an input device for a separate input audio unit instead of depending on the default output unit. Try a USB mic, for instance.

hotpaw2
  • 70,107
  • 14
  • 90
  • 153
  • This is a very logical possibility, if rec callback copies to a local buffer then play callback uses it, two rates would cause disturbance as the drift significantly, but it would always get longer apart. that it latency resets means a circular buffer has to be involved and the record buffer ready callback trigger of bus 1 (input) is slower than the actually buffer fill events of same bus. The device is a usb sound card btw. to eliminate the possibility of callback re-entry i did set up double buffering, but nothing changed. – kirkgcm Feb 17 '16 at 03:13
  • Again i observe that its a internal fifo circular buffer with different write/read timings on the input bus. recently recorded mic audio while a movie was being played and when playing back parts of archived audio it started to sound raspy at some point for 3 mins, during that time the audio had a repeat echo about 1/4 sec apart.I am using only linear buffers so an echo has to come from internal circular Buffer. archiving intimestam->msampletime along with date time does not show signs of over/underflow at the time of noise, only missed callbacks during expensive hard drive write every 3 mins. – kirkgcm Feb 17 '16 at 20:04
0

According to this article https://www.native-instruments.com/forum/threads/latency-drift-problem-on-macbook.175551/ this problem appears to be a usb audio driver bug in maverick. I didn't find a kext replacement solution anywhere.

After making a sonar type tester (1 cycle 22khz square wave click every 600 ms to speaker, display selected recorded frame number after click) and could see the 3 to 4 samples drift per second along with the distortion/latency drift reset experience after 1.5 hrs, I decided to look around and find how to access the buffer pointers to stabilise the latency drift, but also no luck.

Also api latency queries show no changes as it drifts.

I did find that you could reset the latency with audiounitstop then audiounitstart (same thread), but it worked only if only one audiounit bus system wide was active. Research also showed that the latency could be reset if you toggle the hardware device sample-rate in Audio Midi Setup. this is a bit aggressive and would be uncomfortable for some.

My design toggled the nominalsamplerate (AudioObjectSetPropertyData with kAudioDevicePropertyNominalSampleRate) every 60 minutes (48000 then back to 44100), with delay by way of waiting for change notification through a callback.

This cause a 2 second void in audio input and output every hour. Safari playing a youtube video would mute, and cause a 1-2 second video freeze during this time . VLC showed the same but video remained smooth during 2 second silence.

Like I said, it wouldn't work for all, but I chose system wide 2 second mute every hour over a recording that has 3 minutes of fuzzy audio every 1.5 hrs. Its been posted that a yosemite upgrade fixes this, although some have also found crackling after going up to yosemite.

kirkgcm
  • 49
  • 2
  • 5