meirm
meirm

Reputation: 1379

Crackling audio due to wrong audio data

I'm using CoreAudio low level API for audio capturing. The app target is MAC OSX, not iOS.

During testing it, from time to time we got very annoying noise modulate with real audio. the phenomena develops with time, started from barely noticeable and become more and more dominant.

Analyze the captured audio under Audacity indicate that the end part of the audio packet is wrong.

Here are sample picture: enter image description here

the intrusion repeat every 40 ms which is the configured packetization time (in terms of buffer samples)

Update: Over time the gap became larger, here is another snapshot from the same captured file 10 minutes later. the gap now contains 1460 samples which is 33ms from the total 40ms of the packet!! enter image description here

CODE SNIPPESTS:

capture callback

OSStatus MacOS_AudioDevice::captureCallback(void *inRefCon,
                                            AudioUnitRenderActionFlags *ioActionFlags,
                                            const AudioTimeStamp *inTimeStamp,
                                            UInt32 inBusNumber,
                                            UInt32 inNumberFrames,
                                            AudioBufferList *ioData)
{
    MacOS_AudioDevice* _this = static_cast<MacOS_AudioDevice*>(inRefCon);

    // Get the new audio data
    OSStatus err = AudioUnitRender(_this->m_AUHAL, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, _this->m_InputBuffer);
    if (err != noErr)
    {
        ...

        return err;
    }

    // ignore callback on unexpected buffer size
    if (_this->m_params.bufferSizeSamples != inNumberFrames)
    {
        ...

        return noErr;
    }

    // Deliver audio data 
    DeviceIOMessage message;
    message.bufferSizeBytes = _this->m_deviceBufferSizeBytes;
    message.buffer = _this->m_InputBuffer->mBuffers[0].mData;
    if (_this->m_callbackFunc)
    {
       _this->m_callbackFunc(_this, message);
    }
}

Open and start capture device:

