LECTURE 4 マイク入力をスピーカーに出力する

○ はじめに

LECTURE 2 では、スピーカに音を出力する方法について解説しました。ここでは新たにマイク入力を録音するための方法について説明します。スピーカー出力では write() システムコールを使いましたが、マイク入力を扱うためには read() システムコールを使います。

○ サンプルプログラム

5 秒間のマイク入力を録音し、続けてその内容をスピーカーに出力するサンプルプログラム mic.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

gcc -o mic mic.c

としてコンパイルします。コンパイルオプションは各自の環境などに応じて修正して下さい。

実行すると、特にエラーが無ければ

########################################
# Now speak to your mic for 5 seconds. #
########################################

と表示されるので、約 5 秒間程マイクに向かって何か音をふきこんで下さい。録音時間が過ぎると

########################################
# Playing what you have said.          #
########################################

という表示に続いて先程ふきこんだ音が再生されます。

音が出ない場合の注意点として 2 点あります。まず、スピーカー出力と同様にマイク入力のボリュームが 0 になっていない必要があります。ミキサー操作用ソフトを使うか、LECTURE 7 /dev/mixer を操作する に従ってボリュームを設定して下さい。

次に、OSS ( /Free )では入力音源としてマイク入力がデフォルトになっているはずですが、ひょっとすると入力音源としてライン入力など別の音源が選択されている可能性があります。これについても、ミキサー操作用ソフトを使うか LECTURE 7 /dev/mixer を操作する に従って入力音源をマイク入力に設定して下さい ( 以上、これ以降のサンプルでも同様 )。

ちなみに、ミキサーを操作して入力音源を変えると、その音源を全く同じサンプルコードで録音することができます。

--- mic.c ---

    1	#include <fcntl.h>
    2	#include <linux/soundcard.h>
    3	#include <stdio.h>
    4	#include <sys/ioctl.h>
    5	#include <unistd.h>
    6	
    7	
    8	/*
    9	 * 量子化ビット数 : 16 bits
   10	 * サンプリング周波数 : 44.1 KHz
   11	 * チャンネル数 : 1
   12	 * 録音/再生時間 : 5 sec
   13	 *
   14	 * 分のバッファ領域
   15	 *
   16	 * 16 (bits) * 44100 (Hz) * 5 (sec) = 3528000 (bits)
   17	 *                                  = 441000  (bytes)
   18	 */
   19	#define BUFSIZE 441000
   20	
   21	static int setup_dsp( int fd );
   22	
   23	int 
   24	main( void )
   25	{
   26		short buf[ BUFSIZE / sizeof(short) ];
   27		int   fd, rtn;
   28	
   29		if ( ( fd = open( "/dev/dsp", O_RDWR ) ) == -1 ) {
   30			perror( "open()" );
   31			return 1;
   32		}
   33	
   34		/* /dev/dsp の設定 */
   35		if ( setup_dsp( fd ) != 0 ) {
   36			fprintf( stderr, "Setup /dev/dsp failed.\n" );
   37			close( fd );
   38			return 1;
   39		}
   40	
   41	
   42		printf( "########################################\n" );
   43		printf( "# Now speak to your mic for 5 seconds. #\n" );
   44		printf( "########################################\n" );
   45	
   46		/* 5 秒間 マイク入力を録音する */
   47		if ( ( rtn = read( fd, buf, BUFSIZE ) ) == -1 ) {
   48			perror( "read()" );
   49			close( fd );
   50			return 1;
   51		}
   52	
   53	
   54		printf( "\n" );
   55		printf( "########################################\n" );
   56		printf( "# Playing what you have said.          #\n" );
   57		printf( "########################################\n" );
   58	
   59		/* 録音した音を再生する */
   60		if ( write( fd, buf, rtn ) == -1 ) {
   61			perror( "write()" );
   62			close( fd );
   63			return 1;
   64		}
   65	
   66		close( fd );
   67		return 0;
   68	}
   69	
   70	
   71	/*
   72	 * /dev/dsp を以下の様に設定する。
   73	 *
   74	 * 量子化ビット数     : 16   bits
   75	 * サンプリング周波数 : 44.1 KHz
   76	 * チャンネル数       : 1
   77	 * PCM データは符号付き、リトルエンディアン
   78	 *
   79	 */
   80	static int 
   81	setup_dsp( int fd )
   82	{
   83		int fmt     = AFMT_S16_LE;
   84		int freq    = 44100;
   85		int channel = 1;
   86	
   87		/* サウンドフォーマットの設定 */
   88		if ( ioctl( fd, SOUND_PCM_SETFMT, &fmt ) == -1 ) {
   89			perror( "ioctl( SOUND_PCM_SETFMT )" );
   90			return -1;
   91		}
   92	
   93		/* チャンネル数の設定 */
   94		if ( ioctl( fd, SOUND_PCM_WRITE_CHANNELS, &channel ) == -1 ) {
   95			perror( "ioctl( SOUND_PCM_WRITE_CHANNELS )" );
   96			return -1;
   97		}
   98	
   99		/* サンプリング周波数の設定 */
  100		if ( ioctl( fd, SOUND_PCM_WRITE_RATE, &freq ) == -1 ) {
  101			perror( "ioctl( SOUND_PCM_WRITE_RATE )" );
  102			return -1;
  103		}
  104	
  105		return 0;
  106	}
  107	
  108	/* End of mic.c */


