AdventCalendar2020 Rust programming
Rustでwavを解析する その5前回までで, 無圧縮PCMのバイナリデータからフォーマット情報と生の波形データのスライスを取得できるようになったはずですので, 実際にwavファイルからフォーマット情報を取得する単純なCLIを作成してみます.
workspaceのルートにあるCargo.tomlにメンバーを新たに追加します.
[workspace] members = [ "binary_reader", "wav", "wav_probe", ]
そして cargo newでwav_probeというbinary用crateを作成します.
cargo new wav_probe --bin
では依存関係の整備からしていきましょう. wav::PcmWavParserを利用して解析を進めていくので, このwav_probeはwav crateに依存します.
[dependencies] wav = {path = "../wav"}
しかし, wav crateの中を見返してみると, 公開範囲がprivateになっているので, 外から見えるようにいくつかのstructやenum, メソッドを外部から参照できるようにします.
wav/lib.rsに実装している各要素の内, WavFormat, PcmWavFormat structそのものと各メンバーはpublicに設定します.
// wav/lib.rs pub struct WavFormat { pub format_tag: u16, pub channels: u16, pub samples_per_sec: u32, pub avg_bytes_per_sec: u32, pub block_align: u16, } pub struct PcmWavFormat { pub wav_format: WavFormat, pub bit_per_sample: u16, }
また, PcmWavParserもnew()やparse()メソッドは外から使われないといけないので, これらの公開範囲もpublicにします.
pub struct PcmWavParser<'a> { reader: BinaryReader<'a>, } impl<'a> PcmWavParser<'a> { pub fn new(buffer: &'a Vec<u8>) -> Self { // ... } pub fn parse(&mut self) -> Result<(PcmWavFormat, &'a[u8]), Box<dyn Error + 'static>> { // ... }
エラー関連も外に見せる必要があるので, wav/error.rsにあるRiffParseErrorもpublicにしていきます(が, その3あたりでpubにしてました).
#[derive(Debug, Eq, PartialEq)] pub enum RiffParseError { InvalidChunkFourCC(u32), FmtChunkBeforeDataChunk, }
wav/lib.rsの先頭に以下の設定を加えます(加えていましたが一応).
pub mod error;
では, wav_probe/main.rsに実装していきましょう.
今回は引数に与えられたwavファイルを解析して情報の表示を行うので, まずは引数を取得します.
Rustでコマンドライン引数を操作するには, std::env::args()を利用します. 今回はファイルパスとして成立する引数を1つ受け取るだけのCLIにするので, 引数が2つ以上ある時はエラーを返したいと思います. ただし, std::env::args()が返すArgs型はiterを実装しているのですが, その第一要素は必ずアプリケーションそのもののパスになっているので, 最初の要素はskipする必要があります.
fn main() { let args = std::env::args().skip(1).collect::<String>(); match args.len() { 1 => { todo!() }, 0 => println!("パラメータを指定してください."), _ => println!("パラメータが多すぎます"), } }
PcmWavParserはメモリ上に展開されているデータを読み取る物として作られているので, ファイルにあるデータをメモリに載せる必要があります. そのため, std:fs::Fileとstd::io::BufReaderを利用して, ファイルの情報をVec<u8>のバッファにつめこみます.
use std::fs::File; use std::io::{BufReader, Read}; //... match args.len() { 1 => { match File::open(args[0]) { Ok(file) => { let mut reader = BufReader::new(file); let mut buffer = Vec::new(); match reader.read_to_end(&mut buffer){ Ok(_) => { todo!() }, Err(e) => println!("{}", e), } }, Err(e) => println!("{}", e), } },
ネストが深くて若干の気持ち悪さがありますが, (面倒なので)このままやっていきます.
これで, ファイル内のデータを全てbufferというメモリ上に載せる事ができたので, PcmWavParserを呼び出して, フォーマット情報を取得していきましょう.
// ... use wav::PcmWavParser; // ... match reader.read_to_end(&mut buffer){ Ok(_) => { let mut parser = PcmWavParser::new(&buffer); match parser.parse() { Ok((format, data)) => { todo!() }, Err(e) => println!("{}", e), } }, Err(e) => println!("{}", e), }
このあたりはテストコードと同様な書き方になるはずです. では表示用に別途関数を用意して, 取得したフォーマット情報を出力しましょう.
fn display_wav_info(format: &PcmWavFormat, data: &[u8]) { println!("フォーマットタグ: {}\n\ チャンネル数: {}\n\ サンプリングレート: {}\n\ データ転送レート: {}\n\ ブロックサイズ: {}\n\ 量子化bit数: {}\n\ タイム: {}sec", format.wav_format.format_tag, format.wav_format.channels, format.wav_format.samples_per_sec, format.wav_format.avg_bytes_per_sec, format.wav_format.block_align, format.bit_per_sample, data.len() as f32 / format.wav_format.block_align as f32 / format.wav_format.samples_per_sec as f32); }
表示用の関数はこのようにprintln!()で取得した情報を列挙しているだけです. これを, main()関数の中で呼び出します.
//... let mut parser = PcmWavParser::new(&buffer); match parser.parse() { Ok((format, data)) => { display_wav_info(&format, data); }, Err(e) => println!("{}", e), } //...
では, 適当に作ったwavファイルを出力してみましょう.
$ cargo run --package wav_probe "tests/wav/sine.wav"
フォーマットタグ: 1 チャンネル数: 2 サンプリングレート: 44100 データ転送レート: 176400 ブロックサイズ: 4 量子化bit数: 16 タイム: 2.1253288sec
はい, このように情報を表示できるようになりました. お疲れ様でした.