【问题标题】:Continuous recording in PortAudio (from mic or output)PortAudio 中的连续录音(来自麦克风或输出)
【发布时间】:2013-03-19 09:53:35
【问题描述】:

我正在尝试在 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

【问题讨论】:

  • 你在哪个平台上工作?
  • 我正在使用 Debian Linux(64 位)

标签: c audio portaudio


【解决方案1】:

更新

这段代码的目的是什么?

        if((err = Pa_GetStreamReadAvailable(stream)) != paNoError)
            break;

在我看来,这是导致您的(最新)问题的原因。为什么需要检索(然后丢弃)无需等待即可从流中读取的帧数,因为 stream 是回调流,所以大概为零?


上一个答案:

这似乎很可疑:

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?太傻了……你能想出更合适的标识符吗?

其次,您将 &amp;datavoid **)传递给 Pa_OpenStream。据推测,Pa_OpenStream 将相同的值传递给您的回调函数,您将指向void * 的指针视为指向testData *。这是未定义的行为。

删除 static void* data;。没必要,这里。在 main 内部声明一个新的 testData data = { 0 };,就在最顶部。现在您将testData *(转换为void *)传递给Pa_OpenStream,Pa_OpenStream 会将其传递给您的回调,您可以安全地将其转换回testData *。您可能想在调用 Pa_OpenStream 之前设置 data 的成员...

【讨论】:

  • 感谢您指出这些错误,我已尽力修复它们,但错误仍然存​​在;我认为这与内存分配无关,因为现在(请参阅更新的问题)我仍然收到 Active stream error: Can't read from a callback stream 错误。所以它在Pa_GetStreamReadAvailable 通话中中断,但我确实有一个可用的麦克风连接到我的电脑。我还想要使用我自己的计算机发送的声音作为输出的替代方案。
  • 我没有时间检查你的答案,但由于积分时间快结束了,我将它授予你;我会在今天晚些时候检查它!
  • 好吧,让我知道这是怎么回事……我非常不喜欢 ALSA,但我总是乐于学习。我会帮你找出问题所在...
  • 啊,我终于有半小时的时间来看看它,当我删除该语句时,它确实进入了循环;但我不知道如何获取数据,当我将fprintf(stderr, "Stream: %s\n", data-&gt;recordedSamples); 添加到recordCallback 函数时,我得到Stream: H�$�# 一次,当我将它添加到while 循环时它仍然是空的!
  • @ThomasVersteeg 是否有任何证据表明data-&gt;recordedSamples 指向一个字符串(即它保证会被'\0' 终止)?如果不是,那么%s 可能不是正确的格式说明符。也许,由于该数据可能包含不可打印的字节,您可能希望将其转储为十六进制:fprintf(stderr, "Stream: "); for (x = 0; x &lt; data-&gt;frameIndex; x++) { fprintf("%X", (unsigned char) data-&gt;recordedSamples[x]); } putchar('\n');
【解决方案2】:

要与数据流实时交互,您需要一种机制,该机制可以在帧周期(采样率/每帧样本)内休眠(Windows 上的忙等待)或使用线程同步原语来触发您的线程@ 987654322@ 有音频准备好处理。这将使您能够访问 PortAudio 在其回调期间(从 PortAudio 线程调用)传递的每一帧数据。以下是使用boost::conditionboost::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)

【讨论】:

【解决方案3】:

您在第一次迭代中关闭了流 err = Pa_CloseStream(stream);。在第二次迭代中,通道已经关闭。在所有迭代之后尝试操作err = Pa_CloseStream(stream);

【讨论】:

  • 当我删除你指出的行时,我仍然没有得到数据。
【解决方案4】:

我是这样解决的:

    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();
        }
    }

【讨论】:

    猜你喜欢
    • 2021-08-13
    • 2018-02-14
    • 1970-01-01
    • 2016-11-21
    • 2017-05-15
    • 2014-11-23
    • 2021-09-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多