rust-portaudioで録音をする
はじめに
rust-portaudioは、C言語で書かれたportaudioというマイク/スピーカーを用いて入出力をするソフトウェアのラッパーです。
[参考]
- Docs
https://docs.rs/portaudio/latest/portaudio/struct.Devices.html - Github
https://github.com/RustAudio/rust-portaudio
まずportaudioを入れます。
brew install portaudio
次にcargo newをします。
cargo new rustaudio
cd rustaudio
Cargo.tomlに、クレートとバージョンを追記します。
[dependencies]
portaudio = "*"
これで準備ができました。
BlockingとNonbloking
-
Blocking
https://github.com/RustAudio/rust-portaudio/blob/master/examples/blocking.rs -
Non Blocking
https://github.com/RustAudio/rust-portaudio/blob/master/examples/non_blocking.rs
Blockingは、処理を止めて、録音をし、処理をして...の繰り返しです。
NonBlockingは、処理と録音を同時並行で行うものになります。
音は常に流れ続けるので、重い処理などを挟む・尚且つ録音に切れ目を生じさせたくない場合は、Nonblockingを使います。今回は、シンプルなBlockingで試してみます。
blocking.rsを録音用に改変
インポートや定数定義
extern crate portaudio;
use portaudio as pa;
use std::collections::VecDeque;
const SAMPLE_RATE: f64 = 44100.0;
const CHANNELS: i32 = 1;
const FRAMES: u32 = 256;
const INTERLEAVED: bool = true;
-
SAMPLE_RATE
一秒間に何回サンプリングをするか -
CHANNELS
録音(再生)チャネル数。1はモノラル、2はステレオ -
FRAMES
一度の読み取りで取得するサンプルの数。 -
INTERLEAVED
よくわかりません。音声の切れ目を補うもの?とりあえずexample通りtrueにしておきます。
main関数
fn main() {
match run() {
Ok(_) => {}
e => {
eprintln!("Example failed with the following: {:?}", e);
}
}
}
run()
という関数はこの後に定義します。
run()
内では無限ループが起こっており、何らかのエラーが生じた際にはpa::Errorを返し、そこで実行が終わります。次に、run()
関数を見ていきます。
run関数
以下のコードはrun()
関数内で定義されています。
streamを開く
let pa = pa::PortAudio::new()?;
let def_input = pa.default_input_device()?;
let input_info = pa.device_info(def_input)?;
let latency = input_info.default_low_input_latency;
let input_params = pa::StreamParameters::<f32>::new(def_input, CHANNELS, INTERLEAVED, latency);
let settings = pa::InputStreamSettings::new(input_params, SAMPLE_RATE, FRAMES);
let mut stream = pa.open_blocking_stream(settings)?;
stream.start()?;
各種情報を取得して、設定に流し込みます。
-
pa::PortAudio::new()?
で、PortAudio
インスタンスを取得します。new()
メソッドの定義は、pub fn new() -> Result<Self, Error>
となっており、取得に失敗するとエラーを返します。 -
pa.default_input_device()?
で、DeviceIndex
型のdevice indexを返します。 -
pa.device_info(def_input)?
で、DEviceInfo
型のデバイス情報を返します。 -
input_info.default_low_input_latency
でレイテンシを返します。デバイスの遅延時間だと思われます。 -
pa::StreamParameters::<f32>::new(def_input, CHANNELS, INTERLEAVED, latency);
少し長いですが、Parameters<f32>
型のinput_params(Input用パラメータインスタンス)を作成します。 - pa::InputStreamSettings::new(input_params, SAMPLE_RATE, FRAMES);
先ほどのinput_paramsと、SAMPLE_RATE,FRAMESの情報を使って、InputSettings<f32>型
の、ストリーム用の設定(settings
)を作ります。 -
let mut stream = pa.open_blocking_stream(settings)?;
で、ストリーム設定を食わせてストリームを開きます。 -
stream.start()?;
で、ストリームを開始します。
buffer
let mut buffer: VecDeque<f32> = VecDeque::with_capacity(FRAMES as usize * CHANNELS as usize);
録音データを突っ込むためのバッファも用意します。buffer.extend(samples.into_iter())で、bufferを伸長できます。
wait for stream
run()の内部関数として、wait_for_streamが定義されています。
この関数の役割は、stream内にデータサンプルが溜まっているかどうかをチェックし、溜まっていれば、その溜まっているデータサンプル数を返す関数です。
fn wait_for_stream<F>(f: F, name: &str) -> u32
where
F: Fn() -> Result<pa::StreamAvailable, pa::error::Error>,
{
loop {
match f() {
Ok(available) => match available {
pa::StreamAvailable::Frames(frames) => return frames as u32,
pa::StreamAvailable::InputOverflowed => println!("Input stream has overflowed"),
pa::StreamAvailable::OutputUnderflowed => {
println!("Output stream has underflowed")
}
},
Err(err) => panic!(
"An error occurred while waiting for the {} stream: {}",
name, err
),
}
}
}
-
wait_for_stream(f, name)
は関数fと操作名nameを引数に取る関数です。
ここで、fはResult<pa::StreamAvailable, pa::error::Error>,
を返すものとしています。以下で、stream.read_available()
が渡されます。
stream loop
loop {
let in_frames = wait_for_stream(|| stream.read_available(), "Read");
if in_frames > 0 {
let input_samples = stream.read(in_frames)?;
buffer.extend(input_samples.into_iter());
}
wait_for_stream内ではloopがおこっており、in_frameが返されたとき、
下のif分が実行されます。
-
stream.read(in_frames)?
でstreamから、in_frames分のデータが読み込まれます。 -
buffer.extend(input_samples.into_iter())
にて、bufferにデータが追加されていきます。 -
dbg!(&buffer);
などを追加して内容を見るのもいいと思います。
コード全文
extern crate portaudio;
use portaudio as pa;
use std::collections::VecDeque;
const SAMPLE_RATE: f64 = 16000.0;
const CHANNELS: i32 = 1;
const FRAMES: u32 = 16000;
const INTERLEAVED: bool = true;
fn main() {
match run() {
Ok(_) => {}
e => {
eprintln!("Example failed with the following: {:?}", e);
}
}
}
fn run() -> Result<(), pa::Error> {
let pa = pa::PortAudio::new()?;
let def_input = pa.default_input_device()?;
let input_info = pa.device_info(def_input)?;
let latency = input_info.default_low_input_latency;
let input_params =
pa::StreamParameters::<f32>::new(
def_input,
CHANNELS,
INTERLEAVED,
latency);
let settings = pa::InputStreamSettings::new(input_params, SAMPLE_RATE, FRAMES);
let mut stream = pa.open_blocking_stream(settings)?;
let mut buffer: VecDeque<f32> = VecDeque::with_capacity(FRAMES as usize * CHANNELS as usize);
stream.start()?;
fn wait_for_stream<F>(f: F, name: &str) -> u32
where
F: Fn() -> Result<pa::StreamAvailable, pa::error::Error>,
{
loop {
match f() {
Ok(available) => match available {
pa::StreamAvailable::Frames(frames) => return frames as u32,
pa::StreamAvailable::InputOverflowed => println!("Input stream has overflowed"),
pa::StreamAvailable::OutputUnderflowed => {
println!("Output stream has underflowed")
}
},
Err(err) => panic!(
"An error occurred while waiting for the {} stream: {}",
name, err
),
}
}
}
loop {
let in_frames = wait_for_stream(|| stream.read_available(), "Read");
if in_frames > 0 {
let input_samples = stream.read(in_frames)?;
buffer.extend(input_samples.into_iter());
}
}
}
- githubに挙げました
https://github.com/kei67/rust-portaudio-recording
Discussion