1 #![doc = include_str!("../README.md")]
3 use codec2
::{Codec2
, Codec2Mode
};
4 use cpal
::traits
::DeviceTrait
;
5 use cpal
::traits
::HostTrait
;
6 use cpal
::traits
::StreamTrait
;
7 use cpal
::{Sample
, SampleFormat
, SampleRate
};
9 use m17app
::adapter
::StreamAdapter
;
10 use m17app
::app
::TxHandle
;
11 use m17app
::error
::AdapterError
;
12 use m17app
::link_setup
::LinkSetup
;
13 use m17app
::link_setup
::M17Address
;
14 use m17app
::StreamFrame
;
15 use rubato
::Resampler
;
16 use rubato
::SincFixedIn
;
17 use rubato
::SincInterpolationParameters
;
18 use std
::collections
::VecDeque
;
22 use std
::path
::PathBuf
;
24 mpsc
::{channel
, Receiver
, Sender
},
27 use std
::time
::Duration
;
28 use std
::time
::Instant
;
33 /// Write one or more 8-byte chunks of 3200-bit Codec2 to a raw S16LE file
34 /// and return the samples.
35 pub fn decode_codec2
<P
: AsRef
<Path
>>(data
: &[u8], out_path
: P
) -> Vec
<i16> {
36 let codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
37 let var_name
= codec2
;
38 let mut codec
= var_name
;
39 let mut all_samples
: Vec
<i16> = vec
![];
40 for i
in 0..(data
.len() / 8) {
41 let mut samples
= vec
![0; codec
.samples_per_frame()];
42 codec
.decode(&mut samples
, &data
[i
* 8..((i
+ 1) * 8)]);
43 all_samples
.append(&mut samples
);
45 let mut speech_out
= File
::create(out_path
).unwrap
();
46 for b
in &all_samples
{
47 speech_out
.write_all(&b
.to_le_bytes()).unwrap
();
52 /// Subscribes to M17 streams and attempts to play the decoded Codec2
53 pub struct Codec2Adapter
{
54 state
: Arc
<Mutex
<AdapterState
>>,
55 output_card
: Option
<String
>,
59 pub fn new() -> Self {
61 state
: Arc
::new(Mutex
::new(AdapterState
{
63 out_buf
: VecDeque
::new(),
64 codec2
: Codec2
::new(Codec2Mode
::MODE_3200
),
72 pub fn set_output_card
<S
: Into
<String
>>(&mut self, card_name
: S
) {
73 self.output_card
= Some(card_name
.into
());
77 impl Default
for Codec2Adapter
{
78 fn default() -> Self {
85 /// Circular buffer of output samples for playback
86 out_buf
: VecDeque
<i16>,
88 end_tx
: Option
<Sender
<()>>,
89 resampler
: Option
<SincFixedIn
<f32>>,
92 impl StreamAdapter
for Codec2Adapter
{
93 fn start(&self, handle
: TxHandle
) -> Result
<(), AdapterError
> {
94 self.state
.lock().unwrap
().tx
= Some(handle
);
96 let (end_tx
, end_rx
) = channel();
97 let (setup_tx
, setup_rx
) = channel();
98 let state
= self.state
.clone();
99 let output_card
= self.output_card
.clone();
100 std
::thread
::spawn(move || stream_thread(end_rx
, setup_tx
, state
, output_card
));
101 self.state
.lock().unwrap
().end_tx
= Some(end_tx
);
102 // Propagate any errors arising in the thread
103 let sample_rate
= setup_rx
.recv()??
;
104 debug
!("selected codec2 output sample rate {sample_rate}");
105 if sample_rate
!= 8000 {
106 let params
= SincInterpolationParameters
{
109 oversampling_factor
: 256,
110 interpolation
: rubato
::SincInterpolationType
::Cubic
,
111 window
: rubato
::WindowFunction
::BlackmanHarris2
,
114 self.state
.lock().unwrap
().resampler
=
115 Some(SincFixedIn
::new(sample_rate
as f64 / 8000f64, 1.0, params
, 160, 1).unwrap
());
120 fn close(&self) -> Result
<(), AdapterError
> {
121 let mut state
= self.state
.lock().unwrap
();
127 fn stream_began(&self, _link_setup
: LinkSetup
) {
128 // for now we will assume:
130 // - data type is Voice (Codec2 3200), not Voice+Data
131 // TODO: is encryption handled here or in M17App, such that we get a decrypted stream?
132 // TODO: handle the Voice+Data combination with Codec2 1600
133 self.state
.lock().unwrap
().codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
136 fn stream_data(&self, _frame_number
: u16, _is_final
: bool
, data
: Arc
<[u8; 16]>) {
137 let mut state
= self.state
.lock().unwrap
();
138 for encoded
in data
.chunks(8) {
139 if state
.out_buf
.len() < 8192 {
140 let mut samples
= [i16::EQUILIBRIUM
; 160]; // while assuming 3200
141 state
.codec2
.decode(&mut samples
, encoded
);
142 if let Some(resampler
) = state
.resampler
.as_mut() {
143 let samples_f
: Vec
<f32> =
144 samples
.iter
().map(|s
| *s
as f32 / 16384.0f32).collect();
145 let res
= resampler
.process(&vec
![samples_f
], None
).unwrap
();
147 state
.out_buf
.push_back((s
* 16383.0f32) as i16);
150 // TODO: maybe get rid of VecDeque so we can decode directly into ring buffer?
152 state
.out_buf
.push_back(s
);
156 debug
!("out_buf overflow");
162 fn output_cb(data
: &mut [i16], state
: &Mutex
<AdapterState
>, channels
: u16) {
163 let mut state
= state
.lock().unwrap
();
164 for d
in data
.chunks_mut(channels
as usize) {
165 d
.fill
(state
.out_buf
.pop_front().unwrap
_or
(i16::EQUILIBRIUM
));
169 /// Create and manage the stream from a dedicated thread since it's `!Send`
172 setup_tx
: Sender
<Result
<u32, AdapterError
>>,
173 state
: Arc
<Mutex
<AdapterState
>>,
174 output_card
: Option
<String
>,
176 let host
= cpal
::default_host();
177 let device
= if let Some(output_card
) = output_card
{
178 // TODO: more error handling for unwraps
182 .find
(|d
| d
.name().unwrap
() == output_card
)
186 let _
= setup_tx
.send(Err(M17Codec2Error
::CardUnavailable(output_card
).into
()));
191 match host
.default_output_device() {
194 let _
= setup_tx
.send(Err(M17Codec2Error
::DefaultCardUnavailable
.into
()));
199 let card_name
= device
.name().unwrap
();
200 let mut configs
= match device
.supported_output_configs() {
203 let _
= setup_tx
.send(Err(
204 M17Codec2Error
::OutputConfigsUnavailable(card_name
, e
).into
()
209 let config
= match configs
.find
(|c
| {
210 (c
.channels() == 1 || c
.channels() == 2) && c
.sample_format() == SampleFormat
::I16
214 let _
= setup_tx
.send(Err(
215 M17Codec2Error
::SupportedOutputUnavailable(card_name
).into
()
221 let target_sample_rate
=
222 if config
.min_sample_rate().0 <= 8000 && config
.max_sample_rate().0 >= 8000 {
225 config
.max_sample_rate().0
227 let channels
= config
.channels();
229 let config
= config
.with_sample_rate(SampleRate(target_sample_rate
));
230 let stream
= match device
.build_output_stream(
232 move |data
: &mut [i16], _info
: &cpal
::OutputCallbackInfo
| {
233 output_cb(data
, &state
, channels
);
236 // trigger end_tx here? always more edge cases
237 debug
!("error occurred in codec2 playback: {e:?}");
243 let _
= setup_tx
.send(Err(
244 M17Codec2Error
::OutputStreamBuildError(card_name
, e
).into
()
249 match stream
.play() {
252 let _
= setup_tx
.send(Err(
253 M17Codec2Error
::OutputStreamPlayError(card_name
, e
).into
()
258 let _
= setup_tx
.send(Ok(target_sample_rate
));
260 // it seems concrete impls of Stream have a Drop implementation that will handle termination
263 /// Transmits a wave file as an M17 stream
264 pub struct WavePlayer
;
267 /// Plays a wave file (blocking).
269 /// * `path`: wave file to transmit, must be 8 kHz mono and 16-bit LE
270 /// * `tx`: a `TxHandle` obtained from an `M17App`
271 /// * `source`: address of transmission source
272 /// * `destination`: address of transmission destination
273 /// * `channel_access_number`: from 0 to 15, usually 0
278 destination
: &M17Address
,
279 channel_access_number
: u8,
281 let mut reader
= hound
::WavReader
::open(path
).unwrap
();
282 let mut samples
= reader
.samples
::<i16>();
284 let mut codec
= Codec2
::new(Codec2Mode
::MODE_3200
);
285 let mut in_buf
= [0i16; 160];
286 let mut out_buf
= [0u8; 16];
287 let mut lsf_chunk
: usize = 0;
288 const TICK
: Duration
= Duration
::from_millis(40);
289 let mut next_tick
= Instant
::now() + TICK
;
290 let mut frame_number
= 0;
292 let mut setup
= LinkSetup
::new_voice(source
, destination
);
293 setup
.set_channel_access_number(channel_access_number
);
294 tx
.transmit_stream_start(&setup
);
297 let mut last_one
= false;
298 for out
in out_buf
.chunks_mut(8) {
299 for i
in in_buf
.iter
_m
ut
() {
300 let sample
= match samples
.next() {
301 Some(Ok(sample
)) => sample
,
309 codec
.encode(out
, &in_buf
);
311 tx
.transmit_stream_next(&StreamFrame
{
312 lich_idx
: lsf_chunk
as u8,
313 lich_part
: setup
.lich_part(lsf_chunk
as u8),
315 end_of_stream
: last_one
,
316 stream_data
: out_buf
,
319 lsf_chunk
= (lsf_chunk
+ 1) % 6;
325 std
::thread
::sleep(next_tick
.duration_since(Instant
::now()));
331 #[derive(Debug, Error)]
332 pub enum M17Codec2Error
{
333 #[error("selected card '{0}' does not exist or is in use")]
334 CardUnavailable(String
),
336 #[error("default output card is unavailable")]
337 DefaultCardUnavailable
,
339 #[error("selected card '{0}' failed to list available output configs: '{1}'")]
340 OutputConfigsUnavailable(String
, #[source] cpal::SupportedStreamConfigsError),
342 #[error("selected card '{0}' did not offer a compatible output config type, either due to hardware limitations or because it is currently in use")]
343 SupportedOutputUnavailable(String
),
345 #[error("selected card '{0}' was unable to build an output stream: '{1}'")]
346 OutputStreamBuildError(String
, #[source] cpal::BuildStreamError),
348 #[error("selected card '{0}' was unable to play an output stream: '{1}'")]
349 OutputStreamPlayError(String
, #[source] cpal::PlayStreamError),