Rust
PortAudio
サウンドプログラミング

初めてのサウンドプログラミング③~Rustでマイク録音する~

前回のお話はこちら

全然続きじゃないです。


動機

マイク録音を今までC++のopenframeworksでやっていましたが、OSXのアップデートでSDKがどこかに消えました。C++離れしたかったし解決法を探すのもだるかったので。

自分も初心者なので、ソースコードの改善点があればぜひ指摘いただけるとありがたいです。


こんな人に読んでほしい


  • サウンドプログラミングに興味がある人

  • Rustでなんかやってみたい人

  • Rust版portaudioを触ろうとしてる人


環境

OS=> mac Mojave

Rust_version=> 1.30.0


要件


  • mac book pro標準のマイクで入力した音をwaveファイルとして保存する。

  • できればクロスプラットフォームがいい。

  • openframeworksみたいなSDKない事件はもうやだ。


使用クレート


hound

wavファイル書き出し、読み出しをする

portaudio

音の入出力に関するハードウェアの差異を吸収してくれる


portaudioを使う前に

macの場合は一手間必要みたいです。

brew install portaudio

brew install pkg-config


ソースコード

portaudioご本家のサンプルコードをほぼパクっていると言っても過言ではない気がします...。が、ご本家のRust版サンプルコードにInputのサンプルコードがなかったので載せていきます。

簡単の為にmain.rsファイル一個にしています。


extern crate hound;
extern crate portaudio;
use portaudio as pa;
use std::i16;
use std::sync::mpsc;

const SAMPLE_RATE: f64 = 44_100.0; //サンプリングレート
const CHANNELS: i32 = 2; //チャンネル数(1->モノラル, 2->ステレオ)
const INTERLEAVED: bool = true; //謎
const FRAMES_PER_BUFFER: u32 = 64; //フレームごとのバッファ数
const BITS_PER_SAMPLE :u16 = 16; //量子化ビット数
const NUM_SECONDS: i32 = 10; //録音秒数
const BUF_SIZE: usize = SAMPLE_RATE as usize * NUM_SECONDS as usize; //保存するバッファサイズ

fn main() {
match run() {
Ok(_) => {},
e => {
eprintln!("Example failed with the following: {:?}", e);
}
}
}

fn run() -> Result<(), pa::Error> {
//初期化
let pa = try!(pa::PortAudio::new());

println!("PortAudio:");
println!("version: {}", pa.version());
println!("version text: {:?}", pa.version_text());
println!("host count: {}", try!(pa.host_api_count()));

let default_host = try!(pa.default_host_api());
println!("default host: {:#?}", pa.host_api_info(default_host));

//入力デバイスのセット
let def_input = try!(pa.default_input_device());
let input_info = try!(pa.device_info(def_input));
println!("Default input device info: {:#?}", &input_info);
//レイテンシ設定
let latency = input_info.default_low_input_latency;
//インプットパラメータ設定
let input_params = pa::StreamParameters::<f32>::new(def_input, CHANNELS, INTERLEAVED, latency);

//ストリームフォーマットがサポートされてるかチェック
try!(pa.is_input_format_supported(input_params, SAMPLE_RATE));

//インプットストリームのセッティング
let settings = try!(pa.default_input_stream_settings(CHANNELS, SAMPLE_RATE, FRAMES_PER_BUFFER));
let mut counter = 0; //現在のバッファサイズカウント(実際はステレオだとこの数の倍ですが)
let (tx, rx) = mpsc::channel();
//ノンブロッキングストリーム方式なのでコールバックを定義しておく。
let callback = move |pa::InputStreamCallbackArgs {buffer, frames, .. }| {
//ステレオの場合は以下のインデックス指定でbufferを取り出す。
//モノラルの場合は素直にfor i in 0..framesで一つづつbufferを取り出す。
let mut idx = 0;
let mut data: Vec<f32> = vec![];
for _ in 0..frames {
//指定バッファサイズより大きかったら終了
if counter >= BUF_SIZE{
break;
}
        data.push(buffer[idx]);
data.push(buffer[idx+1]);
  counter += 1;
  idx += 2;
}
tx.send(data).ok(); //バッファを書き出し処理に投げる
if counter >= BUF_SIZE {
println!("complete rec.");
pa::Complete
}else {
pa::Continue
}
};
//ストリームセット
let mut stream = try!(pa.open_non_blocking_stream(settings, callback));

//wav書き出し設定
let spec = hound::WavSpec {
channels: CHANNELS as u16, //チャンネル数(1->モノラル, 2->ステレオ)
sample_rate: SAMPLE_RATE as u32, //サンプリングレート
bits_per_sample: BITS_PER_SAMPLE, //量子化ビット数
sample_format: hound::SampleFormat::Int, //インテジャーPCM
};
let mut writer = hound::WavWriter::create(/*好きなファイルパス名->*/"sine.wav", spec).unwrap();
//ストリーム開始
try!(stream.start());
println!("play {} seconds..", NUM_SECONDS);
//ストリームがアクティブのとき回り続ける
while let true = try!(stream.is_active()) {
while let Ok(data) = rx.try_recv() {
//bufferをファイル書き出し
for d in &data {
let amplitude = i16::MAX as f32; //振幅(音量)を調整
writer.write_sample((d * amplitude) as i16).unwrap(); //書き出し
}
}
}
try!(stream.stop());
try!(stream.close());
writer.finalize().unwrap();
println!("finished.");
Ok(())
}


ポイント

portaudioについてですが、だいたいInputとOutputで使い方はそんなに変わらないようです。

公式のOutputソースコードを真似ればできる感じですね。

houndの方は公式のソースコードほぼ丸パクリですね。

結局ほぼ自分書いてないやんっていう。

ライブラリの作者様が素晴らしいってことですよ。ウンウン。


参考文献とかリンクとか


終わりに

ここまで組むのにやたら苦戦しましたが、結局のところサウンド系のバッファ処理の設計方法を全く知らなかったのが問題でした。上記でご紹介した本でサクッと文系でもわかるくらい簡単に説明してありますので、基礎知識が欲しい人はぜひ読んでください。

今日はここまでです。ありがとうございました!