#![doc = include_str!("../README.md")]
use codec2::{Codec2, Codec2Mode};
use cpal::traits::DeviceTrait;
use cpal::traits::HostTrait;
use cpal::traits::StreamTrait;
use cpal::{Sample, SampleFormat, SampleRate};
use log::debug;
use m17app::adapter::StreamAdapter;
use m17app::app::TxHandle;
use m17app::link_setup::LinkSetup;
use m17app::link_setup::M17Address;
use m17app::StreamFrame;
use std::collections::VecDeque;
use std::fs::File;
use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::sync::{
mpsc::{channel, Receiver, Sender},
Arc, Mutex,
};
use std::time::Duration;
use std::time::Instant;
pub fn decode_codec2<P: AsRef<Path>>(data: &[u8], out_path: P) -> Vec<i16> {
let codec2 = Codec2::new(Codec2Mode::MODE_3200);
let var_name = codec2;
let mut codec = var_name;
let mut all_samples: Vec<i16> = vec![];
for i in 0..(data.len() / 8) {
let mut samples = vec![0; codec.samples_per_frame()];
codec.decode(&mut samples, &data[i * 8..((i + 1) * 8)]);
all_samples.append(&mut samples);
}
let mut speech_out = File::create(out_path).unwrap();
for b in &all_samples {
speech_out.write_all(&b.to_le_bytes()).unwrap();
}
all_samples
}
pub struct Codec2Adapter {
state: Arc<Mutex<AdapterState>>,
output_card: String,
}
impl Codec2Adapter {
pub fn new() -> Self {
Self {
state: Arc::new(Mutex::new(AdapterState {
tx: None,
out_buf: VecDeque::new(),
codec2: Codec2::new(Codec2Mode::MODE_3200),
end_tx: None,
})),
output_card: "default".to_owned(),
}
}
}
impl Default for Codec2Adapter {
fn default() -> Self {
Self::new()
}
}
struct AdapterState {
tx: Option<TxHandle>,
out_buf: VecDeque<i16>,
codec2: Codec2,
end_tx: Option<Sender<()>>,
}
impl StreamAdapter for Codec2Adapter {
fn adapter_registered(&self, _id: usize, handle: TxHandle) {
self.state.lock().unwrap().tx = Some(handle);
let (end_tx, end_rx) = channel();
let state = self.state.clone();
let output_card = self.output_card.clone();
std::thread::spawn(move || stream_thread(end_rx, state, output_card));
self.state.lock().unwrap().end_tx = Some(end_tx);
}
fn adapter_removed(&self) {
let mut state = self.state.lock().unwrap();
state.tx = None;
state.end_tx = None;
}
fn tnc_started(&self) {}
fn tnc_closed(&self) {}
fn stream_began(&self, _link_setup: LinkSetup) {
self.state.lock().unwrap().codec2 = Codec2::new(Codec2Mode::MODE_3200);
}
fn stream_data(&self, _frame_number: u16, _is_final: bool, data: Arc<[u8; 16]>) {
let mut state = self.state.lock().unwrap();
for encoded in data.chunks(8) {
if state.out_buf.len() < 1024 {
let mut samples = [i16::EQUILIBRIUM; 160]; state.codec2.decode(&mut samples, encoded);
for s in samples {
state.out_buf.push_back(s);
}
} else {
debug!("out_buf overflow");
}
}
}
}
fn output_cb(data: &mut [i16], state: &Mutex<AdapterState>) {
let mut state = state.lock().unwrap();
for d in data {
*d = state.out_buf.pop_front().unwrap_or(i16::EQUILIBRIUM);
}
}
fn stream_thread(end: Receiver<()>, state: Arc<Mutex<AdapterState>>, output_card: String) {
let host = cpal::default_host();
let device = host
.output_devices()
.unwrap()
.find(|d| d.name().unwrap() == output_card)
.unwrap();
let mut configs = device.supported_output_configs().unwrap();
let config = configs
.find(|c| c.channels() == 1 && c.sample_format() == SampleFormat::I16)
.unwrap()
.with_sample_rate(SampleRate(8000));
let stream = device
.build_output_stream(
&config.into(),
move |data: &mut [i16], _info: &cpal::OutputCallbackInfo| {
output_cb(data, &state);
},
|e| {
debug!("error occurred in codec2 playback: {e:?}");
},
None,
)
.unwrap();
stream.play().unwrap();
let _ = end.recv();
}
pub struct WavePlayer;
impl WavePlayer {
pub fn play(
path: PathBuf,
tx: TxHandle,
source: &M17Address,
destination: &M17Address,
channel_access_number: u8,
) {
let mut reader = hound::WavReader::open(path).unwrap();
let mut samples = reader.samples::<i16>();
let mut codec = Codec2::new(Codec2Mode::MODE_3200);
let mut in_buf = [0i16; 160];
let mut out_buf = [0u8; 16];
let mut lsf_chunk: usize = 0;
const TICK: Duration = Duration::from_millis(40);
let mut next_tick = Instant::now() + TICK;
let mut frame_number = 0;
let mut setup = LinkSetup::new_voice(source, destination);
setup.set_channel_access_number(channel_access_number);
tx.transmit_stream_start(&setup);
loop {
let mut last_one = false;
for out in out_buf.chunks_mut(8) {
for i in in_buf.iter_mut() {
let sample = match samples.next() {
Some(Ok(sample)) => sample,
_ => {
last_one = true;
0
}
};
*i = sample;
}
codec.encode(out, &in_buf);
}
tx.transmit_stream_next(&StreamFrame {
lich_idx: lsf_chunk as u8,
lich_part: setup.lich_part(lsf_chunk as u8),
frame_number,
end_of_stream: last_one,
stream_data: out_buf,
});
frame_number += 1;
lsf_chunk = (lsf_chunk + 1) % 6;
if last_one {
break;
}
std::thread::sleep(next_tick.duration_since(Instant::now()));
next_tick += TICK;
}
}
}