まとめ
- callback関数を使いません
- c言語のscanf関数やprintf関数に似た記述になります.
なぜBlocking APIなのか
PortAudioを使ってASIOドライバから音声を入出力をしようとすると,通常callback関数を使ってデータを受け渡しします.しかし,c++でcallback関数を使うと,発火する(callback関数が呼び出される)スレッドと,main関数のスレッドが違うため,データを受け渡しするために,共有メモリを用意したり特殊なデータ処理が必要になります.これはcallback処理に詳しい方ならば,実装できると思いますが,音響処理のビギナーにとってはハードルが高いです.特に大学の授業でcallback処理を扱う事は無いので,理解がしにくいと思います.
そこで,PortAudioではcallbackを使わずにデータを受け渡す,blocking r/w functionというものがあります.
普通にPortAudioのドキュメントに書いてあるのですが,英語だけで日本語で書かれたものがないので,なかなか使う人がいないような気がします.
準備
- DLL/Libファイルは各自コンパイルしてください.(URL参考)
- Hファイルとlibファイルをプロジェクトに使いしてください.
- https://qiita.com/nicolai_twi/items/db77bfe802d959c14ba4#_reference-3ca3d793553fd56d1398
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時の確認