void MacOS_AudioDevice::openAUHALCapture()
{
    UInt32 enableIO;
    AudioStreamBasicDescription streamFormat;
    UInt32 size;
    SInt32 *channelArr;
    std::stringstream ss;
    AudioObjectPropertyAddress deviceBufSizeProperty =
    {
        kAudioDevicePropertyBufferFrameSize,
        kAudioDevicePropertyScopeInput,
        kAudioObjectPropertyElementMaster
    };

    // AUHAL
    AudioComponentDescription cd = {kAudioUnitType_Output, kAudioUnitSubType_HALOutput, kAudioUnitManufacturer_Apple, 0, 0};
    AudioComponent HALOutput = AudioComponentFindNext(NULL, &cd);
    verify_macosapi(AudioComponentInstanceNew(HALOutput, &m_AUHAL));

    verify_macosapi(AudioUnitInitialize(m_AUHAL));

    // enable input IO
    enableIO = 1;
    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &enableIO, sizeof(enableIO)));

    // disable output IO
    enableIO = 0;
    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Output, 0, &enableIO, sizeof(enableIO)));

    // Setup current device
    size = sizeof(AudioDeviceID);
    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioOutputUnitProperty_CurrentDevice, kAudioUnitScope_Global, 0, &m_MacDeviceID, sizeof(AudioDeviceID)));

    // Set device native buffer length before setting AUHAL stream
    size = sizeof(m_originalDeviceBufferTimeFrames);
    verify_macosapi(AudioObjectSetPropertyData(m_MacDeviceID, &deviceBufSizeProperty, 0, NULL, size, &m_originalDeviceBufferTimeFrames));

    // Get device format
    size = sizeof(AudioStreamBasicDescription);
    verify_macosapi(AudioUnitGetProperty(m_AUHAL, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 1, &streamFormat, &size));

    // Setup channel map
    assert(m_params.numOfChannels <= streamFormat.mChannelsPerFrame);
    channelArr = new SInt32[streamFormat.mChannelsPerFrame];
    for (int i = 0; i < streamFormat.mChannelsPerFrame; i++)
        channelArr[i] = -1;
    for (int i = 0; i < m_params.numOfChannels; i++)
        channelArr[i] = i;

    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioOutputUnitProperty_ChannelMap, kAudioUnitScope_Input, 1, channelArr, sizeof(SInt32) * streamFormat.mChannelsPerFrame));
    delete [] channelArr;

    // Setup stream converters
    streamFormat.mFormatID = kAudioFormatLinearPCM;
    streamFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger;
    streamFormat.mFramesPerPacket = m_SamplesPerPacket;
    streamFormat.mBitsPerChannel = m_params.sampleDepthBits;
    streamFormat.mSampleRate = m_deviceSampleRate;
    streamFormat.mChannelsPerFrame = 1;
    streamFormat.mBytesPerFrame = 2;
    streamFormat.mBytesPerPacket = streamFormat.mFramesPerPacket * streamFormat.mBytesPerFrame;

    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &streamFormat, size));

    // Setup callbacks
    AURenderCallbackStruct input;
    input.inputProc = captureCallback;
    input.inputProcRefCon = this;
    verify_macosapi(AudioUnitSetProperty(m_AUHAL, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &input, sizeof(input)));

    // Calculate the size of the IO buffer (in samples)
    if (m_params.bufferSizeMS != -1)
    {
        unsigned int desiredSignalsInBuffer = (m_params.bufferSizeMS / (double)1000) * m_deviceSampleRate;

        // making sure the value stay in the device's supported range
        desiredSignalsInBuffer = std::min<unsigned int>(desiredSignalsInBuffer, m_deviceBufferFramesRange.mMaximum);
        desiredSignalsInBuffer = std::max<unsigned int>(m_deviceBufferFramesRange.mMinimum, desiredSignalsInBuffer);

        m_deviceBufferFrames = desiredSignalsInBuffer;
    }

    // Set device buffer length
    size = sizeof(m_deviceBufferFrames);
    verify_macosapi(AudioObjectSetPropertyData(m_MacDeviceID, &deviceBufSizeProperty, 0, NULL, size, &m_deviceBufferFrames));

    m_deviceBufferSizeBytes = m_deviceBufferFrames * streamFormat.mBytesPerFrame;
    m_deviceBufferTimeMS = 1000 * m_deviceBufferFrames/m_deviceSampleRate;

    // Calculate number of buffers from channels
    size = offsetof(AudioBufferList, mBuffers[0]) + (sizeof(AudioBuffer) * m_params.numOfChannels);

    // Allocate input buffer
    m_InputBuffer = (AudioBufferList *)malloc(size);
    m_InputBuffer->mNumberBuffers = m_params.numOfChannels;

    // Pre-malloc buffers for AudioBufferLists
    for(UInt32 i = 0; i< m_InputBuffer->mNumberBuffers ; i++)
    {
        m_InputBuffer->mBuffers[i].mNumberChannels = 1;
        m_InputBuffer->mBuffers[i].mDataByteSize = m_deviceBufferSizeBytes;
        m_InputBuffer->mBuffers[i].mData = malloc(m_deviceBufferSizeBytes);
    }

    // Update class properties
    m_params.sampleRateHz = streamFormat.mSampleRate;
    m_params.bufferSizeSamples = m_deviceBufferFrames;
    m_params.bufferSizeBytes = m_params.bufferSizeSamples * streamFormat.mBytesPerFrame;

}


eADMReturnCode MacOS_AudioDevice::start()
{
    eADMReturnCode ret = OK;
    LOGAPI(ret);

    if (!m_isStarted && m_isOpen)
    {
        OSStatus err = AudioOutputUnitStart(m_AUHAL);
        if (err == noErr)
            m_isStarted = true;
        else
            ret = ERROR;
    }
    return ret;
}

Any idea what cause it and how to solve?

Thanks in advance!

Upvotes: 1

Views: 412

Answers (1)

hotpaw2
hotpaw2

Reputation: 70733

Periodic glitches or dropouts can be caused by not paying attention to or by not fully processing the number of frames sent to each audio callback. Valid buffers don't always contain the expected or same number of samples (inNumberFrames might not equal bufferSizeSamples or the previous inNumberFrames in a perfectly valid audio buffer).

It is possible that these types of glitches might be caused by attempting to record at 44.1k on some models of iOS devices that only support 48k audio in hardware.

Some types of glitch might also be caused by any non-hard-real-time code within your m_callbackFunc function (such as any synchronous file reads/writes, OS calls, Objective C message dispatch, GC, or memory allocation/deallocation).

Upvotes: 2

Related Questions