0
$\begingroup$

I am trying to understand audio-buffer processing at the low-level for the first time.

I have the following C++ code copying WAVE-format byte data from capture buffer of Window Core Audio (*ppData in that documentation), into an "AudioSink."

Could someone explain what's happening here (the "WHAT'S GOING ON HERE" in the code)?
I'm confused as to why this works, how the number of channels, number of frames, and the conversion between BYTE and int16 factor into the calculation? I need to eventually convert this into float data so I need to understand what is happening!

Thank you!!!

int AudioSink::CopyData(const BYTE* Data, const int NumFramesAvailable)
{
    // Data is the ppData returned pointer from IAudioCaptureClient::GetBuffer

    std::lock_guard<std::mutex> lock(m_mutex);

    AudioChunk chunk;
    if (Data == NULL)
    {
        chunk.size = 0;
        m_queue.push(chunk);
        return 0;
    }

    // *****  WHAT'S GOING ON HERE  *******
    chunk.size      = NumFramesAvailable * 2;
    chunk.chunk     = new int16[chunk.size];
    int multiplier = sizeof(int16) / sizeof(unsigned char);
    std::memcpy(chunk.chunk, Data, chunk.size * multiplier);
    bool nonZero    = false;

    for (int i = 0; i < chunk.size; i++)
    {
        if (chunk.chunk[i] == -1 || chunk.chunk[i] == 1)
        {
            chunk.chunk[i] = 0;
        }
        if (chunk.chunk[i] != 0)
        {
            nonZero = true;
            UE_LOG(LogTemp, Log, TEXT("NumFramesAvailable: %d, Sample number %d, Sample value %hi"), NumFramesAvailable, i, chunk.chunk[i]);
        }
    }

    if (nonZero)
    {
        m_queue.push(chunk);
    }

    return 0;
}
$\endgroup$
1
$\begingroup$

This doesn't seem to be very good code, and it certainly isn't well commented to explain why it is doing what it is doing.

To answer your question at the "***** WHAT'S GOING ON HERE *******" mark:

1) The "* 2" in the chunk.size calculation is clearly because the data is assumed to be stereo.

2) Each sample value is a 2 byte int (16 bit), the chunk needs to have chunk.size samples.

3) The multiplier is to compensate for the memcpy working differently on different platforms. A char may be one or two bytes, memcpy moves char sized pieces. It used to be strictly bytes, but that isn't the case with this code. This problem would be more efficiently solved with a global DEFINE.

4) The memcpy copies the data from the input buffer to the storage location

5) The loop looks for a silent chunk, allowing a little tolerance -1 to 1 is silent.

Now, here is why I think this is poor code:

  • For ordinary audio, there are going to be a heck of a lot of Log messages generated.

  • If it is non-zero (not silent) the chunk will be kept (pushed onto the queue). If logging every value isn't needed, the loop should break as soon as the chunk is determined not to be silent

  • The logic of zeroing 1s and -1s in a kept chunk makes no sense to me

  • If the chunk is not kept, the chunk.chunk array is not deleted (assuming the kept chunks are deleted downstream). This is a memory leak.

That's enough for now, hope it was helpful.

Ced

$\endgroup$
  • $\begingroup$ more above and beyond... (i ain't gonna get my feet so muddy.) $\endgroup$ – robert bristow-johnson Apr 26 '18 at 3:23
  • $\begingroup$ Appreciate it! Yes I inherited this code and having not had much C++ experience either it is driving me nuts. I think I'm going to try using github.com/thestk/rtaudio instead $\endgroup$ – blisstdev Apr 26 '18 at 18:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.