5

I am using PortAudio to implement a real-time audio processing.

My primary task is to acquire data from mic continuously and provide 100 samples for processing (each FRAME = 100 samples at a time) to some other processing thread.

Here is my callback collecting 100 samples each time on a continuous basis -

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

    paTestData *data = (paTestData*) userData;
    const SAMPLE *readPtr = (const SAMPLE*)input;   // Casting input read to valid input type SAMPLE (float)
    unsigned long totalSamples = TOTAL_NUM_OF_SAMPLES ; // totalSamples = 100 here

    (void) output;
    (void) timeInfo;
    (void) statusFlags;

    static int count = 1;

    if(data->sampleIndex < count * samplesPerFrame){
        data->recordedSample[data->sampleIndex] = *readPtr;
        data->sampleIndex++;
    }

    else if(data->sampleIndex ==  count * samplesPerFrame){

        processSampleFrame(data->recordedSample);
        count++;
        finished = paContinue;
    }

    return finished;
}

My `main function -

int main(void){

// Some Code here

data.sampleIndex = 0;
data.frameIndex = 1;

numBytes = TOTAL_NUM_OF_SAMPLES * sizeof(SAMPLE);
data.recordedSample = (SAMPLE*)malloc(numBytes);

for(i=0; i < TOTAL_NUM_OF_SAMPLES; i++)
    data.recordedSample[i] = 0;

// Open Stream Initialization

err = Pa_StartStream(stream);

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

while((err = Pa_IsStreamActive(stream)) == 1)
    Pa_Sleep(10);

    if(err < 0)
            goto done;

    err = Pa_CloseStream(stream);

// Some more code here
}

Sending each Frame of 100 samples to processSampleFrame.

void processSampleFrame(SAMPLE *singleFrame){

    // Free buffer of this frame
    // Processing sample frame here
}

The challenge is that I need to implement a way in which the time processSampleFrame is processing the samples, my callBack should be active and keep acquiring the next Frame of 100 samples and (may be more depending upon the processing time of processSampleFrame).

Also the buffer should able to free itself of the frame so sooner it has passed it to processSampleFrame.

EDIT 2 :

I tried implementing with PaUtilRingBuffer that PortAudio provides. Here is my callback.

printf("Inside callback\n");
paTestData *data = (paTestData*) userData;

ring_buffer_size_t elementsWritable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer);
ring_buffer_size_t elementsToWrite = rbs_min(elementsWritable, (ring_buffer_size_t)(samplesPerFrame * numChannels));

const SAMPLE *readPtr = (const SAMPLE*)input;
printf("Sample ReadPtr = %.8f\n", *readPtr);
(void) output;      // Preventing unused variable warnings
(void) timeInfo;
(void) statusFlags;

data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, readPtr, elementsToWrite);

return paContinue;

And main :

int main(void){

    paTestData data;    // Object of paTestData structure

    fflush(stdout);

    data.frameIndex = 1;

    long numBytes = TOTAL_NUM_OF_SAMPLES * LIMIT;
    data.ringBufferData = (SAMPLE*)PaUtil_AllocateMemory(numBytes);
    if(PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), ELEMENTS_TO_WRITE, data.ringBufferData) < 0){
        printf("Failed to initialise Ring Buffer.\n");
        goto done;

    err = Pa_StartStream(stream);

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

    while((err = Pa_IsStreamActive(stream)) == 1)
        Pa_Sleep(10);

    if(err < 0)
                goto done;

        err = Pa_CloseStream(stream);

    // Error Handling here
}

PaTestData Structure :

typedef struct{

    SAMPLE *ringBufferData;
    PaUtilRingBuffer ringBuffer;    
    unsigned int frameIndex;
}
paTestData;

I am facing the same issue of seg-fault after successful acquisition for the allocated space because of not being able to use any free in the callback as suggested by PortAudio documentation.

Where can I free the buffer of the allocated frame given to the processing thread. May be a method of obtaining a thread-synchronization can be really useful here. Your help is appreciated.

Ashish K
  • 905
  • 10
  • 27
  • Where is the consumer of `/tmp/fifoPipe`? `open(fifoPipe, O_WRONLY);` will block until the pipe is opened for reading, which may be causing your data loss. Also, do you really want to create and open a fifo for every sample? Shouldn't the fifo only be created and opened once? – Tim Jun 20 '17 at 20:23
  • @Tim , the consumer is another thread which is not mentioned here. I have shown here the producer putting each frames to PIPE. I want to create a non-blocking system, and hence I have taken an approach using ring buffers. Please suggest if I could proceed with anything else? – Ashish K Jun 21 '17 at 05:45
  • Why send this to another thread? Can you not process the data in the callback? – yun Jun 22 '17 at 15:12
  • @yun.cloud If I process in callback, I will lose some data during that time – Ashish K Jun 22 '17 at 15:19
  • Weird I've never encountered a loss of data (at least severe enough) that it affected performance. What are you specifically trying to do? How does it sound when it's just straight audio input -> audio output? Also circular buffers! – yun Jun 22 '17 at 15:23
  • @yun.cloud Actually I am trying to create a system that will continuously listen for audio signals and send 100 samples to a processing thread (the thread has an algo that is detecting for a hotword). I cannot stop listening for the time the previous frame is being processed. I really need help regarding any way that would help me do the same. – Ashish K Jun 22 '17 at 15:36
  • I am open to write everything back from scratch if you can suggest me any better way to do this. – Ashish K Jun 22 '17 at 15:37
  • @yun.cloud I had implemented using `PaUtilRingBuffer`, but then Don't know how do I `freeBuffer` everytime. I know the `PaUtil_InitialiseRingBuffer` does a `PaUtil_FlushRingBuffer` which will enable storage of another frame but not sure whether I could initialise my ring Buffer inside callback. I will edit the code if you want to look at it. – Ashish K Jun 22 '17 at 20:34
  • @yun.cloud I have edited the question – Ashish K Jun 22 '17 at 21:26
  • 1
    I think you should approach this problem as such: Have a fixed circular buffer with a size 4x (or bigger, 4 is arbitrary) the number of samples you're reading per callback (we'll call this size FRAMES). Read in size FRAMES per callback and send data to your separate thread/algorithm to process. At the end of the callback, increment your write index by FRAMES. This will create latency but it's pretty much unavoidable in a real time system. I would search up FFTs and pitch shifting algorithms from Stanford to understand how to process a chunk of data without losing anything. – yun Jun 23 '17 at 13:44
  • @yun.cloud The send data to the thread would be in the callback itself? And I don't need to free buffer (as in will it be overwriting in the next acquisition after the limit 4 FRAMES you suggested)? – Ashish K Jun 23 '17 at 13:48
  • @yun.cloud also if you could suggest me some good reading/tutorial on PortAudio, it would be a great help. – Ashish K Jun 23 '17 at 13:50
  • 1
    Yes; I personally have never done it where I would have to send the data in another thread. I'll post an "example" in the answers to kind of show what I'm talking about. AND you should never allocate/free memory in a callback! This screws up things in a callback because it delays the system since it needs to reallocate memory. And sorry I pretty much learned a bunch of this stuff by myself :( – yun Jun 23 '17 at 14:05
  • I think I am kind of in a same situation, trying and learning things by myself. As of now I have been able to do this reading the documentations portaudio provides but would really appreciate examples for a better understanding. Thankyou so much @yun.cloud for your support! – Ashish K Jun 23 '17 at 14:10
  • And I think I pretty much got to know what is going wrong when I am sending data to another thread, I would better design the processing thread to use data directly from the ring buffer and increment pointer (by Frame size) rightaway. Waiting for your example anyway :) – Ashish K Jun 23 '17 at 14:14
  • Yea no problem be sure to browse through the portaudio mailing list as well; this stuff definitely has a learning curve – yun Jun 23 '17 at 14:19

1 Answers1

9

Example code of processing audio input:

#define FRAME_SIZE                 1024
#define CIRCULAR_BUFFER_SIZE       (FRAME_SIZE * 4)

float buffer[CIRCULAR_BUFFER_SIZE];

typedef struct {
     int read, write;
     float vol;
} paData;

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

    paData *data = (paData *)userData;
    // Write input buffer to our buffer: (memcpy function is fine, just a for loop of writing data
    memcpy(&buffer[write], input, samplesPerFrame); // Assuming samplesPerFrame = FRAME_SIZE

    // Increment write/wraparound
    write = (write + FRAME_SIZE) % CIRCULAR_BUFFER_SIZE;

    // dummy algorithm for processing blocks:
    processAlgorithm(&buffer[read]);

    // Write output
    float *dummy_buffer = &buffer[read]; // just for easy increment
    for (int i = 0; i < samplesPerFrame; i++) {
         // Mix audio input and processed input; 50% mix
         output[i] = input[i] * 0.5 + dummy_buffer[i] * 0.5;
    }

    // Increment read/wraparound
    read = (read + FRAME_SIZE) % CIRCULAR_BUFFER_SIZE;

    return paContinue;
}

int main(void) {
     // Initialize code here; any memory allocation needs to be done here! default buffers to 0 (silence)
     // initialize paData too; write = 0, read = 3072
     // read is 3072 because I'm thinking about this like a delay system.
}

Take this with a grain of salt; Obviously better ways of doing this but it can be used as a starting point.

yun
  • 1,243
  • 11
  • 30
  • 1
    This seems to be wonderful. One last quick question before I proceed with what you suggested. Shall I use normal circular buffer rather than the bit complex PaUtilBuffer API that PortAudio provides, i mean any performance gap? – Ashish K Jun 23 '17 at 14:23
  • 3
    Hm honestly never really looked into it; PaUtilBuffer might be better but the way I was "initially" taught was to make your own buffer. If you don't understand how a certain thing works in API then I think it's better to either research it until you understand it or implement something you can understand. – yun Jun 23 '17 at 14:28