Webカメラ同様、マイク入力も HTML5 API (MediaRecorder) で可能ですが、より低レベルな音声処理やファイル保存を Rust 側で行いたい場合は cpal クレートを使用します。
1. 依存関係の追加
src-tauri ディレクトリに移動して、以下のコマンドを実行します。
cargo add cpal hound
# hound は WAV ファイル保存用
2. 録音コマンドの実装
この例では、指定秒数だけ録音して WAV ファイルに保存します。use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use std::sync::{Arc, Mutex};
#[tauri::command]
fn record_audio(duration_sec: u64, file_path: String) -> Result<String, String> {
let host = cpal::default_host();
let device = host.default_input_device().ok_or("No input device available")?;
let config = device.default_input_config().map_err(|e| e.to_string())?;
// 音声データを一時保存するバッファ
let writer = Arc::new(Mutex::new(hound::WavWriter::create(
&file_path,
hound::WavSpec {
channels: config.channels(),
sample_rate: config.sample_rate(),
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
},
).map_err(|e| e.to_string())?));
let writer_clone = writer.clone();
let err_fn = |err| eprintln!("an error occurred on stream: {}", err);
let stream = match config.sample_format() {
cpal::SampleFormat::F32 => device.build_input_stream(
&config.into(),
move |data: &[f32], _: &_| {
let mut guard = writer_clone.lock().unwrap();
for &sample in data {
// f32 -> i16 変換
let s = (sample * i16::MAX as f32) as i16;
guard.write_sample(s).ok();
}
},
err_fn,
None // Timeout
),
_ => return Err("Unsupported sample format".to_string()),
}.map_err(|e| e.to_string())?;
stream.play().map_err(|e| e.to_string())?;
// 指定時間待機
std::thread::sleep(std::time::Duration::from_secs(duration_sec));
// ストリーム破棄(ドロップ)で停止
// これにより writer_clone もドロップされ、writer の参照カウントが 1 に戻るはずです
drop(stream);
// Arc と Mutex のラップを解除して中の WavWriter を取り出す
let mutex = Arc::try_unwrap(writer).map_err(|_| "Failed to unwrap Arc writer".to_string())?;
let mut inner_writer = mutex.into_inner().map_err(|_| "Mutex is poisoned".to_string())?;
// ファイルをフラッシュして閉じる
inner_writer.finalize().map_err(|e| e.to_string())?;
Ok(format!("Recorded to {}", file_path))
}