LoginSignup
3
3

More than 5 years have passed since last update.

【visual studio】PortAudioのBlocking Read/Write関数

Posted at

まとめ

  • callback関数を使いません
  • c言語のscanf関数やprintf関数に似た記述になります.

なぜBlocking APIなのか

PortAudioを使ってASIOドライバから音声を入出力をしようとすると,通常callback関数を使ってデータを受け渡しします.しかし,c++でcallback関数を使うと,発火する(callback関数が呼び出される)スレッドと,main関数のスレッドが違うため,データを受け渡しするために,共有メモリを用意したり特殊なデータ処理が必要になります.これはcallback処理に詳しい方ならば,実装できると思いますが,音響処理のビギナーにとってはハードルが高いです.特に大学の授業でcallback処理を扱う事は無いので,理解がしにくいと思います.

そこで,PortAudioではcallbackを使わずにデータを受け渡す,blocking r/w functionというものがあります.
普通にPortAudioのドキュメントに書いてあるのですが,英語だけで日本語で書かれたものがないので,なかなか使う人がいないような気がします.

準備

Example

1秒ごとに入力からデータを取得して出力にそのまま流すだけの処理

CLRコンソールアプリケーションのプロジェクトで作成しています.

portaudioBlockngAPI.cpp

#include "stdafx.h"

#include "portaudio.h"//the header file of portaudio

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

using namespace System;


#define     SAMPLE_RATE   (44100)   //sampling rate to open portaudio
#define     FRAMES_PER_BUFFER   (44100) // number of frames per buffer. In this portaudio, 'frames' are used like our samples. 
#define     NUM_CHANNELS   (2)  // 1: mono 2:stereo. Roland octa-capture is not accept more than 2ch.

#define     PRINTF_S_FORMAT   "%.8f"

#define     PA_SAMPLE_TYPE   paInt16    //16bit
#define     SAMPLE_SIZE (2)             //16bit = 2 * 8bit(=byte)

//macros
#define CLEAR(a) memset( (a), 0, FRAMES_PER_BUFFER * NUM_CHANNELS * SAMPLE_SIZE )
#define Lch(a,i) *(a+2*i)
#define Rch(a,i) *(a+2*i+1)


void save_text(int N, __int16* wave,FILE *fp);
void filterExecute(int N, __int16* wave);

