SSブログ

SDカードのWAVファイル再生する。 [Arduino]

SDカードに入ったWAVファイルを再生してみたいと思います。
sd_wav.jpg

MAKE Japan シンプルなWAV再生用のArduinoライブラリ
http://jp.makezine.com/blog/2009/02/arduino_library_for_simple_wav_play.html

こんなのがあったのですが、専用のライブラリを使うみたいで、SDカードのライブラリもArduino標準のものではないのでライブラリを登録しないといけないっぽい。

そこで、以前に購入してあった「エレキジャックBASIC No.1」

エレキジャックBASIC No.1 - サポート・ページ
http://www.eleki-jack.com/support/2010/08/basic-no1.html
(AVRマイコン活用)手軽に試せる マイコン一つだけで作る簡易WAVEプレーヤ

を参考にしました。
アイデアとしては、表と裏のバッファがあり、片方を再生中に、もう片方にSDカードから読み込むという感じ。
このエレキジャックの記事のプログラムは生のAVRマイコンに書き込むというものでしたが、これをArduinoで使えるようなスケッチにしてみました。SDカードライブラリもArduino標準のものを使用します。
形にするまでには数々の問題をクリアしていきました。

■ 問題 その1:出力ピン

Timer0 はArduinoのシステムで使用されているので、Timer1 か Timer2 を使うことになるのですが、8ビットで十分なのでTimer2を使用。OC2A (PB3) は SDカードのMOSIに使われてしまっているので、OC2B (PD3) (Arduino の Digital 3番)をスピーカーにつなぐことにしました。
ちなみにGNDはArduino のICSP 端子の6番からとりました。

sd_wav_icsp.jpg

■ 問題 その2:ファイルの読み込み

Arduino の SDライブラリでは、readは1バイト毎になっています。

Arduino - FileRead
http://arduino.cc/en/Reference/FileRead

これでは、タイムロスが大きすぎるような感じですが、「File.cpp」 や 「SdFile.cpp」 をみてみると、
「int File::read(void *buf, uint16_t nbyte)」 とか、「int16_t SdFile::read(void* buf, uint16_t nbyte)」 とか書いてあり、読み込み用の配列と、何バイト読み込むかを指定して、読み込めたバイト数を返すということもできるみたいでした。

■ 問題 その3:メモリー消費の問題

どうやら ArduinoのSDライブラリがかなりSRAMを消費するようで、読み込みのバッファが多いとメモリが足りなくなっているようでした。
動かない原因を探るのに、ここでかなり時間を消費しました。
バッファサイズを512バイト(表裏で1kバイト)より減らす必要があります。
他のSDカードのライブラリでは書き込みをしないかわりにメモリ使用量を減らすというものもあるようですが、Arduino標準のSDカードライブラリではそのような方法を見つけられませんでした。

■ 問題 その4:SDカード読み込みの遅さ

問題3のこともありバッファサイズを減らすと、今度は裏ページにデータを読み込み終わるまでに再生中のページを再生しつくしてしまうということがありました。
一応class 10のSDカードでも試してみましたが、384バイトくらいがちょうどよさそうです。

■ 問題 その5:再生周波数、チャンネル(モノラル/ステレオ)、ビット数(8ビット/16ビット)

一般的なArduinoは16MHzで 256 clock毎に割り込むので
 16,000,000 Hz / 256 = 62,500 = 62.5 kHz
となります。
62.5 kHzをだいたい64kHzと見なすと扱いやすいかと思います。
 64kHz / 8 bit / Mono
 32kHz / 8 bit / Stereo
これ(512kbps)なら、そのまま再生可能。
16ビットにしても、音質の向上はできず、下位8ビットを間引くことになりますが、それだけSDカードからデータを高速に読み込む必要になります。しかし、問題4と同様に、データを捨てられるほど読み込みが速くはなく、ほとんど限界です。
そのため16ビットではモノラルなら周波数を32kHz以下(ステレオならさらに半減)にしないといけないだろうと思われます。
また 16ビットの場合には符号付き整数 (-32768 ~ 32767) となるので、試してはいませんが、上位8ビットのみを使用し、B10000000 と XOR をとればいいかと思われます。そしてサンプリングのデータを32kHzなら2回、16kHz なら4回再生する必要があると思います。
 32kHz / 16 bit / Mono / データ損失あり、処理必要、1データにつき2回再生
 16kHz / 16 bit / Mono / データ損失あり、処理必要、1データにつき4回再生
 16kHz / 16 bit / Stereo / データ損失あり、処理必要、1データにつき2回再生
