本系列教程对应的代码已开源在 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。