Непрерывная запись в 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();
    }
}