Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory problems with continuously recording audio

Here I am trying to write some code for a continuously recording audio system. I am then attempting to record the audio for a certain amount of time when a certain amplitude threshold is broken.

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <portaudio.h>
#include <sndfile.h>

#define FRAMES_PER_BUFFER (1024)
#define SAMPLE_SIZE (4)

typedef struct
{
    uint16_t formatType;
    uint16_t numberOfChannels;
    uint32_t sampleRate;
    float* recordedSamples;
} AudioData;

AudioData initAudioData(uint32_t sampleRate, uint16_t channels, int type)
{
    AudioData data;
    data.formatType = type;
    data.numberOfChannels = channels;
    data.sampleRate = sampleRate;
    return data;
}

float avg(float *data)
{
    int elems = sizeof(data) / sizeof(data[0]);
    float sum = 0;
    for (int i = 0; i < elems; i++)
    {
        sum += fabs(*(data + i));
    }
    return (float) sum / elems;
}

int main(void)
{
    AudioData data = initAudioData(44100, 2, paFloat32);
    PaStream *stream = NULL;
    PaError err = paNoError;
    int size = FRAMES_PER_BUFFER * data.numberOfChannels * SAMPLE_SIZE;
    float *sampleBlock = malloc(size);
    float *recordedSamples = NULL;
    time_t talking = 0;
    time_t silence = 0;

    if((err = Pa_Initialize())) goto done;
    PaStreamParameters inputParameters =
    {
        .device = Pa_GetDefaultInputDevice(),
        .channelCount = data.numberOfChannels,
        .sampleFormat = data.formatType,
        .suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice())->defaultHighInputLatency,
        .hostApiSpecificStreamInfo = NULL
    };
    if((err = Pa_OpenStream(&stream, &inputParameters, NULL, data.sampleRate, FRAMES_PER_BUFFER, paClipOff, NULL, NULL))) goto done;
    if((err = Pa_StartStream(stream))) goto done;
    for(int i = 0;;)
    {
        err = Pa_ReadStream(stream, sampleBlock, FRAMES_PER_BUFFER);
        if(avg(sampleBlock) > 0.000550) // talking
        {
            printf("You're talking! %d\n", i);
            i++;
            time(&talking);
            recordedSamples = realloc(recordedSamples, size * i);
            if (recordedSamples) memcpy(recordedSamples + ((i - 1) * size), sampleBlock, size); // problem here writing to memory at i = 16?
            else free(recordedSamples);
        }
        else //silence
        {
            double test = difftime(time(&silence), talking);
            printf("Time diff: %g\n", test);
            if (test >= 1.5)
            {
                // TODO: finish code processing audio snippet
                talking = 0;
                free(recordedSamples); // problem freeing memory?
            }
        }
    }

done:
    free(sampleBlock);
    Pa_Terminate();
    return err;
}

However, the code is being somewhat finicky. Sometimes when I run my program in Xcode, I get the following output:

Time diff: 1.4218e+09
You're talking! 0
You're talking! 1
You're talking! 2
You're talking! 3
You're talking! 4
You're talking! 5
You're talking! 6
You're talking! 7
You're talking! 8
You're talking! 9
You're talking! 10
You're talking! 11
You're talking! 12
You're talking! 13
You're talking! 14
You're talking! 15
(lldb)

With Xcode pointing to this line being the problem:

if (recordedSamples) memcpy(recordedSamples + ((i - 1) * size), sampleBlock, size); // problem here writing to memory at i = 16?

Other times I run the code, I get this error:

Time diff: 1.4218e+09
You're talking! 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 0
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 1
Time diff: 2
Time diff: 1.4218e+09
CTestEnvironment(55085,0x7fff7938e300) malloc: *** error for object 0x10081ea00: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug

Both errors are somewhat confusing me... any suggestions?

like image 752
syb0rg Avatar asked Jan 21 '15 01:01

syb0rg


2 Answers

You are writing out of bounds of the allocated buffer:

recordedSamples = realloc(recordedSamples, size * i);
memcpy(recordedSamples + ((i - 1) * size), sampleBlock, size);

realloc() allocates a certain number of bytes, here size * i. The resulting pointer is stored in recordedSamples, which has type float*.

The memcpy() then tries to write data to recordedSamples + ((i - 1) * size. Pointer arithmetic is used to determine the location that should be written to. Since recordedSamples is of type float*, recordedSample + X points to a offset of X float values (not X bytes).

In other words, recordedSamples + ((i - 1) * size points to the memory location ((i - 1) * size * sizeof(float) bytes after recordedSamples. This is usually not inside the allocated buffer, since floats are larger than a single byte.

To fix this, the big question is if size is supposed to be a number of bytes or a number of floats. This depends on the API functions you are using, I haven't looked into it in detail.

If it's a number of floats, then you have to adjust the calls to basic memory management functions like malloc, realloc and memcpy, because the all operate on bytes. To instead of malloc(size) you would call malloc(size * sizeof(float)).

If size is indeed a number of bytes, then it would be more logical to make recordedSamples a char*, or at least cast it before doing pointer arithmetic with byte offsets, like memcpy((char*)recordedSamples + ...).

like image 67
sth Avatar answered Nov 09 '22 05:11

sth


These types of bugs are difficult to recreate due to platform differences, so it's hard for me to know precisely what is happening here, but I will point out some issues with your code that could potentially have something to do with it.

I have noticed some problems with your use of free().

Note that free(ptr) does not change the value of ptr, so your latter error can be caused by the following sequence of calls:

free(recordSamples);
free(recordSamples);

This may be happening because you could be entering the test >= 1.5 condition twice, and hence a double free. Solving this problem should be as simple is adding:

recordSamples = NULL; 

whenever you call free. That way, the pointer is NULL when you call free a second time and you will not get an error.

Another potential issue with this very same case is that a pointer that has been freed and then passed into realloc will create undefined behavior. It could happily return an invalid pointer without throwing any errors, depending on the implementation. If that's the case, it makes sense that a memcpy would fail, although admittedly I am not sure whether this is actually happening in your first case. It is possible that some of your output isn't flushed before the error is thrown, so we may not be getting a full picture of what is being called before the errors. A debugger would be useful for that.

My recommendataion is to make sure that recordSamples is always set to NULL after a free (looks like there are only two in your code) and see if that resolves the issues. If there are still problems, I recommend using a tool like valgrind to get more details on why these memory issues are occuring.

Valgrind works by replacing the system's malloc and free with its own that has extensive metadata tracking. It can often report precisely why something like this might fail.

http://valgrind.org/

like image 25
Dougvj Avatar answered Nov 09 '22 07:11

Dougvj