本系列教程對應的代碼已開源在 Github zeedle
添加依賴
在Cargo.toml添加:
[dependencies]
rodio = "0.21.1"
添加/播放/暫停音頻
use std::{thread, time::Duration};
use rodio::Decoder;
fn main() {
// create an output stream
let stream_handle = rodio::OutputStreamBuilder::from_default_device()
.expect("no output device available")
.open_stream()
.expect("failed to open output stream");
// create a sink to play audio
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
// open an audio file
let file = std::fs::File::open("audios/愛情轉移.flac").expect("failed to open audio file");
// decode the audio file
let source = Decoder::try_from(file).expect("failed to decode audio file");
// append the audio source to the sink & auto play
sink.append(source);
// sleep for a while to let the audio play
thread::sleep(Duration::from_secs(20));
// pause the audio playback explicitly
sink.pause();
// sleep for a while
thread::sleep(Duration::from_secs(20));
// resume the audio playback explicitly
sink.play();
// keep the main thread alive while the audio is playing
thread::sleep(Duration::from_secs(20));
}
代碼及主要API解讀
- stream_handle 是音頻流句柄,直接跟硬件相關
- rodio::Sink::connect_new 連接到音頻流,返回一個Sink對象,是輸出到stream_handle對應device的“音頻管理器”
- Decoder::try_from(file) 嘗試解碼音頻文件
- sink.append 向音頻管理器中添加source並立刻自動啓動播放
- sink.pause 顯式停止音頻播放
- sink.play 顯式恢復音頻播放
- sink.clear 清除sink中存儲的所有source,釋放資源(這裏並未用到)
注意
執行上述代碼,會:
- 播放20秒音頻
- 停止20秒
- 再播放20秒音頻
- 程序退出
如果sink.append之後沒有thread::sleep,程序會立刻結束,任何聲音都不會被播放,這是因為,根據Rust變量的生命週期,stream_handle變量會在main函數右括號}處立刻釋放,由於stream_handle管理了計算機音頻輸出設備硬件資源,當它超出生命週期被釋放時,與之關聯的任何音頻播放(也就是sink中存在的所有source)都會被強制停止,這是Rodio庫為了保護硬件資源做出的一個設計,大大減小了硬件不可控事件的出現。
還有一些額外的事情需要注意:
- stream_handle直接持有了硬件資源,因此它不是線程安全的,無法在線程之間安全傳遞
- sink藉助stream_handle操控音頻播放,因此stream_handle的生命週期一定長於sink
- sink是線程安全的,可以在線程之間安全傳遞,後面製作音樂播放器時會大量用到這個特性,它能同時存在於UI線程與後台線程中,只要確保stream_handle的生命週期長於sink。