use std::vec;
use std::fs::File;
use std::io;
use std::io::{BufReader, prelude::*};
use std::{error::Error, fmt};
use log::error;
use byteorder::{ByteOrder, LittleEndian};
#[cfg(feature = "vad-webrtc")]
use crate::vad::webrtc::Vad;
#[cfg(feature = "vad-silero-onnxruntime")]
use crate::vad::silero_onnxruntime::Vad;
#[cfg(feature = "vad-silero-tract")]
use crate::vad::silero_tract::Vad;
use crate::VoiceActivity;
use AudioSinkError::*;
#[cfg(any(feature = "debug-sample-data", feature = "debug-voice-activity-data"))]
use std::{io::Write, path::PathBuf};
#[cfg(feature = "debug-sample-data")]
use std::slice;
pub struct AudioSink {
pub state: AudioSinkState,
sample_buffer: Vec<i16>,
vad: Vad,
vad_buffer: Vec<bool>,
#[cfg(feature = "debug-sample-data")]
sample_file: File,
#[cfg(feature = "debug-voice-activity-data")]
vad_file: File,
}
impl AudioSink {
pub fn default() -> Result<Self, String> {
let vad = Vad::new().map_err(|s| format!("Error instantiating voice-detector! ({})", s))?;
Ok(AudioSink {
state: AudioSinkState::Open,
sample_buffer: Vec::new(),
vad,
vad_buffer: Vec::new(),
#[cfg(feature = "debug-sample-data")]
sample_file: AudioSink::create_debug_file("alass-sample-data.raw"),
#[cfg(feature = "debug-voice-activity-data")]
vad_file: AudioSink::create_debug_file("alass-voice-activity-data")
})
}
pub fn send_samples(self: &mut AudioSink, samples: &[i16]) -> Result<(), AudioSinkError> {
if self.state == AudioSinkState::Open {
if self.sample_buffer.len() + samples.len() >= self.vad.chunk_size {
let mut first_chunk = Vec::with_capacity(self.vad.chunk_size);
first_chunk.extend_from_slice(self.sample_buffer.as_slice());
let len2 = self.vad.chunk_size - self.sample_buffer.len();
first_chunk.extend_from_slice(&samples[0..len2]);
self.process_chunk(&first_chunk)?;
let remaining_chunks = samples[len2..].chunks_exact(self.vad.chunk_size);
self.sample_buffer.clear();
self.sample_buffer.extend_from_slice(remaining_chunks.remainder());
for chunk in remaining_chunks {
self.process_chunk(chunk)?;
}
} else {
self.sample_buffer.extend_from_slice(samples);
}
Ok(())
} else {
Err(AudioSinkError::SinkClosed)
}
}
fn process_chunk(self: &mut AudioSink, chunk: &[i16]) -> Result<(), AudioSinkError> {
if chunk.len() != self.vad.chunk_size {
error!("Error processing samples: chunk length must be exactly {}", self.vad.chunk_size);
return Err(VoiceDetectionError)
}
match &self.vad.is_voice_segment(chunk) {
Ok(is_voice) => {
self.vad_buffer.push(*is_voice);
#[cfg(feature = "debug-voice-activity-data")]
AudioSink::dump_vad(&is_voice, &mut self.vad_file);
},
_ => return Err(VoiceDetectionError)
}
#[cfg(feature = "debug-sample-data")]
AudioSink::dump_samples(chunk, &mut self.sample_file);
Ok(())
}
pub fn close(self: &mut AudioSink) -> Result<(), AudioSinkError> {
if self.state == AudioSinkState::Open {
let buf_len = self.sample_buffer.len();
if buf_len > 0 {
let chunk = &mut vec![0i16; self.vad.chunk_size];
chunk[..buf_len].clone_from_slice(self.sample_buffer.as_slice());
self.process_chunk(chunk.as_slice())?;
}
self.state = AudioSinkState::Closed
}
Ok(())
}
pub fn voice_activity(self: &mut Self) -> VoiceActivity {
let _ = self.close();
VoiceActivity { data: self.vad_buffer.clone(), chunk_millis: Vad::expected_chunk_millis() }
}
pub fn expected_sample_rate() -> usize {
Vad::expected_sample_rate()
}
#[cfg(any(feature = "debug-sample-data", feature = "debug-voice-activity-data"))]
fn create_debug_file(filename: &str) -> File {
let mut path = match std::env::var_os("LIBALASS_DEBUG_DATA_DIR") {
Some(dir) => PathBuf::from(dir),
None => match std::env::current_dir() {
Ok(dir) => dir,
Err(e) => panic!("[alass-util] Unable to obtain current working directory in order to dump raw sample data! (msg='{}')", e)
}
};
path.push(String::from(filename));
File::create(path).unwrap()
}
#[cfg(feature = "debug-sample-data")]
fn dump_samples(chunk: &[i16], file: &mut File) {
let bytes: &[u8] = unsafe { slice::from_raw_parts(chunk.as_ptr() as *const u8, chunk.len()*2) };
match file.write_all(bytes) {
Ok(_) => (),
Err(e) => panic!("[libalass] Error writing raw sample data to file! (msg='{}')", e)
}
}
#[cfg(feature = "debug-voice-activity-data")]
fn dump_vad(is_voice: &bool, file: &mut File) {
let bytes: &[u8] = &[ if *is_voice { 1_u8 } else { 0u8 } ];
match file.write_all(bytes) {
Ok(_) => (),
Err(e) => panic!("[libalass] Error writing voice activity data to file! (msg='{}')", e)
}
}
pub fn load_sample_data(filename: &str) -> Result<Vec<i16>, io::Error> {
let file = File::open(String::from(filename))?;
let bytes = &mut vec![];
BufReader::new(file).read_to_end(bytes)?;
let samples = bytes.as_slice().chunks_exact(2).map(LittleEndian::read_i16).collect();
Ok(samples)
}
pub fn load_vad_data(filename: &str) -> Result<Vec<bool>, io::Error> {
let file = File::open(String::from(filename))?;
let bytes = &mut vec![];
BufReader::new(file).read_to_end(bytes)?;
let vad_data = bytes.iter().map(|b| *b != 0).collect();
Ok(vad_data)
}
}
#[derive(PartialEq)]
pub enum AudioSinkState { Open, Closed }
#[derive(Debug)]
pub enum AudioSinkError {
SinkClosed,
SinkOpen,
VoiceDetectionError
}
impl Error for AudioSinkError {}
impl fmt::Display for AudioSinkError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
AudioSinkError::SinkClosed => write!(f, "Cannot write samples to sink after it's been closed"),
AudioSinkError::SinkOpen => write!(f, "Cannot cannot access voice-activity data until sink has been closed"),
AudioSinkError::VoiceDetectionError => write!(f, "An error occurred during voice-detection")
}
}
}