int main(array<System::String ^> ^args)
{
    FILE *fp;
    PaStreamParameters inputParameters, outputParameters;
    PaStream *stream;
    PaError err;
    __int16 *sampleBlock;
    int i;
    int numBytes;

    /* Initalize Buffer*/
    numBytes = FRAMES_PER_BUFFER * NUM_CHANNELS * SAMPLE_SIZE ;
    sampleBlock = (__int16 *) malloc( numBytes );
    if( sampleBlock == NULL )
    {
        printf("Could not allocate record array.\n");
        exit(1);
    }
    CLEAR( sampleBlock );

    /* Initalize PortAudio */
    err = Pa_Initialize();
    if( err != paNoError ) {
        Console::WriteLine(L"Error");
        return 1;
    }

    /* Initalize Save File Point*/
    if((fp = fopen("output.txt","w"))==NULL){
        printf("Could not open 'output.txt'. \n");
        exit(1);
    }

    /* -- setup input and output -- */
    inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */

    printf( "Input device # %d.\n", inputParameters.device );
    printf( "Input device max channels %d.\n", Pa_GetDeviceInfo(inputParameters.device)->maxInputChannels );

    inputParameters.channelCount = NUM_CHANNELS;
    inputParameters.sampleFormat = PA_SAMPLE_TYPE;
    inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultHighInputLatency ;
    inputParameters.hostApiSpecificStreamInfo = NULL;

    outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
    outputParameters.channelCount = NUM_CHANNELS;
    outputParameters.sampleFormat = PA_SAMPLE_TYPE;
    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultHighOutputLatency;
    outputParameters.hostApiSpecificStreamInfo = NULL;

    printf( "Output device # %d.\n", outputParameters.device );
    printf( "Output device max channels %d.\n", Pa_GetDeviceInfo(outputParameters.device)->maxInputChannels );


    /* -- setup stream -- */
    err = Pa_OpenStream(
              &stream,
              &inputParameters,
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,      /* we won't output out of range samples so don't bother clipping them */
              NULL, /* no callback, use blocking API */
              NULL ); /* no callback, so no callback userData */
    if( err != paNoError ) {printf("OpenErr");goto error;};
    /* -- start stream -- */
    err = Pa_StartStream( stream );
    if( err != paNoError ) goto error;
    printf("Wire on. Will run one minute.\n"); fflush(stdout);
    /* -- Here's the loop where we pass data from input to output -- */
    for( i=0; i<(60*SAMPLE_RATE)/FRAMES_PER_BUFFER; ++i )
    {
        printf("%3d sec passed.\n",i);
       err = Pa_ReadStream( stream, sampleBlock, FRAMES_PER_BUFFER );
       if( err ) goto xrun;

       filterExecute(FRAMES_PER_BUFFER,sampleBlock);

       err = Pa_WriteStream( stream, sampleBlock, FRAMES_PER_BUFFER );
       if( err ) goto xrun;

       //save_text(FRAMES_PER_BUFFER,sampleBlock,fp);

    }   
    printf("Wire off."); fflush(stdout);
    /* -- Now we stop the stream -- */
    err = Pa_StopStream( stream );
    if( err != paNoError ) goto error;
    /* -- don't forget to cleanup! -- */
    err = Pa_CloseStream( stream );
    if( err != paNoError ) goto error;
    Pa_Terminate();

    /* --cleanup buffer --*/
    free( sampleBlock );
    /* --cleanup file pointer */
    fclose(fp);

    return 0;

error: return 1;
xrun:   return 2;
}

void save_text(int N, __int16* wave,FILE *fp){
    int i;
    for(i=0;i<N;i++){
        fprintf(fp, "%d\t %d\n",*(wave+2*i),*(wave+2*i+1));
        //printf("%d\n",*(wave+i));
    }
}
void filterExecute(int N, __int16* wave){
    int i;

    for(i=0;i<N;i++){
        Lch(wave,i) = Lch(wave,i);
        Rch(wave,i) = Rch(wave,i);

    }
}

本来エラー処理時にメモリ解放を書くべきなのですが,省略します.

open時にcallbackにはNULLを指定する.

err = Pa_OpenStream(
              &stream,
              &inputParameters,
              &outputParameters,
              SAMPLE_RATE,
              FRAMES_PER_BUFFER,
              paClipOff,      /* we won't output out of range samples so don't bother clipping them */
              NULL, /* no callback, use blocking API */
              NULL ); /* no callback, so no callback userData */

バッファは16bit整数型を指定する.

#define     PA_SAMPLE_TYPE   paInt16    //16bit

.....

__int16 *sampleBlock;

今回はサンプリングレートが44.1Hz,16bitで読み出すので,上記のようになります.shortでもいいですが,__int16の方がわかりやすいです.(Visual Studio用の型)

forループでバッファに読み書きする.

for( i=0; i<(60*SAMPLE_RATE)/FRAMES_PER_BUFFER; ++i )
    {
       err = Pa_ReadStream( stream, sampleBlock, FRAMES_PER_BUFFER );
       if( err ) goto xrun;

       err = Pa_WriteStream( stream, sampleBlock, FRAMES_PER_BUFFER );
       if( err ) goto xrun;
    }   

Definition

PaError Pa_WriteStream  (
    PaStream *  stream,
    const void *    buffer,
    unsigned long   frames 
)

PaError Pa_WriteStream  (
    PaStream *  stream,
    const void *    buffer,
    unsigned long   frames 
)   

C言語のscanfやprintfに近い書き方で音声の入出力の処理がかけると思います.

 まとめ2

  • 日本語で書かれたドキュメントが少ないの書きました.(単純に需要がないだけなのかもしれません・・)
  • blocking apiとても楽です.

 未実施事項

  • 24bit時の検証
  • 96kHz時の確認
3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3