问题
I am trying to create a music visualizer application in PortAudio, I did some basic research and found some examples on how to record from a mic to a (temporary) file. But there was no example where the data is not used runtime during the recording.
So how can I start a continuous audio-stream where I can catch the data from the current "frame"?
This is how I tried to do it:
#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: %s\n", 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: %s\n", Pa_GetErrorText(err));
return 1;
}
err = Pa_StartStream(stream);
if(err != paNoError){
fprintf(stderr, "Pa_StartStream error: %s\n", Pa_GetErrorText(err));
return 1;
}
return 0;
}
void quit(testData streamData){
PaError err;
err = Pa_Terminate();
if(err != paNoError){
fprintf(stderr, "Pa_Terminate error: %s\n", 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("Opened\n");
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: %s\n", Pa_GetErrorText(err));
}
quit(streamData);
}else{
puts("Couldn't open\n");
}
return 0;
}
But it gives the following output:
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
回答1:
Update:
What is the purpose of this code?
if((err = Pa_GetStreamReadAvailable(stream)) != paNoError)
break;
It seems to me like this is causing your (latest) problem. Why do you need to retrieve (and then discard) the number of frames that can be read from the stream without waiting, which would presumably be zero since stream is a callback stream?
Previous answer:
This seems highly suspicious:
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;
/* ... */
}
Firstly, why are there two variables named data
? That's just silly... Can you think of more appropriate identifiers?
Secondly, you're passing a &data
(a void **
) to Pa_OpenStream. Presumably, Pa_OpenStream passes that same value on to your callback function, where you treat that pointer to void *
as though it points to a testData *
. That's undefined behaviour.
Remove . That's not necessary, here. Declare a new static void* data;
testData data = { 0 };
inside main, right at the very top. Now you're passing a testData *
(converted to void *
) to Pa_OpenStream, Pa_OpenStream will pass that on to your callback where you can safely convert it back to a testData *
as you are. You might want to set the members of data
before calling Pa_OpenStream...
回答2:
To interact with the data stream real-time you'll need a mechanism that either sleeps (busy waits on Windows) for the frame period (sample rate / samples per frame) or uses a thread synchronization primitive to trigger your thread int main
that there is audio ready to be processed. This will give you access to each frame of data that PortAudio delivers during its callback (called from the PortAudio thread). Here's how the concept works using boost::condition
and 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;
}
Generally, this technique is cross-platform, however this specific implementation may only work on Linux. On Windows use SetEvent(eventVar)
in place of condition::notify_one()
and WaitForSingleObject(eventVar, duration)
instead of condition::timed_wait(lock, duration)
.
回答3:
You closed the stream err = Pa_CloseStream(stream);
in the first iteration. In the second iteration, the channel was already closed. Try the operation err = Pa_CloseStream(stream);
after all iterations.
回答4:
I solved it this way:
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();
}
}
来源:https://stackoverflow.com/questions/15690668/continuous-recording-in-portaudio-from-mic-or-output