○ サンプルプログラムの説明

では、サンプルプログラムについて説明していきます。といっても、ほとんどの部分は LECTURE 2 の sound.c で解説したものばかりですが。。。

  • 8 〜 19 行目

    PCM データを格納するために必要なバッファ領域のサイズです。LECTURE 2 の sound.c のそれと全く同じ大きさです。量子化ビット数 16 ビット、サンプリング周波数 44.1 KHz、 チャンネル数 1 という PCM データを 5 秒間扱うので、 16 ( bits ) * 44100 (Hz) * 5 (sec) = 441000 (bytes) のバッファを使います。

    2007/12/03 追記。34 行目で実際にバッファを確保していますが、short 型で確保しているにも関わらず BUFSIZE をそのまま要素数に指定していました (short buf[BUFSIZE];)。 正しくは short buf[BUFSIZE / sizeof(short)]; でした。堀さん、ご指摘ありがとうございました!

  • 23 〜 68 行目 ( main() )
    • 29 〜 32 行目

      mic.c ではマイク入力とスピーカー出力の両方を行うので、/dev/dsp を O_RDWR ( 読み書き両用 ) で開いています。

    • 34 〜 39 行目

      自作関数 setup_dsp() で /dev/dsp の設定を行っています。LECTURE 2 の setup_dsp() と全く同じ処理をしているので、そちらの解説を参照して下さい。

    • 42 〜 51 行目

      /dev/dsp を read() することでマイク入力を録音することが出来ます。録音した PCM データは buf に格納されます。

      ここで注意しておいて欲しいことがあります。サウンドドライバから PCM データを読み込む際、 サウンドドライバ内部のフラグメントと呼ばれるバッファが、 最低一つはデータで埋められなければ read() はブロックします。

      これでは、フラグメントのサイズより小さなデータを読み込みたい場合に不要にブロック してしまいます。これを避けるには、フラグメントのサイズを変更したり、 ブロックせずに読み込むことのできるフラグメントの数を取得したりする必要があります。 これらの方法については LECTURE 5 リアルタイム性を考慮する で紹介しますが、ここではこの様な問題があることを覚えておいて下さい。

    • 54 〜 64 行目

      sound.c と同じく、buf の内容を /dev/dsp に write() することでスピーカーに 出力しています。

  • 71 〜 106 行目 ( setup_dsp() )

    /dev/dsp の設定を行うための関数です。LECTURE 2 の setup_dsp() と全く同じなので、 詳細はそちらの解説を参照して下さい。


mic.c の説明は以上です。OSS API を使ったプログラミングでは、マイク入力を録音するには read() するだけで良いのです。これまたとても簡単ですね。

○ サウンドの録音と再生を同時に行う

mic.c では、read() でマイク入力を録音した後に write() で録音したデータを再生しています。この場合は、再生している間は入力音源を取り込む必要は無いので問題ありません。

しかし、write() で再生している間も入力音源をドライバ内のバッファに取り込んでおいて欲しいといった場合は、サウンドカードが全二重モードをサポートしている必要があります。使用しているデバイスが全二重モードをサポートしているかどうかは、LECTURE 3 /dev/dsp の機能を調べる で紹介している ioctl SNDCTL_DSP_GETCAPS コマンドで調べることができます。

int caps;
ioctl( fd, SNDCTL_DSP_GETCAPS, &caps );

上の ioctl() を発行して得られる caps と DSP_CAP_DUPLEX の AND の結果が 0 でなければ、そのデバイスは全二重モードをサポートしていることになります。

デバイスを使用する時に全二重モードに移行するには、デバイスを O_RDWR モードで open() した直後に下記の ioctl() で設定します。

ioctl( fd, SNDCTL_DSP_SETDUPLEX, 0 );

この ioctl() はデバイスを open() した直後、他の ioctl() や read()、write() などを実行する前に行います。この ioctl() によってデバイスは全二重モードで使用できます。

SNDCTL_DSP_GETCAPS コマンドを実行する際は、まず SNDCTL_DSP_SETDUPLEX コマンドを実行するべきだと OSS プログラマーズガイドに記述されています。そうしないと、SNDCTL_DSP_GETCAPS コマンドが DSP_CAP_DUPLEX のビットは設定されていないと報告するかもしれないからということです。

ということなんですがいくつかのドライバのコードを見たところ、SNDCTL_DSP_SETDUPLEX が発行された場合、既に設定されているフラグと比較して全二重モードであれば成功とみなすとか、最初から全二重モードになっているのでこのコマンド発行時には何もしないとか、全二重サポートしてないからエラーを返すといった、サポート状況表示用みたいなコードが多い様です ( 全て見たわけではありません )。