あるいは、音質が大幅に落ちるのを覚悟して、
TCCR2B = _BV(CS21); // 8分周
とすると、約8kHz (正確には7.8125kHz)での再生となるので、
 8kHz / 8 bit / Mono
 4kHz / 8 bit / Stereo
としてデータ量を減らす(512 → 64kbps)ことも可能かと思われます。
11025 , 22050, 44100, 48000 Hz といったデータは勘弁してください。

■ 問題 その6:データの作成

ボクが使っているソフト「Roxio Creator 2010」では、任意の周波数は指定できず、48kHzが最大でした。そのため 32kHz / 8 bit / Stereo としデータを作成しました。
これはそれほど問題ではないのですが、ブログに載せるにあたって、著作権フリーかつ無料の音声データを探しましたが予想外に苦労しました。こんなに無いものかとは思いませんでした。

クラシック名曲サウンドライブラリー
http://andotowa.quu.cc/

こちらを利用させていただきました。ありがとうございます。

さて、スケッチです。肝心の部分は、エレキジャックBASICがもとになっています。
// とりあえず、SDカードのWAVファイルを再生してみる。
// 16MHzのArduino で 256 clock毎に割り込むので 62.5kHz(8bit Monoral)が理想だが
// ボクの持っているソフトが対応していないので、32kHz(8bit Stereo)で代用
#include <SD.h>

#define BUF_SIZE 384          // バッファ・サイズ

volatile uint8_t              // グローバル変数
    buf[2][BUF_SIZE],           // バッファ
    buf_page,                   // バッファ・ページ
    buf_flg;                    // バッファ読み込みフラグ
volatile uint16_t buf_index;    // バッファ位置
volatile uint16_t read_size[2]; // バッファ読み込みサイズ

void setup() {
  pinMode(10, OUTPUT);                             // SDライブラリ使用時のお約束
  while( !SD.begin(10) );                          // ライブラリとSDカードを初期化
  // PWM初期化
  DDRD  |=  B00001000;                             // PD3 (OC2B) (Arduino D3)
  TCCR2A = _BV(COM2B1) | _BV(WGM21) | _BV(WGM20);  // 8bit高速PWM
  TCCR2B = _BV(CS20);                              // 分周なし
}

void loop() {
  File dataFile;
  if ( !(dataFile = SD.open( "hoge.wav" )) )  return;   // error opening wavefile
  
  buf_index = 44;  buf_page = 0;  buf_flg = 1;    // パラメータ設定 (44byteはヘッダ)  
  read_size[buf_page] = dataFile.read( (uint8_t*)buf[buf_page], BUF_SIZE );
  TIMSK2 |= _BV(TOIE2);
  while(TIMSK2 & _BV(TOIE2)) {
    if(buf_flg) {     // データ読み込み指令のフラグが立ったら読み込む
      read_size[buf_page ^ 1] = dataFile.read( (uint8_t*)buf[buf_page ^ 1], BUF_SIZE );
      buf_flg = 0;    // 読み込んだらフラグを下ろす
    }
  }
  OCR2B = 0;
  dataFile.close();
  delay(5000);
}

ISR(TIMER2_OVF_vect) {
  OCR2B = buf[buf_page][buf_index++];                  // データをPWMとして出力
  if(buf_index == read_size[buf_page]) {               // 現在のバッファの最後まで来たら...
    if(buf_index != BUF_SIZE)  TIMSK2 &= ~_BV(TOIE2);    // ファイルの最後なら,TOIE2をクリア
    buf_index = 0;  buf_page ^= 1;  buf_flg = 1;         // バッファを切り替え
  }
}

再生の様子はこちら。

ArduinoとSDカードシールドとスピーカだけでWAVファイル再生できました。
(USBケーブルは電源をとっているだけです。)

エレキジャックベーシック 2010年 09月号 [雑誌]

エレキジャックベーシック 2010年 09月号 [雑誌]

  • 作者:
  • 出版社/メーカー: CQ出版
  • 発売日: 2010/08/25
  • メディア: 雑誌

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。