feat: 动态链接到 opus 和 alsa 基础库,完成音频录制播放和编解码
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
use opus::{Encoder, Decoder, Application, Bitrate, Channels};
|
||||
use anyhow::{Result, Context};
|
||||
use crate::config::AudioConfig;
|
||||
|
||||
pub struct OpusCodec {
|
||||
encoder: Encoder,
|
||||
decoder: Decoder,
|
||||
}
|
||||
|
||||
impl OpusCodec {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
let channels = if config.channels == 1 { Channels::Mono } else { Channels::Stereo };
|
||||
let mut encoder = Encoder::new(config.sample_rate, channels, Application::Voip)
|
||||
.context("Failed to create Opus encoder")?;
|
||||
encoder.set_bitrate(Bitrate::Bits(config.bitrate))?;
|
||||
|
||||
let decoder = Decoder::new(config.sample_rate, channels)
|
||||
.context("Failed to create Opus decoder")?;
|
||||
|
||||
Ok(Self { encoder, decoder })
|
||||
}
|
||||
|
||||
pub fn encode(&mut self, pcm: &[i16], out: &mut [u8]) -> Result<usize> {
|
||||
self.encoder.encode(pcm, out).context("Opus encoding failed")
|
||||
}
|
||||
|
||||
pub fn decode(&mut self, opus: &[u8], out: &mut [i16]) -> Result<usize> {
|
||||
self.decoder.decode(opus, out, false).context("Opus decoding failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod recorder;
|
||||
pub mod player;
|
||||
pub mod codec;
|
||||
|
||||
pub use recorder::AudioRecorder;
|
||||
pub use player::AudioPlayer;
|
||||
pub use codec::OpusCodec;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
use crate::config::AudioConfig;
|
||||
use alsa::pcm::{Access, Format, HwParams, PCM};
|
||||
use alsa::Direction;
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
pub struct AudioPlayer {
|
||||
pcm: PCM,
|
||||
}
|
||||
|
||||
impl AudioPlayer {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
let pcm = PCM::new(&config.playback_device, Direction::Playback, false)
|
||||
.context("Failed to open playback PCM device")?;
|
||||
|
||||
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||
Ok(Self { pcm })
|
||||
}
|
||||
|
||||
pub fn write(&self, buffer: &[i16]) -> Result<usize> {
|
||||
let res = self.pcm.io_i16()?.writei(buffer);
|
||||
|
||||
match res {
|
||||
Ok(written) => Ok(written),
|
||||
Err(e) => {
|
||||
// Buffer Underrun,即播放缓冲区的数据被耗尽,导致音频流中断
|
||||
if e.errno() == 32 {
|
||||
// 恢复音频流状态
|
||||
self.pcm.prepare()?;
|
||||
// 重新获取 IO 对象并尝试写入数据
|
||||
self.pcm
|
||||
.io_i16()?
|
||||
.writei(buffer)
|
||||
.context("Failed to write to playback device after recovery")
|
||||
} else {
|
||||
Err(e).context("Failed to write to playback device")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels(channels as u32)?;
|
||||
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||
|
||||
let swp = pcm.sw_params_current()?;
|
||||
pcm.sw_params(&swp)?;
|
||||
pcm.prepare()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
use alsa::pcm::{PCM, HwParams, Format, Access};
|
||||
use alsa::Direction;
|
||||
use anyhow::{Result, Context};
|
||||
use crate::config::AudioConfig;
|
||||
|
||||
pub struct AudioRecorder {
|
||||
pcm: PCM,
|
||||
}
|
||||
|
||||
impl AudioRecorder {
|
||||
pub fn new(config: &AudioConfig) -> Result<Self> {
|
||||
let pcm = PCM::new(&config.capture_device, Direction::Capture, false)
|
||||
.context("Failed to open capture PCM device")?;
|
||||
|
||||
setup_pcm(&pcm, config.sample_rate, config.channels)?;
|
||||
Ok(Self { pcm })
|
||||
}
|
||||
|
||||
pub fn read(&self, buffer: &mut [i16]) -> Result<usize> {
|
||||
self.pcm.io_i16()?.readi(buffer).context("Failed to read from capture device")
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_pcm(pcm: &PCM, sample_rate: u32, channels: u16) -> Result<()> {
|
||||
let hwp = HwParams::any(pcm).context("Failed to get HwParams")?;
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate(sample_rate, alsa::ValueOr::Nearest)?;
|
||||
hwp.set_channels(channels as u32)?;
|
||||
pcm.hw_params(&hwp).context("Failed to set HwParams")?;
|
||||
|
||||
let swp = pcm.sw_params_current()?;
|
||||
pcm.sw_params(&swp)?;
|
||||
pcm.prepare()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
pub struct AudioConfig {
|
||||
pub capture_device: String,
|
||||
pub playback_device: String,
|
||||
pub sample_rate: u32,
|
||||
pub channels: u16,
|
||||
pub frame_size: usize,
|
||||
pub bitrate: i32,
|
||||
}
|
||||
|
||||
impl Default for AudioConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// 根据 apps/runtime/squashfs-root/etc/asound.conf 分析:
|
||||
// 录音设备对应 pcm.Capture -> hw:0,3 (S32_LE, 48000Hz)
|
||||
// 播放设备对应 pcm.dmixer -> hw:0,2 (S16_LE, 48000Hz)
|
||||
// 使用 "default" 或 "plug" 设备可以由 ALSA 自动处理采样率和格式转换
|
||||
capture_device: "plug:Capture".to_string(),
|
||||
playback_device: "default".to_string(),
|
||||
sample_rate: 16000, // Opus 常用 16k,ALSA plug 会自动从 48k 转换
|
||||
channels: 1,
|
||||
frame_size: 320, // 20ms at 16kHz
|
||||
bitrate: 16000,
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-7
@@ -1,9 +1,63 @@
|
||||
fn main() {
|
||||
println!("Hello from OpenWrt (ARMhf)!");
|
||||
println!("System information (via /proc/version):");
|
||||
if let Ok(version) = std::fs::read_to_string("/proc/version") {
|
||||
println!("{}", version);
|
||||
} else {
|
||||
println!("Could not read /proc/version (running in simulation?)");
|
||||
mod audio;
|
||||
mod config;
|
||||
|
||||
use crate::audio::{AudioPlayer, AudioRecorder, OpusCodec};
|
||||
use crate::config::AudioConfig;
|
||||
use anyhow::Result;
|
||||
use std::collections::VecDeque;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let config = AudioConfig::default();
|
||||
println!("Starting Audio Modular Demo with 1s delay");
|
||||
println!(
|
||||
"Config: capture={}, playback={}, rate={}, channels={}, frame_size={}",
|
||||
config.capture_device,
|
||||
config.playback_device,
|
||||
config.sample_rate,
|
||||
config.channels,
|
||||
config.frame_size
|
||||
);
|
||||
|
||||
let recorder = AudioRecorder::new(&config)?;
|
||||
let player = AudioPlayer::new(&config)?;
|
||||
let mut codec = OpusCodec::new(&config)?;
|
||||
|
||||
// 使用队列存储编码后的 Opus 数据块及录制时间
|
||||
let mut delay_queue: VecDeque<(Instant, Vec<u8>)> = VecDeque::new();
|
||||
let delay_duration = Duration::from_secs(1);
|
||||
|
||||
let mut pcm_in = vec![0i16; config.frame_size * config.channels as usize];
|
||||
let mut opus_buf = vec![0u8; 1024];
|
||||
|
||||
println!("Recording... Delaying playback by 1s. Press Ctrl+C to stop.");
|
||||
|
||||
loop {
|
||||
// 1. 录制原始 PCM
|
||||
let read = recorder.read(&mut pcm_in)?;
|
||||
if read == config.frame_size {
|
||||
// 2. 编码并存入队列
|
||||
let opus_len = codec.encode(&pcm_in, &mut opus_buf)?;
|
||||
let opus_data = opus_buf[..opus_len].to_vec();
|
||||
delay_queue.push_back((Instant::now(), opus_data));
|
||||
}
|
||||
|
||||
// 3. 处理延迟播放:检查队列头部数据是否已等待超过 1s
|
||||
while let Some((timestamp, _)) = delay_queue.front() {
|
||||
if timestamp.elapsed() >= delay_duration {
|
||||
let (_, opus_data) = delay_queue.pop_front().unwrap();
|
||||
|
||||
let mut pcm_out = vec![0i16; config.frame_size * config.channels as usize];
|
||||
let decoded_len = codec.decode(&opus_data, &mut pcm_out)?;
|
||||
|
||||
if decoded_len == config.frame_size {
|
||||
// 4. 播放还原后的 PCM
|
||||
player.write(&pcm_out)?;
|
||||
}
|
||||
} else {
|
||||
// 头部数据还未到 1s,由于队列是按时间排序的,后面的肯定也没到
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user