mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

raspberry pi ALSAを使ったサウンド出力

昨日は、シリアル通信が正しくできているかどうかを確かめることができました。
結果としてRubyのserialportを使ったシリアル受信処理に問題があることが分かったので万万歳です。
※原因がわかれば後は追っかけていくだけだ〜!

ということで、今日はシリアル通信側はいったん置いといてRaspberry piのサウンド出力の
プログラミングについて調べてみました。

Linuxでサウンド出力を行うプログラミングについては全く知識がなかったのですが、
ALSA(Advanced Linux Sound Architecture)というライブラリ(ミドルウェア相当でしょうか!?)
がありALSAAPIを使ってサウンド出力のプログラミングができるらしいです。

raspberry pi(raspbian)にはaplayというwavファイルを再生するコマンドが入っているのですが、
このコマンドはALSAを使っているようです。

■Rapberry pi上でのaplayを使ったサウンド出力テスト
※Rapberrypi本体のイヤホンジャックにスピーカーもしくは、ヘッドホンをつないだ状態で以下のコマンドを
実行します。


$sudo aplay /usr/share/sounds/alsa/Front_Center.wav
正しく動くと、英語で「フロント...センター」という声が再生されます。
※デバイスを扱う関係からだと思いますが一般ユーザ権限では動きませんでした。

さて音がでるのがわかったのですが、私がやりたいのは任意の周波数の音をスピーカー
から出力したいというものです。

いろいろネットで調べていて、
C program to play a WAV file
ALSAの叩き方
のページを参考にさせて頂き、無事音を出すことができました。

まずはALSAの開発環境をインストールします。


$sudo apt-get install libasound2 libasound2-dev

続いて動作確認用コードを書き、コンパイルします。
※とりあえずALSAの叩き方に書かれていたコードを流用させて頂きました。

実行すると、

「プー」という音がちゃんとでました!

音の出力部分をうまくライブラリ化して、センサとの入力と繋げれば
私のやりたかったテルミンもどきができそうです!

今日はALSAの方のコードをもう少しいじってから寝ようと思います。

ちなみに、サウンドプログラミングについてはあまり書籍などは出ていないようです。
OS毎にALSADirectXのようなライブラリ、ミドルウェアが異なるためニッチな内容になってしまうからかもしれません。

ALSAC言語でコーディングできますが、サウンドプログラミング入門――音響合成の基本とC言語
による実装という本を買ってみました。

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

サウンドプログラミング入門――音響合成の基本とC言語による実装 (Software Design plus)

が、タイトルにもあるように音響合成について主に触れられており内容ははっきり言って今の自分には難しかったです。。。
興味のある方は読んでみられるとよいかもしれません。(もう少しだけ初心者よりの本があれば買いたいです。)

追記(2018-02-12)

パソコンのデータを整理していたら、当時遊んでいたコードが出てきましたのであげておきたいと思います。

#include <stdlib.h>
#include <stdint.h>
#include <math.h>
#include <time.h>
#include <alsa/asoundlib.h>

// 現在時刻を返す
double getTime() {
  struct timespec time_spec;
  clock_getres( CLOCK_REALTIME, &time_spec );
  return (double)( time_spec.tv_sec ) + (double)( time_spec.tv_nsec ) * 0.000001;
}

// 指定された時刻までスリープ
void waitUntil( double _until ) {
  struct timespec until;
  until.tv_sec = (time_t)( _until );
  until.tv_nsec = (long)( ( _until - (time_t)( _until ) ) * 1000000.0 );
  clock_nanosleep( CLOCK_REALTIME, TIMER_ABSTIME, &until, NULL );
}

#define OCTAVE_NUM          ( 2 )                                               // オクターブ数
#define SCALE_TONE_NUM      ( 7 )                                               // 1スケールにおける音の数
#define BUFF_SIZE_MAX       ( 200 )                                             // 1サンプリング周期内のバッファサイズ
static const unsigned int toneTbl[ OCTAVE_NUM ][ SCALE_TONE_NUM ] = {
//    C         D       E       F       G       A       B
    { 261,      294,    329,    349,    392,    440,    493 },
    { 523,      587,    659,    698,    784,    880,    987 },
};


// =============================================================================
// main
// =============================================================================
int main( int argc, char **argv ) {
    char device[] = "hw:0";
    // 符号付き16bit
    snd_pcm_format_t format = SND_PCM_FORMAT_S16_LE;

    // snd_pcm_readi/snd_pcm_writeiを使って読み書きする
    snd_pcm_access_t access = SND_PCM_ACCESS_RW_INTERLEAVED;
    // 再生周波数 48kHz
    unsigned int sampling_rate = 48000;
    // モノラル
    static unsigned int channels = 2;
    // ALSAがサウンドカードの都合に合わせて勝手に再生周波数を変更することを許す
    unsigned int soft_resample = 1;

    // 20ミリ秒分ALSAのバッファに蓄える
    unsigned int latency = 20000;
    unsigned int tone_freq = 700;
    unsigned int length = 1;
    unsigned int buffer_size;
    int16_t buffer[ BUFF_SIZE_MAX ];
    

    int ret;
    snd_pcm_t *pcm;
    int i;

    // 再生用PCMストリームを開く
    ret = snd_pcm_open( &pcm, device, SND_PCM_STREAM_PLAYBACK, 0 );
    if ( ret < 0 ) {
        fprintf( stderr, "pcm open failed:[%d]\n", ret );
        return -1;
    }

    // 再生周波数、フォーマット、バッファのサイズ等を指定する
    ret = snd_pcm_set_params( pcm, format, access, channels, sampling_rate, soft_resample, latency );
    if ( ret < 0 ) {
        fprintf( stderr, "pcm set failed:[%d]\n", ret );
        snd_pcm_close( pcm );
        return -1;
    }

    // =====================================================
    // 再生するためのサイン波を作っておく
    // =====================================================
    buffer_size = sampling_rate / tone_freq;
    for( i = 0; i < buffer_size; i++ ) {
        buffer[ i ] = sin( 2.0 * M_PI * i / buffer_size ) * 32767;
    }

    double system_time = getTime();
    for( i = 0; i < tone_freq * length; i++ ) {
        // 1周期書き出し
        ret = snd_pcm_writei ( pcm, ( const void* )buffer, buffer_size );
        if( ret < 0 ) {
            // バッファアンダーラン
            ret = snd_pcm_recover( pcm, ret, 0 );
            if ( ret < 0 ) {
                fprintf( stderr, "Unable to recover this stream.\n" );
                snd_pcm_close( pcm );
                return -1;
            }
        }

        // 少し待ってからまた1周期分書き出す
        waitUntil( system_time + (double)buffer_size / 48000.0f );
        system_time = getTime();
    }

    snd_pcm_close( pcm );
    return 0;
}