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

Но это дает следующий результат:

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

person tversteeg    schedule 28.03.2013    source источник
comment
На какой платформе вы работаете?   -  person Multimedia Mike    schedule 29.03.2013
comment
Я работаю над Debian Linux (64-разрядная версия).   -  person tversteeg    schedule 29.03.2013


Ответы (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 (void **) в Pa_OpenStream. Предположительно, Pa_OpenStream передает это же значение вашей функции обратного вызова, где вы обрабатываете этот указатель на void *, как если бы он указывал на testData *. Это неопределенное поведение.

Удалите static void* data;. Вот и не надо. Объявите новый testData data = { 0 }; внутри основного, прямо в самом верху. Теперь вы передаете testData * (преобразованный в void *) в Pa_OpenStream, Pa_OpenStream передаст это вашему обратному вызову, где вы можете безопасно преобразовать его обратно в testData *, как есть. Возможно, вы захотите установить членов data перед вызовом Pa_OpenStream...

person autistic    schedule 02.04.2013
comment
Спасибо, что указали на эти ошибки, я пытался исправить их как мог, но ошибка все еще остается; Я не думаю, что это имеет какое-либо отношение к распределению памяти, потому что теперь (см. обновленный вопрос) я все еще получаю ошибку Active stream error: Can't read from a callback stream. Таким образом, он прерывается на вызове Pa_GetStreamReadAvailable, но у меня есть пригодный для использования микрофон, подключенный к моему компьютеру. Я также хотел бы использовать альтернативу использования звука, который мой собственный компьютер отправляет в качестве вывода. - person tversteeg; 02.04.2013
comment
У меня не было времени проверить ваш ответ, но поскольку время начисления баллов почти истекло, я вам его предоставляю; Я проверю это позже сегодня! - person tversteeg; 08.04.2013
comment
Что ж, дайте мне знать, как это работает... Мне очень не нравится ALSA, но я всегда рад научиться. Я помогу тебе понять, что не так... - person autistic; 08.04.2013
comment
Ах, наконец-то у меня есть полчаса, чтобы посмотреть на это, оно действительно переходит в цикл, когда я удаляю это утверждение; но я понятия не имею, как получить данные, когда я добавляю fprintf(stderr, "Stream: %s\n", data->recordedSamples); в функцию recordCallback, я получаю Stream: H�$�# один раз, а когда я добавляю его в цикл while, он остается пустым! - person tversteeg; 09.04.2013
comment
@ThomasVersteeg Есть ли какие-либо доказательства того, что data->recordedSamples указывает на строку (т. Е. Что она гарантированно завершается '\0')? Если нет, то %s может быть неправильным спецификатором формата. Возможно, поскольку эти данные могут содержать непечатаемые байты, вы можете захотеть вывести их в шестнадцатеричном виде: fprintf(stderr, "Stream: "); for (x = 0; x < data->frameIndex; x++) { fprintf("%X", (unsigned char) data->recordedSamples[x]); } putchar('\n'); - person autistic; 10.04.2013
comment
Я пытался добавить ваш фрагмент кода в разные места моего исходного кода, но все, что я получаю, это пустое Stream: ; и когда я добавляю его в цикле while выше Pa_Sleep(), я получаю весь поток Stream: , и он завершается с кодом 139 (segfault). - person tversteeg; 10.04.2013

Для взаимодействия с потоком данных в режиме реального времени вам понадобится механизм, который либо спит (ожидание занятости в 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).

person trukvl    schedule 25.05.2013
comment
@AshishK в чем твои сомнения? - person trukvl; 12.06.2017
comment
Привет @trukvl, запросите ваше предложение по этому поводу, пожалуйста (stackoverflow.com/questions/44645466/) - person Ashish K; 22.06.2017

Вы закрыли поток err = Pa_CloseStream(stream); на первой итерации. Во второй итерации канал уже был закрыт. Попробуйте выполнить операцию err = Pa_CloseStream(stream); после всех итераций.

person Samir Trajano Feitosa    schedule 13.04.2013
comment
Когда я удаляю указанную вами строку, я все равно не получаю данные. - person tversteeg; 14.04.2013

Я решил это так:

    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();
        }
    }
person Gonzalo Rodriguez    schedule 23.04.2015