project restructure with Rust workspaces
This commit is contained in:
parent
3ffbe876f3
commit
09f72622bc
428 changed files with 19372 additions and 6061 deletions
191
crates/jarvis-core/src/recorder/cpal.rs
Normal file
191
crates/jarvis-core/src/recorder/cpal.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
Abandoned temporary.
|
||||
Problems with frame size.
|
||||
*/
|
||||
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::{BufferSize, StreamConfig, SampleRate, Host, Device, Stream, SampleFormat};
|
||||
use log::{info, warn, error};
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Arc;
|
||||
use arc_swap::ArcSwap;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
||||
|
||||
use crate::tauri_commands::cpal_data_callback;
|
||||
|
||||
static HOST: OnceCell<Host> = OnceCell::new();
|
||||
thread_local!(static RECORDER: OnceCell<ArcSwap<Stream>> = OnceCell::new());
|
||||
static SELECTED_MICROPHONE_IDX: AtomicI32 = AtomicI32::new(0);
|
||||
static FRAME_LENGTH: AtomicU32 = AtomicU32::new(0);
|
||||
static IS_RECORDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
|
||||
// init host & frame buffer for the callback
|
||||
if HOST.get().is_none() {
|
||||
HOST.set(cpal::default_host());
|
||||
|
||||
// FRAME_BUFFER.set(Mutex::new(vec![0; FRAME_LENGTH.load(Ordering::SeqCst) as usize]));
|
||||
}
|
||||
|
||||
// init microphone
|
||||
RECORDER.with(|recorder| {
|
||||
match recorder.get().is_none() {
|
||||
true => {
|
||||
if let Some(device) = get_device(device_index as usize) {
|
||||
// store
|
||||
recorder.set(ArcSwap::from_pointee(create_stream(device, frame_length)));
|
||||
|
||||
// remember current configuration
|
||||
SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
|
||||
FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
|
||||
|
||||
// success
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
false => {
|
||||
// check if re-initialization required (i.e. selecetd microphoneor frame-length was changed )
|
||||
if SELECTED_MICROPHONE_IDX.load(Ordering::SeqCst) != device_index
|
||||
||
|
||||
FRAME_LENGTH.load(Ordering::SeqCst) != frame_length {
|
||||
warn!("Selected microphone or frame length was changed, re-initializing ...");
|
||||
// initialize again with new device index
|
||||
if IS_RECORDING.load(Ordering::SeqCst) {
|
||||
stop_recording();
|
||||
}
|
||||
|
||||
// remember new configuration
|
||||
SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
|
||||
FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
|
||||
|
||||
if let Some(device) = get_device(device_index as usize) {
|
||||
// store
|
||||
recorder.get().unwrap().store(Arc::new(create_stream(device, frame_length)));
|
||||
|
||||
// success
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// success
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn create_stream(device: Device, frame_length: u32) -> Stream {
|
||||
// get default input stream config
|
||||
// let default_config = device.default_input_config().unwrap();
|
||||
|
||||
// create config for the stream
|
||||
// let config: StreamConfig = StreamConfig {
|
||||
// channels: default_config.channels(),
|
||||
// sample_rate: SampleRate(16000),
|
||||
// buffer_size: BufferSize::Fixed(frame_length)
|
||||
// };
|
||||
|
||||
let config = device
|
||||
.default_input_config()
|
||||
.expect("Failed to load default input config");
|
||||
|
||||
let channels = config.channels();
|
||||
|
||||
let err_fn = move |err| {
|
||||
eprintln!("an error occurred on stream: {}", err);
|
||||
};
|
||||
|
||||
match config.sample_format() {
|
||||
SampleFormat::F32 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[f32], info| {
|
||||
cpal_data_callback(data, channels);
|
||||
},
|
||||
err_fn,
|
||||
None
|
||||
),
|
||||
SampleFormat::U16 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[u16], info| {
|
||||
cpal_data_callback(data, channels);
|
||||
},
|
||||
err_fn,
|
||||
None
|
||||
),
|
||||
SampleFormat::I16 => device.build_input_stream(
|
||||
&config.into(),
|
||||
move |data: &[i16], info| {
|
||||
cpal_data_callback(data, channels);
|
||||
},
|
||||
err_fn,
|
||||
None
|
||||
),
|
||||
_ => todo!()
|
||||
}.unwrap()
|
||||
}
|
||||
|
||||
pub fn stereo_to_mono(input_data: &[i16]) -> Vec<i16> {
|
||||
let mut result = Vec::with_capacity(input_data.len() / 2);
|
||||
result.extend(
|
||||
input_data
|
||||
.chunks_exact(2)
|
||||
.map(|chunk| chunk[0] / 2 + chunk[1] / 2),
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn get_device(device_index: usize) -> Option<Device> {
|
||||
if let Some(device) = HOST.get().unwrap().input_devices().expect("Get devices error ...").nth(device_index) {
|
||||
Some(device)
|
||||
} else {
|
||||
if let Some(default) = HOST.get().unwrap().default_input_device() {
|
||||
Some(default)
|
||||
} else {
|
||||
error!("No default input device ...");
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_recording(device_index: i32, frame_length: u32) {
|
||||
// ensure microphone is initialized
|
||||
init_microphone(device_index, frame_length);
|
||||
|
||||
// start recording
|
||||
RECORDER.with(|recorder| {
|
||||
match recorder.get().unwrap().load().play() {
|
||||
Err(msg) => {
|
||||
error!("[CPAL] Audio stream PLAY error ... {:?}", msg);
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
|
||||
IS_RECORDING.store(true, Ordering::SeqCst);
|
||||
info!("START recording from microphone ...");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop_recording() {
|
||||
// ensure microphone is initialized
|
||||
RECORDER.with(|recorder| {
|
||||
if !recorder.get().is_none() && IS_RECORDING.load(Ordering::SeqCst) {
|
||||
// pause instead of stop
|
||||
match recorder.get().unwrap().load().pause() {
|
||||
Err(msg) => {
|
||||
error!("[CPAL] Audio stream PAUSE error ... {:?}", msg);
|
||||
},
|
||||
_ => ()
|
||||
};
|
||||
|
||||
IS_RECORDING.store(false, Ordering::SeqCst);
|
||||
info!("STOP recording from microphone ...");
|
||||
}
|
||||
});
|
||||
}
|
||||
205
crates/jarvis-core/src/recorder/portaudio.rs
Normal file
205
crates/jarvis-core/src/recorder/portaudio.rs
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
/*
|
||||
Abandoned temporary.
|
||||
*/
|
||||
|
||||
use portaudio as pa;
|
||||
use pa::{DeviceIndex, Stream};
|
||||
use log::{info, warn, error};
|
||||
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use arc_swap::ArcSwap;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicU32, Ordering};
|
||||
|
||||
thread_local!(static RECORDER: OnceCell<ArcSwap<Mutex<Stream<pa::Blocking<pa::stream::Buffer>, pa::Input<i16>>>>> = OnceCell::new());
|
||||
static SELECTED_MICROPHONE_IDX: AtomicI32 = AtomicI32::new(0);
|
||||
static FRAME_LENGTH: AtomicU32 = AtomicU32::new(0);
|
||||
static IS_RECORDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const CHANNELS: i32 = 1;
|
||||
const SAMPLE_RATE: f64 = 16_000.0;
|
||||
|
||||
pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
|
||||
RECORDER.with(|r| {
|
||||
match r.get().is_none() {
|
||||
true => {
|
||||
match create_stream(device_index, frame_length) {
|
||||
Ok(stream) => {
|
||||
// store
|
||||
r.set(ArcSwap::from_pointee(Mutex::new(stream)));
|
||||
|
||||
// remember current configuration
|
||||
SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
|
||||
FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
|
||||
|
||||
// success
|
||||
true
|
||||
},
|
||||
Err(msg) => {
|
||||
error!("Failed to initialize portaudio.\nError details: {:?}", msg);
|
||||
|
||||
// fail
|
||||
false
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
// check if re-initialization required (i.e. selecetd microphoneor frame-length was changed )
|
||||
if SELECTED_MICROPHONE_IDX.load(Ordering::SeqCst) != device_index
|
||||
||
|
||||
FRAME_LENGTH.load(Ordering::SeqCst) != frame_length {
|
||||
warn!("Selected microphone or frame length was changed, re-initializing ...");
|
||||
// initialize again with new device index
|
||||
if IS_RECORDING.load(Ordering::SeqCst) {
|
||||
// RECORDER.get().unwrap().load().stop().expect("Failed to start audio recording!");
|
||||
stop_recording();
|
||||
}
|
||||
|
||||
// store
|
||||
match create_stream(device_index, frame_length) {
|
||||
Ok(stream) => {
|
||||
// store new stream
|
||||
r.get().unwrap().store(Arc::new(Mutex::new(stream)));
|
||||
|
||||
// remember new configuration
|
||||
SELECTED_MICROPHONE_IDX.store(device_index, Ordering::SeqCst);
|
||||
FRAME_LENGTH.store(frame_length, Ordering::SeqCst);
|
||||
|
||||
// success
|
||||
return true
|
||||
},
|
||||
Err(msg) => {
|
||||
error!("Failed to initialize portaudio.\nError details: {:?}", msg);
|
||||
|
||||
// fail
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// success
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn create_stream(device_index: i32, frame_length: u32) -> Result<Stream<pa::Blocking<pa::stream::Buffer>, pa::Input<i16>>, pa::Error> {
|
||||
let pa_recorder: Result<pa::PortAudio, pa::Error> = pa::PortAudio::new();
|
||||
|
||||
match pa_recorder {
|
||||
Ok(pa) => {
|
||||
let input_settings = match get_input_settings(DeviceIndex(device_index as u32), &pa, SAMPLE_RATE, frame_length, CHANNELS) {
|
||||
Ok(settings) => settings,
|
||||
Err(error) => panic!("{}", String::from(error))
|
||||
};
|
||||
|
||||
// Construct a stream with input and output sample types of i16
|
||||
match pa.open_blocking_stream(input_settings) {
|
||||
Ok(strm) => Ok(strm),
|
||||
Err(error) => panic!("{}", error.to_string()),
|
||||
}
|
||||
},
|
||||
Err(msg) => Err(msg)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_input_latency(audio_port: &pa::PortAudio, input_index: pa::DeviceIndex) -> Result<f64, String>
|
||||
{
|
||||
let input_device_information = audio_port.device_info(input_index).or_else(|error| Err(String::from(format!("{}", error))));
|
||||
Ok(input_device_information.unwrap().default_low_input_latency)
|
||||
}
|
||||
|
||||
fn get_input_stream_parameters(input_index: pa::DeviceIndex, latency: f64, channels: i32) -> Result<pa::StreamParameters<i16>, String>
|
||||
{
|
||||
const INTERLEAVED: bool = true;
|
||||
Ok(pa::StreamParameters::<i16>::new(input_index, channels, INTERLEAVED, latency))
|
||||
}
|
||||
|
||||
fn get_input_settings(input_index: pa::DeviceIndex, audio_port: &pa::PortAudio, sample_rate: f64, frames: u32, channels: i32) -> Result<pa::InputStreamSettings<i16>, String>
|
||||
{
|
||||
Ok(
|
||||
pa::InputStreamSettings::new(
|
||||
(get_input_stream_parameters(
|
||||
input_index,
|
||||
(get_input_latency(
|
||||
&audio_port,
|
||||
input_index,
|
||||
))?,
|
||||
channels
|
||||
))?,
|
||||
sample_rate,
|
||||
frames,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// We'll use this function to wait for read/write availability.
|
||||
fn wait_for_stream<F>(f: F, name: &str) -> u32
|
||||
where
|
||||
F: Fn() -> Result<pa::StreamAvailable, pa::error::Error>,
|
||||
{
|
||||
loop {
|
||||
match f() {
|
||||
Ok(available) => match available {
|
||||
pa::StreamAvailable::Frames(frames) => return frames as u32,
|
||||
pa::StreamAvailable::InputOverflowed => println!("Input stream has overflowed"),
|
||||
pa::StreamAvailable::OutputUnderflowed => {
|
||||
println!("Output stream has underflowed")
|
||||
}
|
||||
},
|
||||
Err(err) => panic!(
|
||||
"An error occurred while waiting for the {} stream: {}",
|
||||
name, err
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_microphone(frame_buffer: &mut [i16]) {
|
||||
// ensure microphone is initialized
|
||||
RECORDER.with(|r| {
|
||||
if !r.get().is_none() {
|
||||
let cell = r.get().unwrap().load();
|
||||
let mut lock = cell.lock();
|
||||
let stream = lock.as_mut().unwrap();
|
||||
|
||||
// read to frame buffer
|
||||
let in_frames = wait_for_stream(|| stream.read_available(), "Read");
|
||||
|
||||
if in_frames > 0 {
|
||||
// let input_samples = stream.read(in_frames).expect("Cannot read frames ...");
|
||||
// println!("Read {:?} frames from the input stream.", in_frames);
|
||||
|
||||
let input_samples = stream.read(in_frames).expect("Cannot read frames ...");
|
||||
println!("Read: {} (required {})", input_samples.len(), frame_buffer.len());
|
||||
frame_buffer.copy_from_slice(input_samples.chunks(frame_buffer.len()).last().unwrap());
|
||||
}
|
||||
// r.get().unwrap().load().read(frame_buffer).expect("Failed to read audio frame");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn start_recording(device_index: i32, frame_length: u32) {
|
||||
// ensure microphone is initialized
|
||||
init_microphone(device_index, frame_length);
|
||||
|
||||
// start recording
|
||||
RECORDER.with(|r| {
|
||||
r.get().unwrap().load().lock().unwrap().start().expect("Failed to start audio recording!");
|
||||
IS_RECORDING.store(true, Ordering::SeqCst);
|
||||
info!("START recording from microphone ...");
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop_recording() {
|
||||
RECORDER.with(|r| {
|
||||
if !r.get().is_none() && IS_RECORDING.load(Ordering::SeqCst) {
|
||||
// stop recording
|
||||
let pa = r.get().unwrap().load();
|
||||
r.get().unwrap().load().lock().unwrap().stop().expect("Failed to stop audio recording!");
|
||||
IS_RECORDING.store(false, Ordering::SeqCst);
|
||||
info!("STOP recording from microphone ...");
|
||||
}
|
||||
});
|
||||
}
|
||||
129
crates/jarvis-core/src/recorder/pvrecorder.rs
Normal file
129
crates/jarvis-core/src/recorder/pvrecorder.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use once_cell::sync::OnceCell;
|
||||
use pv_recorder::{PvRecorder, PvRecorderBuilder};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
static RECORDER: OnceCell<PvRecorder> = OnceCell::new();
|
||||
static IS_RECORDING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub fn init_microphone(device_index: i32, frame_length: u32) -> bool {
|
||||
match RECORDER.get().is_none() {
|
||||
true => {
|
||||
let pv_recorder = PvRecorderBuilder::new(frame_length as i32)
|
||||
.device_index(device_index)
|
||||
// .frame_length(frame_length as i32)
|
||||
.init();
|
||||
|
||||
match pv_recorder {
|
||||
Ok(pv) => {
|
||||
// store
|
||||
RECORDER.set(pv);
|
||||
|
||||
// success
|
||||
true
|
||||
}
|
||||
Err(msg) => {
|
||||
error!("Failed to initialize pvrecorder.\nError details: {:?}", msg);
|
||||
|
||||
// fail
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => true, // already initialized
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_microphone(frame_buffer: &mut [i16]) {
|
||||
// ensure microphone is initialized
|
||||
if !RECORDER.get().is_none() {
|
||||
// read to frame buffer
|
||||
|
||||
let frame = RECORDER.get().unwrap().read();
|
||||
|
||||
match frame {
|
||||
Ok(f) => {
|
||||
frame_buffer.copy_from_slice(f.as_slice());
|
||||
}
|
||||
Err(msg) => {
|
||||
// @TODO: Fix? PvRecorder always wait for PCM buffer size of 512.
|
||||
error!("Failed to read audio frame. {:?}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_recording(device_index: i32, frame_length: u32) -> Result<(), ()> {
|
||||
// ensure microphone is initialized
|
||||
init_microphone(device_index, frame_length);
|
||||
|
||||
// start recording
|
||||
match RECORDER.get().unwrap().start() {
|
||||
Ok(_) => {
|
||||
info!("START recording from microphone ...");
|
||||
|
||||
// change recording state
|
||||
IS_RECORDING.store(true, Ordering::SeqCst);
|
||||
|
||||
// success
|
||||
Ok(())
|
||||
}
|
||||
Err(msg) => {
|
||||
error!("Failed to start audio recording!");
|
||||
|
||||
// fail
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stop_recording() -> Result<(), ()> {
|
||||
// ensure microphone is initialized & recording is in process
|
||||
if !RECORDER.get().is_none() && IS_RECORDING.load(Ordering::SeqCst) {
|
||||
// stop recording
|
||||
match RECORDER.get().unwrap().stop() {
|
||||
Ok(_) => {
|
||||
info!("STOP recording from microphone ...");
|
||||
|
||||
// change recording state
|
||||
IS_RECORDING.store(false, Ordering::SeqCst);
|
||||
|
||||
// success
|
||||
return Ok(());
|
||||
}
|
||||
Err(msg) => {
|
||||
error!("Failed to stop audio recording!");
|
||||
|
||||
// fail
|
||||
return Err(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(()) // if already stopped or not yet initialized
|
||||
}
|
||||
|
||||
pub fn list_audio_devices() -> Vec<String> {
|
||||
let audio_devices = PvRecorderBuilder::default().get_available_devices();
|
||||
match audio_devices {
|
||||
Ok(audio_devices) => audio_devices,
|
||||
Err(err) => panic!("Failed to get audio devices: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_audio_device_name(idx: i32) -> String {
|
||||
let audio_devices = list_audio_devices();
|
||||
let mut first_device: String = String::new();
|
||||
|
||||
for (_idx, device) in audio_devices.iter().enumerate() {
|
||||
if idx as usize == _idx {
|
||||
return device.to_string();
|
||||
}
|
||||
|
||||
if _idx == 0 {
|
||||
first_device = device.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// return first device as default, if none were matched
|
||||
first_device
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue