1 use crate::M17Codec2Error
;
2 use codec2
::{Codec2
, Codec2Mode
};
3 use cpal
::traits
::DeviceTrait
;
4 use cpal
::traits
::HostTrait
;
5 use cpal
::traits
::StreamTrait
;
6 use cpal
::{Sample
, SampleFormat
, SampleRate
};
8 use m17app
::adapter
::StreamAdapter
;
9 use m17app
::app
::TxHandle
;
10 use m17app
::error
::AdapterError
;
11 use m17app
::link_setup
::LinkSetup
;
12 use rubato
::Resampler
;
13 use rubato
::SincFixedIn
;
14 use rubato
::SincInterpolationParameters
;
15 use std
::collections
::VecDeque
;
20 mpsc
::{channel
, Receiver
, Sender
},
24 /// Write one or more 8-byte chunks of 3200-bit Codec2 to a raw S16LE file
25 /// and return the samples.
26 pub fn decode_codec2
<P
: AsRef
<Path
>>(data
: &[u8], out_path
: P
) -> Vec
<i16> {
27 let codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
28 let var_name
= codec2
;
29 let mut codec
= var_name
;
30 let mut all_samples
: Vec
<i16> = vec
![];
31 for i
in 0..(data
.len() / 8) {
32 let mut samples
= vec
![0; codec
.samples_per_frame()];
33 codec
.decode(&mut samples
, &data
[i
* 8..((i
+ 1) * 8)]);
34 all_samples
.append(&mut samples
);
36 let mut speech_out
= File
::create(out_path
).unwrap
();
37 for b
in &all_samples
{
38 speech_out
.write_all(&b
.to_le_bytes()).unwrap
();
43 /// Subscribes to M17 streams and attempts to play the decoded Codec2
44 pub struct Codec2RxAdapter
{
45 state
: Arc
<Mutex
<AdapterState
>>,
46 output_card
: Option
<String
>,
49 impl Codec2RxAdapter
{
50 pub fn new() -> Self {
52 state
: Arc
::new(Mutex
::new(AdapterState
{
53 out_buf
: VecDeque
::new(),
54 codec2
: Codec2
::new(Codec2Mode
::MODE_3200
),
62 pub fn set_output_card
<S
: Into
<String
>>(&mut self, card_name
: S
) {
63 self.output_card
= Some(card_name
.into
());
66 /// List sound cards supported for audio output.
68 /// M17RT will handle any card with 1 or 2 channels and 16-bit output.
69 pub fn supported_output_cards() -> Vec
<String
> {
71 let host
= cpal
::default_host();
72 let Ok(output_devices
) = host
.output_devices() else {
75 for d
in output_devices
{
76 let Ok(mut configs
) = d
.supported_output_configs() else {
79 if configs
.any(|config
| {
80 (config
.channels() == 1 || config
.channels() == 2)
81 && config
.sample_format() == SampleFormat
::I16
83 let Ok(name
) = d
.name() else {
94 impl Default
for Codec2RxAdapter
{
95 fn default() -> Self {
100 struct AdapterState
{
101 /// Circular buffer of output samples for playback
102 out_buf
: VecDeque
<i16>,
104 end_tx
: Option
<Sender
<()>>,
105 resampler
: Option
<SincFixedIn
<f32>>,
108 impl StreamAdapter
for Codec2RxAdapter
{
109 fn start(&self, _handle
: TxHandle
) -> Result
<(), AdapterError
> {
110 let (end_tx
, end_rx
) = channel();
111 let (setup_tx
, setup_rx
) = channel();
112 let state
= self.state
.clone();
113 let output_card
= self.output_card
.clone();
114 std
::thread
::spawn(move || stream_thread(end_rx
, setup_tx
, state
, output_card
));
115 self.state
.lock().unwrap
().end_tx
= Some(end_tx
);
116 // Propagate any errors arising in the thread
117 let sample_rate
= setup_rx
.recv()??
;
118 debug
!("selected codec2 speaker sample rate {sample_rate}");
119 if sample_rate
!= 8000 {
120 let params
= SincInterpolationParameters
{
123 oversampling_factor
: 128,
124 interpolation
: rubato
::SincInterpolationType
::Cubic
,
125 window
: rubato
::WindowFunction
::BlackmanHarris2
,
128 self.state
.lock().unwrap
().resampler
=
129 Some(SincFixedIn
::new(sample_rate
as f64 / 8000f64, 1.0, params
, 160, 1).unwrap
());
134 fn close(&self) -> Result
<(), AdapterError
> {
135 let mut state
= self.state
.lock().unwrap
();
140 fn stream_began(&self, _link_setup
: LinkSetup
) {
141 // for now we will assume:
143 // - data type is Voice (Codec2 3200), not Voice+Data
144 // TODO: is encryption handled here or in M17App, such that we get a decrypted stream?
145 // TODO: handle the Voice+Data combination with Codec2 1600
146 self.state
.lock().unwrap
().codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
149 fn stream_data(&self, _frame_number
: u16, _is_final
: bool
, data
: Arc
<[u8; 16]>) {
150 let mut state
= self.state
.lock().unwrap
();
151 for encoded
in data
.chunks(8) {
152 if state
.out_buf
.len() < 8192 {
153 let mut samples
= [i16::EQUILIBRIUM
; 160]; // while assuming 3200
154 state
.codec2
.decode(&mut samples
, encoded
);
155 if let Some(resampler
) = state
.resampler
.as_mut() {
156 let samples_f
: Vec
<f32> =
157 samples
.iter
().map(|s
| *s
as f32 / 16384.0f32).collect();
158 let res
= resampler
.process(&[samples_f
], None
).unwrap
();
160 state
.out_buf
.push_back((s
* 16383.0f32) as i16);
163 // TODO: maybe get rid of VecDeque so we can decode directly into ring buffer?
165 state
.out_buf
.push_back(s
);
169 debug
!("out_buf overflow");
175 fn output_cb(data
: &mut [i16], state
: &Mutex
<AdapterState
>, channels
: u16) {
176 let mut state
= state
.lock().unwrap
();
177 for d
in data
.chunks_mut(channels
as usize) {
178 d
.fill
(state
.out_buf
.pop_front().unwrap
_or
(i16::EQUILIBRIUM
));
182 /// Create and manage the stream from a dedicated thread since it's `!Send`
185 setup_tx
: Sender
<Result
<u32, AdapterError
>>,
186 state
: Arc
<Mutex
<AdapterState
>>,
187 output_card
: Option
<String
>,
189 let host
= cpal
::default_host();
190 let device
= if let Some(output_card
) = output_card
{
191 // TODO: more error handling for unwraps
195 .find
(|d
| d
.name().unwrap
() == output_card
)
199 let _
= setup_tx
.send(Err(M17Codec2Error
::CardUnavailable(output_card
).into
()));
204 match host
.default_output_device() {
207 let _
= setup_tx
.send(Err(M17Codec2Error
::DefaultCardUnavailable
.into
()));
212 let card_name
= device
.name().unwrap
();
213 let mut configs
= match device
.supported_output_configs() {
216 let _
= setup_tx
.send(Err(
217 M17Codec2Error
::OutputConfigsUnavailable(card_name
, e
).into
()
222 let config
= match configs
.find
(|c
| {
223 (c
.channels() == 1 || c
.channels() == 2) && c
.sample_format() == SampleFormat
::I16
227 let _
= setup_tx
.send(Err(
228 M17Codec2Error
::SupportedOutputUnavailable(card_name
).into
()
234 let target_sample_rate
=
235 if config
.min_sample_rate().0 <= 8000 && config
.max_sample_rate().0 >= 8000 {
238 config
.min_sample_rate().0
240 let channels
= config
.channels();
242 let config
= config
.with_sample_rate(SampleRate(target_sample_rate
));
243 let stream
= match device
.build_output_stream(
245 move |data
: &mut [i16], _info
: &cpal
::OutputCallbackInfo
| {
246 output_cb(data
, &state
, channels
);
249 // trigger end_tx here? always more edge cases
250 debug
!("error occurred in codec2 playback: {e:?}");
256 let _
= setup_tx
.send(Err(
257 M17Codec2Error
::OutputStreamBuildError(card_name
, e
).into
()
262 match stream
.play() {
265 let _
= setup_tx
.send(Err(
266 M17Codec2Error
::OutputStreamPlayError(card_name
, e
).into
()
271 let _
= setup_tx
.send(Ok(target_sample_rate
));
273 // it seems concrete impls of Stream have a Drop implementation that will handle termination