Search code examples
c++audioportaudiocircular-buffer

Writing audio stream to circular buffer but Segmentation error reading value


I very new concepts of audio processing and have a basic knowledge of C++, my end goal is to create a means to measure harmonic distortion on line-in audio; my thoughts are to write audio-in stream to a circular buffer, then read from the circular buffer to an FFT function; from the FFT data I can workout audio distortion.

So far I'm using Portaudio to write streamed audio to the circular buffer from it's streaming input callback; I've created a Thread that checks if there's any data in the circular buffer, if there is data, read it into a temporary buffer. As a means of a simple test, I'm merely echo'ing the number of elements to read from the circular buffer, this works but after a second or so, produces an incorrect value for 'read elements available' (18446744073709542400) followed by a Segmentation error; sample output and my code below:

+Read available 0
Stream started
+Read available 8192
>>dataIndex: 8192
+Read available 2048
>>dataIndex: 10240
+Read available 0
+Read available 9216
>>dataIndex: 19456
+Read available 18446744073709542400
Segmentation fault: 11

Would appreciate any help in understanding the very large read value above (18446744073709542400) and the reason for the Segmentation error; my thoughts are it could be because the Circular Buffer read is being done in a separate Thread; however the Circular Buffer used does state that its Thread safe.

ringbuffer.hpp

main.cpp:

#include <array>
#include <stdio.h>
#include <thread>
#include "ringbuffer.hpp"
#include "portaudio.h"


/* #define SAMPLE_RATE  (17932) // Test failure to open with this value. */
#define SAMPLE_RATE  (44100)
#define FRAMES_PER_BUFFER (512)
#define NUM_SECONDS     (1)
#define NUM_CHANNELS    (2)
#define NUM_WRITES_PER_BUFFER   (4)
#define DITHER_FLAG     (paDitherOff)
//#define DITHER_FLAG     (0)


/* Select sample format. */
#if 1
#define PA_SAMPLE_TYPE  paFloat32
typedef float SAMPLE;
#define SAMPLE_SILENCE  (0.0f)
#define PRINTF_S_FORMAT "%.8f"
#elif 1
#define PA_SAMPLE_TYPE  paInt16
typedef short SAMPLE;
#define SAMPLE_SILENCE  (0)
#define PRINTF_S_FORMAT "%d"
#elif 0
#define PA_SAMPLE_TYPE  paInt8
typedef char SAMPLE;
#define SAMPLE_SILENCE  (0)
#define PRINTF_S_FORMAT "%d"
#else
#define PA_SAMPLE_TYPE  paUInt8
typedef unsigned char SAMPLE;
#define SAMPLE_SILENCE  (128)
#define PRINTF_S_FORMAT "%d"
#endif


static unsigned long int rbs_min(unsigned long int a, unsigned long int b)
{
    return (a < b) ? a : b;
}

typedef struct
{
    unsigned long int frameIndex;
    unsigned long int dataIndex;
    unsigned long int maxFrameIndex;
    int threadSyncFlag;
    SAMPLE *sampleData;
    const SAMPLE* buff;
    Ringbuffer<const SAMPLE*, 65536> ringBuffer;
    void *threadHandle;
}
paTestData;


static int cons(void* ptr) {
    unsigned long int ra_i;

    paTestData* pData = (paTestData*)ptr;

    /* Mark thread started */
    pData->threadSyncFlag = 0;

    while(1) {

        ra_i = pData->ringBuffer.readAvailable();
        printf("+Read available %lu\n",ra_i);

        if ( (pData->dataIndex <= 65536) )
        {
            if (! pData->ringBuffer.isEmpty()) {
                pData->dataIndex += pData->ringBuffer.readBuff(&pData->buff,ra_i);
                printf(">>dataIndex: %lu\n",pData->dataIndex);
            }


        } else
        {
            break;
        }

        Pa_Sleep(100);

    }

    pData->threadSyncFlag = 0;

    return 0;

}

static unsigned NextPowerOf2(unsigned val)
{
    val--;
    val = (val >> 1) | val;
    val = (val >> 2) | val;
    val = (val >> 4) | val;
    val = (val >> 8) | val;
    val = (val >> 16) | val;
    return ++val;
}

/* This routine will be called by the PortAudio engine when audio is needed.
** It may be called at interrupt level on some machines so don't do anything
** that could mess up the system like calling malloc() or free().
*/
static int recordCallback( const void *inputBuffer, void *outputBuffer,
                           unsigned long framesPerBuffer,
                           const PaStreamCallbackTimeInfo* timeInfo,
                           PaStreamCallbackFlags statusFlags,
                           void *userData )
{

    paTestData *data = (paTestData*)userData;

    unsigned long int elementsWriteable = data->ringBuffer.writeAvailable();

    unsigned long int elementsToWrite = rbs_min( elementsWriteable, (unsigned long int)(framesPerBuffer * NUM_CHANNELS) );

    SAMPLE *rptr = (SAMPLE*)inputBuffer;


    (void) outputBuffer; /* Prevent unused variable warnings. */
    (void) timeInfo;
    (void) statusFlags;
    (void) userData;

    data->frameIndex += data->ringBuffer.writeBuff( &rptr, elementsToWrite);

    return paContinue;


}


PaError pa_term(PaError err) {
    Pa_Terminate();
    if( err != paNoError )
    {
        fprintf( stderr, "An error occured while using the portaudio stream\n" );
        fprintf( stderr, "Error number: %d\n", err );
        fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
        err = 1;          // Always return 0 or 1, but no other return codes.
    }
    return err;
}


int main(void);

int main(void) {

    PaStreamParameters  inputParameters,
                        outputParameters;
    PaStream*           stream;
    PaError             err = paNoError;
    paTestData          data = {0};
    unsigned            delayCntr;
    unsigned long int   totalFrames;
    unsigned            numSamples;
    unsigned            numBytes;

    data.dataIndex = 0;

    err = Pa_Initialize();
    if( err != paNoError )
    {
        pa_term(err);
    }

    inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
    if (inputParameters.device == paNoDevice) {
        printf("Error: No default input device.\n");
        pa_term(paDeviceUnavailable);
    }

    inputParameters.channelCount = 2;                    /* stereo input */
    inputParameters.sampleFormat = PA_SAMPLE_TYPE;
    inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
    inputParameters.hostApiSpecificStreamInfo = NULL;

    /* Record some audio. -------------------------------------------- */
    err = Pa_OpenStream(
              &stream,
              &inputParameters,
              NULL,                  /* &outputParameters, */
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,      /* we won't output out of range samples so don't bother clipping them */
              recordCallback,
              &data );
    if( err != paNoError )
    {
        pa_term(err);
    }

    // Start stream logging thread
    std::thread first (cons, &data);


    err = Pa_StartStream( stream );
    if( err != paNoError )
    {
        pa_term(err);
    }

    printf("Stream started\n");

    while(!paNoError) {
        Pa_Sleep(1);
    }

    err = Pa_CloseStream( stream );
    if( err != paNoError )
    {
        pa_term(err);
    }

}

Solution

  • RingBuffer::readAvailable() is returning a small negative number as a size_t. Since size_t is an unsigned type and because you're using %lu in the printf, it's being displayed as though it's a huge unsigned long. (Somehow your output has extra digits.)

    It's possible RingBuffer has a bug. It seems to assume that that tail index will always be less then or equal to the head index. I'm not an expert in the memory ordering constraints used in lockless code, but it looks to me like it could be a synchronization problem with when it reads the head and tail to compute the amount available.