Непрерывная запись в PortAudio (от микрофона или выхода)
Я пытаюсь создать приложение музыкального визуализатора в PortAudio, я провел некоторые базовые исследования и нашел несколько примеров того, как записывать с микрофона в (временный) файл. Но не было примера, когда данные не использовались во время выполнения записи.
Итак, как я могу запустить непрерывный аудиопоток, где я могу поймать данные из текущего "кадра"?
вот как я пытался это сделать:
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include "portaudio.h"
#define SAMPLE_RATE (44100)
typedef struct{
int frameIndex;
int maxFrameIndex;
char* recordedSamples;
}
testData;
PaStream* stream;
static int recordCallback(const void* inputBuffer, void* outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData){
testData* data = (testData*)userData;
const char* buffer_ptr = (const char*)inputBuffer;
char* index_ptr = &data->recordedSamples[data->frameIndex];
long framesToCalc;
long i;
int finished;
unsigned long framesLeft = data->maxFrameIndex - data->frameIndex;
if(framesLeft < frameCount){
framesToCalc = framesLeft;
finished = paComplete;
}else{
framesToCalc = frameCount;
finished = paContinue;
}
if(inputBuffer == NULL){
for(i = 0; i < framesToCalc; i++){
*index_ptr++ = 0;
}
}else{
for(i = 0; i < framesToCalc; i++){
*index_ptr++ = *buffer_ptr++;
}
}
data->frameIndex += framesToCalc;
return finished;
}
int setup(testData streamData){
PaError err;
err = Pa_Initialize();
if(err != paNoError){
fprintf(stderr, "Pa_Initialize error: %sn", Pa_GetErrorText(err));
return 1;
}
PaStreamParameters inputParameters;
inputParameters.device = Pa_GetDefaultInputDevice();
if (inputParameters.device == paNoDevice) {
fprintf(stderr, "Error: No default input device.n");
return 1;
}
inputParameters.channelCount = 1;
inputParameters.sampleFormat = paInt8;
inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
err = Pa_OpenStream(&stream, &inputParameters, NULL, SAMPLE_RATE, 256, paClipOff, recordCallback, &streamData);
if(err != paNoError){
fprintf(stderr, "Pa_OpenDefaultStream error: %sn", Pa_GetErrorText(err));
return 1;
}
err = Pa_StartStream(stream);
if(err != paNoError){
fprintf(stderr, "Pa_StartStream error: %sn", Pa_GetErrorText(err));
return 1;
}
return 0;
}
void quit(testData streamData){
PaError err;
err = Pa_Terminate();
if(err != paNoError){
fprintf(stderr, "Pa_Terminate error: %sn", Pa_GetErrorText(err));
}
if(streamData.recordedSamples)
free(streamData.recordedSamples);
}
int main(){
int i;
PaError err;
testData streamData = {0};
streamData.frameIndex = 0;
streamData.maxFrameIndex = SAMPLE_RATE;
streamData.recordedSamples = (char*)malloc(SAMPLE_RATE * sizeof(char));
if(streamData.recordedSamples == NULL)
printf("Could not allocate record array.n");
for(i=0; i<SAMPLE_RATE; i++)
streamData.recordedSamples[i] = 0;
//int totalFrames = SAMPLE_RATE;
if(!setup(streamData)){
printf("Openedn");
int i = 0;
while(i++ < 500){
if((err = Pa_GetStreamReadAvailable(stream)) != paNoError)
break;
while((err = Pa_IsStreamActive(stream)) == 1){
Pa_Sleep(1000);
}
err = Pa_CloseStream(stream);
if(err != paNoError)
break;
streamData.frameIndex = 0;
for(i=0; i<SAMPLE_RATE; i++)
streamData.recordedSamples[i] = 0;
}
if(err != paNoError){
fprintf(stderr, "Active stream error: %sn", Pa_GetErrorText(err));
}
quit(streamData);
}else{
puts("Couldn't openn");
}
return 0;
}
но это дает следующее вывод:
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.rear
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.center_lfe
ALSA lib pcm.c:2217:(snd_pcm_open_noupdate) Unknown PCM cards.pcm.side
ALSA lib pcm_dmix.c:957:(snd_pcm_dmix_open) The dmix plugin supports only playback stream
Active stream error: Can't read from a callback stream
4 ответов
обновление:
какова цель этого кода?
if((err = Pa_GetStreamReadAvailable(stream)) != paNoError)
break;
мне кажется, что это вызывает вашу (последнюю) проблему. Почему вам нужно получить (а затем отбросить) количество кадров, которые можно прочитать из потока без ожидания, которое, по-видимому, будет равно нулю, поскольку поток является потоком обратного вызова?
предыдущий ответ:
это кажется очень подозрительным:
static void* data;
/* ... */
static int recordCallback(const void* inputBuffer, void* outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData){
testData* data = (testData*)userData;
/* ... */
}
во-первых, почему существуют две переменные с именем data
? Это просто глупо... Вы можете придумать более подходящие идентификаторы?
во-вторых, вы проезжаете &data
(a void **
) в Pa_OpenStream. Предположительно, Pa_OpenStream передает это же значение в вашу функцию обратного вызова, где вы обрабатываете этот указатель на void *
как будто это указывает на testData *
. Это неопределенное поведение.
удалить . Это не обязательно, здесь. Объявить новое static void* data;
testData data = { 0 };
внутри main, прямо на самом верху. Теперь вы проходите testData *
(преобразован в void *
) в Pa_OpenStream, Pa_OpenStream передаст это на ваш обратный вызов, где вы можете безопасно преобразовать его обратно в testData *
как и ты. Возможно, вы захотите установить члены data
перед вызовом Pa_OpenStream...
для взаимодействия с потоком данных в режиме реального времени вам понадобится механизм, который либо спит (занят ожиданиями в Windows) в течение периода кадра (частота дискретизации / образцы на кадр), либо использует примитив синхронизации потоков для запуска вашего потока int main
что есть аудио, готовое к обработке. Это даст вам доступ к каждому кадру данных, которые PortAudio доставляет во время обратного вызова (вызывается из потока PortAudio). Вот как работает концепция с помощью boost::condition
и boost::mutex
.
//CAUTION THIS SNIPPET IS ONLY INTENDED TO DEMONSTRATE HOW ONE MIGHT
//SYNCHRONIZE WITH THE PORTAUDIO THREAD
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/condition.hpp>
#include <boost/interprocess/sync/scoped_lock.hpp>
#include "portaudio.h"
boost::condition waitForAudio;
boost::mutex waitForAudioMutex;
boost::mutex audioBufferMutex;
bool trigger = false;
std::deque<char> audioBuffer;
static int recordCallback(const void* inputBuffer, void* outputBuffer, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData){
const char* buffer_ptr = (const char*)inputBuffer;
//Lock mutex to block user thread from modifying data buffer
audioBufferMutex.lock();
//Copy data to user buffer
for(i = 0; i < frameCount; ++i) {
audioBuffer.push_back(buffer_ptr + i);
}
//Unlock mutex, allow user to manipulate buffer
audioBufferMutex.unlock();
//Signal user thread to process audio
waitForAudioMutex.lock();
trigger= true;
waitForAudio.notify_one();
waitForAudioMutex.unlock();
return finished;
}
int main(){
Pa_Initialize();
//OPEN AND START PORTAUDIO STREAM
while(true){ //Catch signal (Ctrl+C) or some other mechanism to interrupt this loop
boost::xtime duration;
boost::xtime_get(&duration, boost::TIME_UTC);
boost::interprocess::scoped_lock<boost::mutex> lock(waitForAudioMutex);
if(!trigger) {
if(!waitForAudio.timed_wait(lock, duration)) {
//Condition timed out -- assume audio stream failed
break;
}
}
trigger= false;
audioBufferMutex.lock();
//VISUALIZE AUDIO HERE
//JUST MAKE SURE TO FINISH BEFORE PORTAUDIO MAKES ANOTHER CALLBACK
audioBufferMutex.unlock();
}
//STOP AND CLOSE PORTAUDIO STEAM
Pa_Terminate();
return 0;
}
как правило, этот метод является кросс-платформенным, однако эта конкретная реализация может работать только на Linux. В Windows используйте SetEvent(eventVar)
на месте condition::notify_one()
и WaitForSingleObject(eventVar, duration)
вместо condition::timed_wait(lock, duration)
.
вы закрыли поток err = Pa_CloseStream(stream);
в первой итерации. На второй итерации канал был уже закрыт. Попробуйте операцию err = Pa_CloseStream(stream);
после всех итераций.
Я решил это так:
PaStreamParameters inputParameters ,outputParameters;
PaStream* stream;
PaError err;
paTestData data;
int i;
int totalFrames;
int numSamples;
int numBytes;
err = paNoError;
inputParameters.device = 4;
data.maxFrameIndex = totalFrames = NUM_SECONDS * SAMPLE_RATE;
data.frameIndex = 0;
numSamples = totalFrames * NUM_CHANNELS;
numBytes = numSamples * sizeof(SAMPLE);
data.recordedSamples = (SAMPLE *) malloc( numBytes );
std::ofstream arch;
arch.open("signal.csv");
err = Pa_Initialize();
inputParameters.channelCount = 1;
inputParameters.sampleFormat = PA_SAMPLE_TYPE;
inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
inputParameters.hostApiSpecificStreamInfo = NULL;
int contador = 0;
bool strec = true;
while (strec)
{
err = Pa_OpenStream(
&stream,
&inputParameters,
NULL,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
paClipOff,
recordCallback,
&data );
err = Pa_StartStream( stream );
printf("\n===Grabando.... ===\n"); fflush(stdout);
Pa_Sleep(3000);
while( ( err = Pa_IsStreamActive(stream) ) == 1 )
{
}
err = Pa_CloseStream( stream );
for( i=0; i<numSamples; i++ )
{
val = data.recordedSamples[i];
arch << val << std::endl;
std::cout << std::endl << "valor : " << val;
}
data.frameIndex = 0;
contador++;
if (contador >= 100) //if you delete this condition continuously recorded this audio
{
strec = false;
arch.close();
}
}