PortAudio 用于连续输入流的实时音频处理
Posted
技术标签:
【中文标题】PortAudio 用于连续输入流的实时音频处理【英文标题】:PortAudio real-time audio processing for continuous input stream 【发布时间】:2017-11-22 13:11:24 【问题描述】:我正在使用 PortAudio 来实现实时音频处理。
我的主要任务是不断地从 mic 获取数据,并提供 100 个样本用于处理 (each FRAME = 100 samples at a time)
给其他一些处理线程。
这是我的回调,每次连续收集 100 个样本 -
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;
我的`main函数-
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
将每帧 100 个样本发送到 processSampleFrame。
void processSampleFrame(SAMPLE *singleFrame)
// Free buffer of this frame
// Processing sample frame here
挑战在于我需要实现一种方式,让processSampleFrame
处理样本的时间,我的callBack
应该处于活动状态并继续获取下一个Frame of 100 samples
并且(可能更多取决于处理时间processSampleFrame
)。
此外,缓冲区应该能够在将帧传递给processSampleFrame
之前将其从帧中释放出来。
编辑 2:
我尝试使用PortAudio
提供的PaUtilRingBuffer
来实现。这是我的回调。
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;
和主要:
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
结构:
typedef struct
SAMPLE *ringBufferData;
PaUtilRingBuffer ringBuffer;
unsigned int frameIndex;
paTestData;
在成功获取分配的空间后,我面临同样的 seg-fault 问题,因为无法按照PortAudio
文档的建议在callback
中使用任何free
。
在哪里可以释放分配给处理线程的已分配帧的缓冲区。可能是一种获得线程同步的方法在这里非常有用。感谢您的帮助。
【问题讨论】:
/tmp/fifoPipe
的消费者在哪里? open(fifoPipe, O_WRONLY);
将阻塞直到管道打开读取,这可能会导致您的数据丢失。另外,您真的想为每个样本创建并打开一个先进先出吗? fifo不应该只创建和打开一次吗?
@Tim ,消费者是另一个线程,这里没有提到。我在这里展示了制作人将每个帧都放到 PIPE 中。我想创建一个非阻塞系统,因此我采用了使用环形缓冲区的方法。请建议我是否可以继续做其他事情?
为什么要把这个发到另一个线程?不能不处理回调中的数据吗?
我认为你应该这样解决这个问题:有一个大小为 4 倍(或更大,4 是任意值)的固定循环缓冲区,你在每个回调中读取的样本数(我们将调用这个尺寸的框架)。每个回调读取大小 FRAMES 并将数据发送到单独的线程/算法以进行处理。在回调结束时,将您的写入索引增加 FRAMES。这会产生延迟,但在实时系统中几乎是不可避免的。我会搜索斯坦福大学的 FFT 和音高转换算法,以了解如何在不丢失任何内容的情况下处理大量数据。
是的;我个人从未在必须在另一个线程中发送数据的情况下这样做。我将在答案中发布一个“示例”,以显示我在说什么。而且你永远不应该在回调中分配/释放内存!这在回调中搞砸了,因为它延迟了系统,因为它需要重新分配内存。抱歉,我自己学了很多这些东西:(
【参考方案1】:
处理音频输入的示例代码:
#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.
把这个和一粒盐放在一起;显然更好的方法可以做到这一点,但它可以用作起点。
【讨论】:
这似乎很棒。在我继续您的建议之前,最后一个快速问题。我应该使用普通的循环缓冲区而不是 PortAudio 提供的有点复杂的 PaUtilBuffer API,我的意思是任何性能差距? 嗯,老实说,从来没有真正研究过它; PaUtilBuffer 可能会更好,但我“最初”被教导的方式是制作自己的缓冲区。如果您不了解某个事物在 API 中的工作原理,那么我认为最好研究它直到您理解它或实现您可以理解的东西。以上是关于PortAudio 用于连续输入流的实时音频处理的主要内容,如果未能解决你的问题,请参考以下文章