🦀

rust-portaudioで録音をする

2022/04/18に公開

はじめに

rust-portaudioは、C言語で書かれたportaudioというマイク/スピーカーを用いて入出力をするソフトウェアのラッパーです。

[参考]

まずportaudioを入れます。

brew install portaudio

次にcargo newをします。

cargo new rustaudio
cd rustaudio

Cargo.tomlに、クレートとバージョンを追記します。

[dependencies]
portaudio = "*"

これで準備ができました。

BlockingとNonbloking

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());
        }
    }
}

Discussion