5
\$\begingroup\$

The following code uses portAudio library to implement a real-time audio input through default input device. The SAMPLE format is float32 with 100 samples per frame.

The code is able to provide samples accurately but there is a significant lag that can be seen.

There are two questions which I would like to put forward.

  1. Changing number of Sample per frame produce different number of Samples acquired. For e.g: When I run the program for 10 sec, it's giving around 811 frames (with 10 samples each) which is about 8110 samples and with 100 samples per frame, it gives 8 frames which is 800 samples for the same interval of time.

    I don't understand why such a difference for as of my understanding the sample acquisition for a particular amount of time should remain constant.

  2. Can the code be optimized as I need to acquire 100 samples per Frame with a lesser lag (presently only 200 samples i.e 2 Frames per second) that I am getting now.

The code :

#include "portaudio.h"

#define FRAME_SIZE (100)    // Total samples that make a Frame. Change here if you wish to have different number of samples per frame
#define CB_SIZE (FRAME_SIZE * 64)   // Maximum limit of Frames handled by the Circular Buffer, here 64 as it would be kind of a delayed system.

typedef float SAMPLE;

PaStream *stream;
const int samplesPerFrame = FRAME_SIZE;

int sampleRate = 8000;
int numChannels = 1;

int num;
int fd;

PaSampleFormat sampleFormat;
PaError err = paNoError;

int i, j=0;

typedef struct{

    SAMPLE *sampleValues;
    int readFromCB;
    int writeToCB;
}
paTestData;                     // This structure will hold everything we need

void printFrame(paTestData *data){

    int readCount   = FRAME_SIZE;

    while(readCount > 0){

        printf("Data: %.8f\n", data->sampleValues[data->readFromCB++]); // Observing Sample Output
        readCount--;
    }

    if(data->readFromCB == CB_SIZE)
        data->readFromCB = 0;

    printf("\n\n");
}

static int paStreamRecordCallback( const void* input, void* output,
    unsigned long samplesPerFrame,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData ) {

    paTestData *data = (paTestData*) userData;

        (void) output;          // Preventing unused variable warnings
        (void) timeInfo;
        (void) statusFlags;

    while(1){

        static int count = FRAME_SIZE;

        if(count > 0){
            const SAMPLE *writeValue = (const SAMPLE*)input; // Each Sample is casted here to float32
            memcpy(&data->sampleValues[data->writeToCB], writeValue, sizeof(SAMPLE)); // Each sample put to sampleValues

                    data->writeToCB++;
                    count--;
            if(data->writeToCB == CB_SIZE)
                data->writeToCB = 0;
        }

        if(count == 0){
            printFrame(data);
            count = FRAME_SIZE;
        }


        return paContinue;
    }

}        

int main(void);
int main(void){

    paTestData data;    // Object of paTestData structure

    fflush(stdout);

    data.writeToCB = 0;
    data.readFromCB = 0;

    data.sampleValues = (SAMPLE*)malloc(sizeof(SAMPLE)*CB_SIZE); // Memory allocation for Circular Buffer

    if(data.sampleValues == NULL){
        printf("Malloc failed to allocate ringBuffer\n");
        goto done;
    }

    err = Pa_Initialize();
    if( err!= paNoError)
        goto done;

    PaStreamParameters inputParameter;

    inputParameter.device = Pa_GetDefaultInputDevice();
    if(inputParameter.device == paNoDevice){
        fprintf(stderr, "ERROR: No input device found.\n");
        goto done;
    }

        /* Input Parameter structure definition here */
    inputParameter.channelCount = numChannels;
    inputParameter.sampleFormat = paFloat32;
    inputParameter.suggestedLatency = Pa_GetDeviceInfo(inputParameter.device)->defaultLowOutputLatency;
    inputParameter.hostApiSpecificStreamInfo = NULL;

    err = Pa_OpenStream(

        &stream,
        &inputParameter,
        NULL,               // *outputParameter Not needed here
        sampleRate,
        samplesPerFrame,
        paClipOff,                      // All flags OFF
        paStreamRecordCallback,
        &data
    );

    if(err != paNoError)
        goto done;

    err = Pa_StartStream(stream);
    if(err != paNoError)
        goto done;

    /* Recording audio here..Speak into the MIC */
    printf("\nRecording samples\n");
    fflush(stdout);



    while((err = Pa_IsStreamActive(stream)) == 1)
        Pa_Sleep(10);       // Time of input sample collection

    if(err < 0)
                goto done;

        err = Pa_CloseStream(stream);
    if(err != paNoError)
        goto done;

    printf("\nRecord program finished successfully!\n");

    /* Error handling here */
    done:

        Pa_Terminate();
        if( data.sampleValues )
            free( data.sampleValues );

        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;
        }

        return err;
}
\$\endgroup\$
  • \$\begingroup\$ Shouldn't readFrame be printFrame (or vice versa)? \$\endgroup\$ – vnp Jul 21 '17 at 17:09
  • \$\begingroup\$ Oh sorry that's a typo \$\endgroup\$ – Ashish K Jul 21 '17 at 17:16
