Files
open-xiaoai/packages/client-rust/src/services/audio/record.rs
T

127 lines
3.7 KiB
Rust

use std::future::Future;
use std::process::Stdio;
use std::sync::{Arc, LazyLock};
use tokio::io::AsyncReadExt;
use tokio::process::{Child, Command};
use tokio::sync::Mutex;
use tokio::task::JoinHandle;
use crate::base::AppError;
use super::config::{AudioConfig, AUDIO_CONFIG};
#[derive(PartialEq)]
enum State {
Idle,
Recording,
}
pub struct AudioRecorder {
state: Arc<Mutex<State>>,
arecord_thread: Arc<Mutex<Option<Child>>>,
read_thread: Arc<Mutex<Option<JoinHandle<()>>>>,
}
static INSTANCE: LazyLock<AudioRecorder> = LazyLock::new(AudioRecorder::new);
impl AudioRecorder {
fn new() -> Self {
Self {
state: Arc::new(Mutex::new(State::Idle)),
arecord_thread: Arc::new(Mutex::new(None)),
read_thread: Arc::new(Mutex::new(None)),
}
}
pub fn instance() -> &'static Self {
&INSTANCE
}
pub async fn stop_recording(&self) -> Result<(), AppError> {
let mut state = self.state.lock().await;
if *state == State::Idle {
return Ok(());
}
if let Some(read_thread) = self.read_thread.lock().await.take() {
read_thread.abort();
}
if let Some(mut arecord_thread) = self.arecord_thread.lock().await.take() {
let _ = arecord_thread.kill().await;
}
*state = State::Idle;
Ok(())
}
pub async fn start_recording<F, Fut>(
&self,
on_stream: F,
config: Option<AudioConfig>,
) -> Result<(), AppError>
where
F: Fn(Vec<u8>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<(), AppError>> + Send + 'static,
{
let mut state = self.state.lock().await;
if *state == State::Recording {
return Ok(());
}
let config = config.unwrap_or_else(|| (*AUDIO_CONFIG).clone());
let mut arecord_thread = Command::new("arecord")
.args([
"--quiet",
"-t",
"raw",
"-D",
&config.pcm,
"-f",
&format!("S{}_LE", config.bits_per_sample),
"-r",
&config.sample_rate.to_string(),
"-c",
&config.channels.to_string(),
"--buffer-size",
&config.buffer_size.to_string(),
"--period-size",
&config.period_size.to_string(),
])
.stdout(Stdio::piped())
.spawn()?;
let mut stdout = arecord_thread.stdout.take().unwrap();
let read_thread = tokio::spawn(async move {
let size = (config.bits_per_sample / 8) as usize;
let target_size = config.buffer_size as usize * size;
let mut accumulated_data = Vec::new();
let mut buffer = vec![0u8; config.period_size as usize * size];
loop {
match stdout.read(&mut buffer).await {
Ok(size) if size > 0 => {
accumulated_data.extend_from_slice(&buffer[..size]);
if accumulated_data.len() >= target_size {
let data_to_send =
accumulated_data.drain(..target_size).collect::<Vec<u8>>();
let _ = on_stream(data_to_send).await;
}
}
_ => break,
}
}
let _ = AudioRecorder::instance().stop_recording().await;
});
self.arecord_thread.lock().await.replace(arecord_thread);
self.read_thread.lock().await.replace(read_thread);
*state = State::Recording;
Ok(())
}
}