Проблемы с памятью при непрерывной записи звука

Здесь я пытаюсь написать код для непрерывной записи аудиосистемы. Затем я пытаюсь записать аудио в течение определенного промежутка времени, когда нарушается определенный порог амплитуды.

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

Однако код несколько пугающий. Иногда, когда я запускаю свою программу в Xcode, я получаю следующий вывод:

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)

Когда Xcode указывает на эту строку, проблема:

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

В других случаях я запускаю код, я получаю эту ошибку:

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

Обе ошибки несколько сбивают меня с толку... любые предложения?

Ответ 1

Вы записываете границы выделенного буфера:

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

realloc() выделяет определенное количество байтов, здесь size * i. Результирующий указатель хранится в recordedSamples, который имеет тип float*.

Затем memcpy() пытается записать данные в recordedSamples + ((i - 1) * size. Арифметика указателя используется для определения местоположения, которое должно быть записано. Поскольку recordedSamples имеет тип float*, recordedSample + X указывает на смещение значений X float (не X байтов).

Другими словами, recordedSamples + ((i - 1) * size указывает на ячейку памяти ((i - 1) * size * sizeof(float) байт после recordedSamples. Обычно это не внутри выделенного буфера, так как float больше, чем один байт.

Чтобы исправить это, большой вопрос заключается в том, что size должен быть числом байтов или числом поплавков. Это зависит от функций API, которые вы используете, я не рассматривал их подробно.

Если это число floats, вам необходимо настроить вызовы на основные функции управления памятью, такие как malloc, realloc и memcpy, потому что все работают с байтами. Вместо malloc(size) вы вызываете malloc(size * sizeof(float)).

Если size действительно число байтов, было бы логичнее сделать recordedSamples a char* или, по крайней мере, бросить его перед выполнением арифметики указателя с смещениями байта, например memcpy((char*)recordedSamples + ...).

Ответ 2

Эти типы ошибок трудно воссоздать из-за различий в платформе, поэтому мне трудно точно знать, что здесь происходит, но я укажу на некоторые проблемы с вашим кодом, которые могут иметь какое-то отношение к нему.

Я заметил некоторые проблемы с вашим использованием free().

Обратите внимание, что free (ptr) не изменяет значение ptr, поэтому ваша последняя ошибка может быть вызвана следующей последовательностью вызовов:

free(recordSamples);
free(recordSamples);

Это может произойти, потому что вы можете дважды войти в тест >= 1.5, а значит, и двойной. Решение этой проблемы должно быть простым: добавление:

recordSamples = NULL; 

когда вы звоните бесплатно. Таким образом, указатель будет NULL, когда вы вызываете бесплатный второй раз, и вы не получите ошибку.

Еще одна потенциальная проблема в этом же случае заключается в том, что указатель, который был освобожден, а затем передан в realloc, создаст поведение undefined. Он может с радостью вернуть недопустимый указатель без каких-либо ошибок, в зависимости от реализации. Если это так, имеет смысл, что memcpy потерпит неудачу, хотя, по общему признанию, я не уверен, действительно ли это происходит в вашем первом случае. Возможно, что некоторые из ваших выходов не сбрасываются до появления ошибки, поэтому мы можем не получить полную картину того, что вызывается перед ошибками. Для этого был бы полезен отладчик.

Моя рекомендация состоит в том, чтобы убедиться, что в файле recordSamples всегда установлено значение NULL после бесплатного (похоже, что в коде есть только два) и посмотрите, устраняет ли это проблемы. Если все еще есть проблемы, я рекомендую использовать такой инструмент, как valgrind, чтобы получить более подробную информацию о том, почему возникают эти проблемы памяти.

Valgrind работает, заменяя систему malloc и бесплатную с ее собственной, которая имеет обширное отслеживание метаданных. Он может часто сообщать, почему что-то подобное может потерпеть неудачу.

http://valgrind.org/

Ответ 3

// Note: I do not have the portaudio.h and sndfile.h so could not compile/test


#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)
#define NUMBER_OF_CHANNELS (2)
#define SAMPLE_RATE        (44100)
#define SIZE               (FRAMES_PER_BUFFER * NUMBER_OF_CHANNELS * SAMPLE_SIZE)

static const int size = SIZE;

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


void initAudioData(
    struct AudioData_t *pData,
    uint32_t sampleRate,
    uint16_t channels,
    int type)
{
    (*pData).formatType = type;
    (*pData).numberOfChannels = channels;
    (*pData).sampleRate = sampleRate;
} // end function: initAudioData


float averageAudioLevel(float *data)
{
    int elems = size / sizeof(float); // <--
    float sum = 0;

    for (int i = 0; i < elems; i++)
    {
        sum += fabs(*(data + i));
    }
    return  sum / elems; // sum is float so result is float
} // end function: averageAudioLevel


int main(void)
{
    struct AudioData_t data;
    initAudioData(&data, SAMPLE_RATE, NUMBER_OF_CHANNELS, paFloat32);

    PaStream *stream = NULL;
    PaError err = paNoError;


    float *sampleBlock = NULL;

    if(NULL == (sampleBlock = malloc(size) ) )
    { // then, malloc failed
        perror( "malloc failed" );
        exit( EXIT_FAILURE );
    }

    // implied else, malloc successful

    float *recordedSamples = NULL;

    time_t talking = 0;
    time_t silence = 0;

    if( 0 == (err = Pa_Initialize()))
    { // then init successful

        PaStreamParameters inputParameters =
        {
            .device = Pa_GetDefaultInputDevice(),
            .channelCount = data.numberOfChannels,
            .sampleFormat = data.formatType,
            .suggestedLatency = Pa_GetDeviceInfo(Pa_GetDefaultInputDevice())->defaultHighInputLatency,
            .hostApiSpecificStreamInfo = NULL
        };

        // if err >0, exit
        if( 0 == (err = Pa_OpenStream(&stream, &inputParameters, NULL, SAMPLE_RATE, FRAMES_PER_BUFFER, paClipOff, NULL, NULL)))
        { // then success
            if( 0 == (err = Pa_StartStream(stream)) )
            { // then success

                int i = 0;
                while(1) // this loop never exits
                {
                    talking = 0;
                    if( 0 == (err = Pa_ReadStream(stream, sampleBlock, FRAMES_PER_BUFFER) ) )
                    {
                        if(averageAudioLevel(sampleBlock) > 0.000550) // talking
                        {
                            printf("You're talking! %d\n", i);

                            i++; // counting usable audio samples

                            if( !talking ) {talking = time(NULL);} // only do once per audio sample

                            // increase allocation for another audio sample (starts at 0 allocated)
                            float *temp;
                            if( NULL == (temp = realloc(recordedSamples, size * i ) )
                            { // then realloc failed
                                perror( ""realloc failed" ");
                                free( sampleBlock );
                                free( recordedSamples );
                                exit( EXIT_FAILURE );
                            }

                            // implied else, realloc successful

                            // update the actual allocated memory pointer
                            recordedSamples = temp;

                            // save the new sample into array of samples
                            memcpy(recordedSamples + ((i - 1) * size), sampleBlock, size);}
                        } // end if
                    }

                    else //silence
                    {
                        if( 0 < i )
                        { // then some samples to evaluate

                            double elapsedTime = difftime(time(NULL), talking);
                            printf("Time diff: %g\n", elapsedTime);

                            if (elapsedTime >= 1.5)
                            {
                                // TODO: finish code processing audio snippet

                                // reset time indicators so do not process silence unless proceed by audio sound
                                talking = 0;

                                // reset audio sample counter
                                i = 0;

                                // dispose of recorded samples
                                free( recordedSamples );

                                // prep for next recording
                                recordedSamples = NULL;
                            } // end if
                        } // end if
                    } // end if
                } //  end forever loop
            } // end if
        } // end if
    } // end if


    free( sampleBlock );
    free( recordedSamples );
    Pa_Terminate();
    return err;
} // end function: main