2
\$\begingroup\$
  • First, you should not call printf from a callback. It is unsafe (the callback executes in an async context), and in fact may be responsible for the lags and frame drops.

    On the other hand, you should test the flags to detect input errors.

  • Copying a single sample with memcpy looks strange. A simple assignment would suffice. However, I'd rather memcpy an entire buffer.

  • Never cast the malloc return value. It may mask a serious problem, if you forgot to #include <stdlib.h>. And, BTW, I don't see it included.

  • There is a slight mismatch of terminology, which makes following the code harder. What you call sample, PortAudio calls frame, and what you call frame, PortAudio calls buffer. I recommend to stick with PortAudio terminology.

Edit:

The statusFlags passed to your callback contain, among other things, paInputOverflow and paInputUnderflow flags. If they are set, you have a problem (data lost, and data incorrect, respectively).

\$\endgroup\$
  • \$\begingroup\$ Thanks for your inputs @vnp I got all your points except the first. Could you please elaborate onyou should test the flags to detect input errors ? And I have already included the header you suggested. I skipped them mentioning here. \$\endgroup\$ – Ashish K Jul 21 '17 at 19:08
  • 1
    \$\begingroup\$ @AshishK See edit. \$\endgroup\$ – vnp Jul 21 '17 at 19:22
  • \$\begingroup\$ I will surely try including the flags :) \$\endgroup\$ – Ashish K Jul 21 '17 at 19:23
0
\$\begingroup\$

Thanks to @vpn for pointing out

There is a slight mismatch of terminology, which makes following the code harder. What you call sample, PortAudio calls frame, and what you call frame, PortAudio calls buffer. I recommend to stick with PortAudio terminology.

Here is my callback which is now giving me above 7000 samples per second (at 8000 sampling rate).

static int paStreamRecordCallback( const void* input, void* output,
    unsigned long framesToProcess,
    const PaStreamCallbackTimeInfo* timeInfo,
    PaStreamCallbackFlags statusFlags,
    void* userData ) {

    paTestData *data = (paTestData*) userData;
    const SAMPLE *readPtr = (const SAMPLE*)input;

    SAMPLE *writePtr = &data->frameValues[data->frameIndex];

    int i;

        (void) output;          // Preventing unused variable warnings
        (void) timeInfo;
        (void) statusFlags;

    unsigned long buffersRemaining = data->maxBufferIndex - data->bufferIndex;

    if(buffersRemaining > 0){

        for(i=0; i<framesToProcess; i++)
            *writePtr++ = *readPtr++;

        data->frameIndex += framesToProcess;
        data->bufferIndex++;
    }

    else if(buffersRemaining == 0){

        for(i=0; i<framesToProcess; i++)
            *writePtr++ = *readPtr++;

        data->frameIndex = 1;
        data->bufferIndex = 1;
    }
    return paContinue;

}

And I am printing the values in the main function by checking it through -

int main(){

    // Some Code

    while((err = Pa_IsStreamActive(stream)) == 1){

        Pa_Sleep(16);       // 16 ms so as to achieve 126 frames per buffer
        printValues(data.frameValues);
        printf("Frame Index: %d\nBuffer Index = %d\n", data.frameIndex, data.bufferIndex);
        fflush(stdout);
    }

    // Other Code

}
\$\endgroup\$

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.