Проблемы с памятью при непрерывной записи звука
Здесь я пытаюсь написать код для непрерывной записи аудиосистемы. Затем я пытаюсь записать аудио в течение определенного промежутка времени, когда нарушается определенный порог амплитуды.
#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