LECTURE 9 マイク入力を別 PC で再生する(サーバ編)

○ はじめに

前回 に引続き、サーバ側のプログラムを作成します。今回も UDP ソケット通信用システムコールを使っています。簡単には解説していますが、詳細はソケットプログラミング関連の書籍などを参照して下さい。

○ -サーバ- サンプルプログラム

サーバのサンプルプログラム server.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

gcc -o server server.c

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

実行すると、UDP ポートの 13000 番に対して送信された音声パケットを受信してスピーカに出力します。サーバを起動した上で前回作成したクライアントを実行し、クライアント PC でマイクに向かって何か話したり歌ったりしてみて下さい。サーバ PC のスピーカから同じ音が出て来ましたか? 大したプログラムではないですが、自分で作ったプログラムがうまく動いた時の嬉しさを実感できると思います。

--- server.c ---

    1	#include <fcntl.h>
    2	#include <stdio.h>
    3	#include <string.h>
    4	#include <sys/ioctl.h>
    5	#include <sys/types.h>
    6	#include <sys/socket.h>
    7	#include <unistd.h>
    8	#include <netdb.h>            /* gethostbyname() ... */
    9	#include <netinet/in.h>       /* htons() ... */
   10	#include <arpa/inet.h>        /* inet_aton() ... */
   11	#include <linux/soundcard.h>
   12	
   13	
   14	static int setup_dsp( int* dsp  );
   15	static int setup_udp( int* sock, unsigned short port );
   16	
   17	
   18	/*
   19	 * サーバの UDP ポート
   20	 */
   21	#define PORT     13000
   22	
   23	
   24	/*
   25	 * 量子化ビット数 : 16 bits
   26	 * サンプリング周波数 : 44.1 KHz
   27	 * チャンネル数 : 1
   28	 *
   29	 * フラグメントサイズを 100 ms 分の領域
   30	 *
   31	 * 16 (bits) * 44100 (Hz) * 0.1 (sec) = 8820 (bytes)
   32	 *
   33	 * を越えない最大の 2 の巾乗数とする 
   34	 * ( SNDCTL_DSP_SETFRAGMENT の仕様による )
   35	 */
   36	#define FRAGSIZE 8192
   37	
   38	
   39	int 
   40	main( int argc, char** argv )
   41	{
   42		struct sockaddr_in client_addr;
   43		int sock;
   44		int dsp;
   45		int len;
   46		int addr_len;
   47	
   48		unsigned char buf[ FRAGSIZE ];
   49	
   50	
   51		/* /dev/dsp の設定 */
   52		if ( setup_dsp( &dsp ) != 0 ) {
   53			fprintf( stderr, "Setup /dev/dsp failed.\n" );
   54			return 1;
   55		}
   56	
   57		/* UDP ソケットの設定 */
   58		if ( setup_udp( &sock, PORT ) != 0 ) {
   59			fprintf( stderr, "Setup UDP socket failed.\n" );
   60			close( dsp );
   61			return 1;
   62		}
   63	
   64		addr_len = sizeof( struct sockaddr_in );
   65	
   66		/*
   67		 * エラーが起こるか強制終了されるまでループします。
   68		 */
   69		while ( 1 ) {
   70			/*
   71			 * UDP ソケット入力を読み込むまでブロック
   72			 */
   73			if ( ( len = recvfrom( sock, buf, sizeof( buf ), 0, 
   74			       ( struct sockaddr* )&client_addr, &addr_len ) ) == -1 ) {
   75	
   76				perror( "recvfrom()" );
   77				close( dsp  );
   78				close( sock );
   79				return 1;
   80			}
   81	
   82			/*
   83			 * /dev/dsp に書き込む
   84			 */
   85			if ( write( dsp, buf, len ) == -1 ) {
   86				perror( "write()" );
   87				close( dsp  );
   88				close( sock );
   89				return 1;
   90			}
   91		}
   92	
   93	
   94		/*
   95		 * while() ループは終わらないのでここには来ません。。。
   96		 */
   97		close( dsp  );
   98		close( sock );
   99	
  100		return 0;
  101	}
  102	
  103	
  104	/*
  105	 * /dev/dsp を設定する
  106	 */
  107	static int 
  108	setup_dsp( int* dsp )
  109	{
  110		int fmt     = AFMT_S16_LE;
  111		int freq    = 44100;
  112		int channel = 1;
  113	
  114		int bit;
  115		int i;
  116		int fragarg;
  117	
  118	
  119		if ( ( *dsp = open( "/dev/dsp", O_WRONLY ) ) == -1 ) {
  120			perror( "open()" );
  121			return -1;
  122		}
  123	
  124		/* 
  125		 * フラグメントサイズの設定
  126		 *
  127		 * 0xMMMMSSSS ( MMMM はフラグメント数、SSSS は
  128		 * フラグメントサイズ = 2^{SSSS} となる数 )
  129		 *
  130		 * の形式で設定するため、まず SSSS を求める。
  131		 *
  132		 * ( 0 <= SSSS <= 65535, ただしフラグメントサイズ
  133		 *   は最大でも全バッファサイズ / 2 までしか
  134		 *   設定できない )
  135		 *
  136		 * また MMMM は特に制限を設けないため 0x7fff
  137		 * を設定する。
  138		 */
  139		for ( i = 0, bit = 1; i <= 65535; bit <<= 1, i ++ ) {
  140			if ( FRAGSIZE < bit ) {
  141				i = ( i == 0 ) ? 0 : i - 1;
  142				break;
  143			}
  144		}
  145	
  146		fragarg = ( 0x7fff << 16 ) | i;
  147	
  148		if ( ioctl( *dsp, SNDCTL_DSP_SETFRAGMENT, &fragarg )== -1 ) {
  149			perror( "ioctl( SNDCTL_DSP_SETFRAGMENT )" );
  150			close( *dsp );
  151			return -1;
  152		}
  153	
  154		if ( ioctl( *dsp, SOUND_PCM_SETFMT, &fmt ) == -1 ) {
  155			perror( "ioctl( SOUND_PCM_SETFMT )" );
  156			close( *dsp );
  157			return -1;
  158		}
  159	
  160		if ( ioctl( *dsp, SOUND_PCM_WRITE_CHANNELS, &channel ) == -1 ) {
  161			perror( "ioctl( SOUND_PCM_WRITE_CHANNELS )" );
  162			close( *dsp );
  163			return -1;
  164		}
  165	
  166		if ( ioctl( *dsp, SOUND_PCM_WRITE_RATE, &freq ) == -1 ) {
  167			perror( "ioctl( SOUND_PCM_WRITE_RATE )" );
  168			close( *dsp );
  169			return -1;
  170		}
  171	          
  172		return 0;
  173	}
  174	
  175	
  176	/*
  177	 * クライアントとの通信用に UDP ソケットを設定する
  178	 */
  179	static int 
  180	setup_udp( int* sock, unsigned short port )
  181	{
  182		struct sockaddr_in server_addr;
  183	
  184		if ( ( *sock = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 ) {
  185			perror( "socket()" );
  186			return -1;
  187		}
  188	
  189		memset( &server_addr, 0, sizeof( struct sockaddr_in ) );
  190	
  191		server_addr.sin_family      = AF_INET;
  192		server_addr.sin_addr.s_addr = INADDR_ANY;
  193		server_addr.sin_port        = htons( port );
  194	
  195		if ( bind( *sock, ( struct sockaddr* )&server_addr, 
  196			   sizeof( struct sockaddr_in ) ) == -1 ) {
  197	
  198			perror( "bind()" );
  199			close( *sock );
  200			return -1;
  201		}
  202	
  203		return 0;
  204	}
  205	
  206	
  207	/* End of server.c */


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

では、サンプルプログラムについて説明していきます。

  • 14 〜 15 行目

    自作内部関数のプロトタイプ宣言です。setup_dsp() は /dev/dsp の設定を行います。setup_udp() は、 クライアントとの通信を行うための UDP ソケットの設定を行います。

  • 18 〜 21 行目

    クライアントからの音声を受信するための UDP ポート番号を定義しています。 この値はクライアントの値と一致していてかつ他のソフトウェアから使用されていなければ、 どんな値でも構いません。

  • 24 〜 36 行目

    前回 の client.c の FRAGSIZE と同じ値の fragment サイズを定義しています。16bits、44.1 KHz、100ms 分のバッファサイズです。

  • 39 〜 101 行目 ( main() )

    main() 関数では、まず /dev/dsp の設定 ( setup_dsp() ) および UDP ソケットの設定 ( setup_udp() ) を行った後に、受信した音声パケットを /dev/dsp に書き込む while() ループを実行します。

    while() ループでは、まず UDP ソケットから FRAGSIZE 分のデータを recvfrom() で読み込み、 読み込んだパケットを write() で /dev/dsp に書き込んでいます。この書き込みにより、 音声がスピーカから出力されます。

      #include <sys/types.h>
      #include <sys/socket.h>
    
      int  recvfrom( int  s, void* buf, size_t len, int flags, 
                     struct sockaddr* from, socklen_t* fromlen );
    
      s       : socket() システムコールで作成したソケット
      buf     : 受信データ格納用バッファへのポインタ
      len     : 受信するデータ長
      flags   : ソケット通信用フラグ。詳細はマニュアル参照。ここでは使用しないため 0 を設定
      from    : 送信元情報へのポインタ(送信元情報が格納される)
      fromlen : from の実データの大きさ
    
    

    エラーが発生した場合は -1 が返され、それ以外は受信したデータ長が返されます。

  • 104 〜 173 行目 ( setup_dsp() )

    /dev/dsp の設定を行っています。前回 の client.c の setup_dsp() と一ヶ所を除いて全く同じ内容です。唯一、119 行目で /dev/dsp を O_WRONLY ( 書き込みのみ ) モードで開いている点が異なります。

  • 176 〜 204 行目 ( setup_udp() )

    クライアントとの通信に使用する、UDP ソケットの設定を行っています。

    184 〜 187 行目では、socket() システムコールによって通信に使用するソケットの準備を行っています。

      #include <sys/types.h>
      #include <sys/socket.h>
    
      int socket( int domain, int type, int protocol );
    
      domain   : IP 通信の場合は PF_INET もしくは AF_INET を指定する
      type     : UDP の場合は SOCK_DGRAM を指定する
      protocol : ここでは 0 を指定して下さい
    
    

    成功すると、準備されたソケットを参照するディスクリプタが返されます。 以後はこの値を使用してソケットを指定します。エラーが発生した場合は -1 が返されます。

    191 〜 193 行目では、自分の情報を struct sockaddr_in 型構造体に格納しています。 後述の bind() システムコールで使用します。

    191 行目でクライアントとの通信に用いるプロトコルを ( IP の場合は PF_INET もしくは AF_INET )、 192 行目でパケットの受信待ちを行う IP アドレスを設定 ( INADDR_ANY を指定することにより、 実際のアドレス値の設定を OS に依頼している ) し、193 行目でポート番号を設定しています。 ポート番号については、ネットワークバイトオーダという形式で数値を格納する必要がありますので、 unsisgned short 型の数値をネットワークバイトオーダに変換する htons() 関数を使っています。

    195 〜 201 行目では bind() システムコールを使って、作成したソケットにアドレスを設定しています。 これにより、OS が該当アドレス:ポートに対して送信されたパケットの受信を開始してくれます。

      #include <sys/types.h>
      #include <sys/socket.h>
    
      int bind( int sockfd, struct sockaddr* my_addr, socklen_t addrlen );
    
      sockfd  : 作成したソケット
      my_addr : ソケットに設定するアドレス情報
      addrlen : my_addr のデータ長
    
    

    成功すると 0 を返し、エラーが発生した場合には -1 を返します。


server.c の説明は以上です。サーバ実行後にクライアントを実行して、クライアントのマイクに入力した音声がサーバのスピーカから聞こえて来る感動をぜひ味わってみて下さい!