1 use codec2
::{Codec2
, Codec2Mode
};
2 use cpal
::traits
::DeviceTrait
;
3 use cpal
::traits
::HostTrait
;
4 use cpal
::traits
::StreamTrait
;
5 use cpal
::SampleFormat
;
8 use m17app
::adapter
::StreamAdapter
;
9 use m17app
::app
::TxHandle
;
10 use m17app
::error
::AdapterError
;
11 use m17app
::link_setup
::LinkSetup
;
12 use m17app
::link_setup
::M17Address
;
13 use m17app
::StreamFrame
;
14 use rubato
::Resampler
;
15 use rubato
::SincFixedOut
;
16 use rubato
::SincInterpolationParameters
;
17 use std
::path
::PathBuf
;
19 use std
::sync
::mpsc
::channel
;
22 use std
::time
::Duration
;
23 use std
::time
::Instant
;
25 use crate::M17Codec2Error
;
27 /// Transmits a wave file as an M17 stream
28 pub struct WavePlayer
;
31 /// Plays a wave file (blocking).
33 /// * `path`: wave file to transmit, must be 8 kHz mono and 16-bit LE
34 /// * `tx`: a `TxHandle` obtained from an `M17App`
35 /// * `source`: address of transmission source
36 /// * `destination`: address of transmission destination
37 /// * `channel_access_number`: from 0 to 15, usually 0
42 destination
: &M17Address
,
43 channel_access_number
: u8,
45 let mut reader
= hound
::WavReader
::open(path
).unwrap
();
46 let mut samples
= reader
.samples
::<i16>();
48 let mut codec
= Codec2
::new(Codec2Mode
::MODE_3200
);
49 let mut in_buf
= [0i16; 160];
50 let mut out_buf
= [0u8; 16];
51 let mut lsf_chunk
: usize = 0;
52 const TICK
: Duration
= Duration
::from_millis(40);
53 let mut next_tick
= Instant
::now() + TICK
;
54 let mut frame_number
= 0;
56 let mut setup
= LinkSetup
::new_voice(source
, destination
);
57 setup
.set_channel_access_number(channel_access_number
);
58 tx
.transmit_stream_start(&setup
);
61 let mut last_one
= false;
62 for out
in out_buf
.chunks_mut(8) {
63 for i
in in_buf
.iter
_m
ut
() {
64 let sample
= match samples
.next() {
65 Some(Ok(sample
)) => sample
,
73 codec
.encode(out
, &in_buf
);
75 tx
.transmit_stream_next(&StreamFrame
{
76 lich_idx
: lsf_chunk
as u8,
77 lich_part
: setup
.lich_part(lsf_chunk
as u8),
79 end_of_stream
: last_one
,
83 lsf_chunk
= (lsf_chunk
+ 1) % 6;
89 std
::thread
::sleep(next_tick
.duration_since(Instant
::now()));
95 /// Control transmissions into a Codec2TxAdapter
98 tx
: mpsc
::Sender
<Event
>,
102 pub fn set_ptt(&self, ptt
: bool
) {
103 let _
= self.tx
.send(if ptt
{ Event
::PttOn
} else { Event
::PttOff
});
107 /// Use a microphone and local PTT to transmit Codec2 voice data into an M17 channel.
108 pub struct Codec2TxAdapter
{
109 input_card
: Option
<String
>,
110 event_tx
: mpsc
::Sender
<Event
>,
111 event_rx
: Mutex
<Option
<mpsc
::Receiver
<Event
>>>,
113 destination
: M17Address
,
116 impl Codec2TxAdapter
{
117 pub fn new(source
: M17Address
, destination
: M17Address
) -> Self {
118 let (event_tx
, event_rx
) = mpsc
::channel();
122 event_rx
: Mutex
::new(Some(event_rx
)),
128 pub fn set_input_card
<S
: Into
<String
>>(&mut self, card_name
: S
) {
129 self.inp
ut
_card
= Some(card_name
.into
());
132 pub fn ptt(&self) -> Ptt
{
134 tx
: self.event_tx
.clone(),
138 /// List sound cards supported for audio input.
140 /// M17RT will handle any card with 1 or 2 channels and 16-bit output.
141 pub fn supported_input_cards() -> Vec
<String
> {
142 let mut out
= vec
![];
143 let host
= cpal
::default_host();
144 let Ok(input_devices
) = host
.inp
ut
_dev
ices
() else {
147 for d
in input_devices
{
148 let Ok(mut configs
) = d
.supported_input_configs() else {
151 if configs
.any(|config
| {
152 (config
.channels() == 1 || config
.channels() == 2)
153 && config
.sample_format() == SampleFormat
::I16
155 let Ok(name
) = d
.name() else {
169 MicSamples(Arc
<[i16]>),
173 impl StreamAdapter
for Codec2TxAdapter
{
174 fn start(&self, handle
: TxHandle
) -> Result
<(), AdapterError
> {
175 let Some(event_rx
) = self.event_rx
.lock().unwrap
().take() else {
176 return Err(M17Codec2Error
::RepeatStart
.into
());
178 let event_tx
= self.event_tx
.clone();
179 let (setup_tx
, setup_rx
) = channel();
180 let input_card
= self.inp
ut
_card
.clone();
181 let from
= self.source
.clone();
182 let to
= self.destination
.clone();
183 std
::thread
::spawn(move || {
184 stream_thread(event_tx
, event_rx
, setup_tx
, input_card
, handle
, from
, to
)
186 let sample_rate
= setup_rx
.recv()??
;
187 debug
!("selected codec2 microphone sample rate {sample_rate}");
192 fn close(&self) -> Result
<(), AdapterError
> {
193 let _
= self.event_tx
.send(Event
::Close
);
197 fn stream_began(&self, _link_setup
: LinkSetup
) {
198 // not interested in incoming transmissions
201 fn stream_data(&self, _frame_number
: u16, _is_final
: bool
, _data
: Arc
<[u8; 16]>) {
202 // not interested in incoming transmissions
204 // the only reason this is an adapter at all is for future "transmission aborted" feedback
205 // when that's implemented by m17app
210 event_tx
: mpsc
::Sender
<Event
>,
211 event_rx
: mpsc
::Receiver
<Event
>,
212 setup_tx
: mpsc
::Sender
<Result
<u32, AdapterError
>>,
213 input_card
: Option
<String
>,
216 destination
: M17Address
,
218 let host
= cpal
::default_host();
219 let device
= if let Some(input_card
) = input_card
{
220 // TODO: more error handling for unwraps
224 .find
(|d
| d
.name().unwrap
() == input_card
)
228 let _
= setup_tx
.send(Err(M17Codec2Error
::CardUnavailable(input_card
).into
()));
233 match host
.default_input_device() {
236 let _
= setup_tx
.send(Err(M17Codec2Error
::DefaultCardUnavailable
.into
()));
241 let card_name
= device
.name().unwrap
();
242 let mut configs
= match device
.supported_input_configs() {
245 let _
= setup_tx
.send(Err(
246 M17Codec2Error
::InputConfigsUnavailable(card_name
, e
).into
()
251 // TODO: rank these by most favourable, same for rx
252 let config
= match configs
.find
(|c
| {
253 (c
.channels() == 1 || c
.channels() == 2) && c
.sample_format() == SampleFormat
::I16
257 let _
= setup_tx
.send(Err(
258 M17Codec2Error
::SupportedInputUnavailable(card_name
).into
()
264 let target_sample_rate
=
265 if config
.min_sample_rate().0 <= 8000 && config
.max_sample_rate().0 >= 8000 {
268 config
.min_sample_rate().0
270 let channels
= config
.channels();
272 let mut acc
: Box
<dyn Accumulator
> = if target_sample_rate
!= 8000 {
273 Box
::new(ResamplingAccumulator
::new(target_sample_rate
as f64))
275 Box
::new(DirectAccumulator
::new())
278 let config
= config
.with_sample_rate(SampleRate(target_sample_rate
));
279 let stream
= match device
.build_input_stream(
281 move |data
: &[i16], _info
: &cpal
::InputCallbackInfo
| {
282 let mut samples
= vec
![];
283 for d
in data
.chunks(channels
as usize) {
284 // if we were given multi-channel input we'll pick the first channel
285 // TODO: configurable?
288 let _
= event_tx
.send(Event
::MicSamples(samples
.into
()));
292 debug
!("error occurred in codec2 recording: {e:?}");
298 let _
= setup_tx
.send(Err(
299 M17Codec2Error
::InputStreamBuildError(card_name
, e
).into
()
305 let _
= setup_tx
.send(Ok(target_sample_rate
));
306 let mut state
= State
::Idle
;
307 let mut codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
308 let link_setup
= LinkSetup
::new_voice(&source
, &destination
);
309 let mut lich_idx
= 0;
310 let mut frame_number
= 0;
313 while let Ok(ev
) = event_rx
.recv() {
318 match stream
.play() {
321 // TODO: report M17Codec2Error::InputStreamPlayError(card_name, e).into()
326 codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
327 state
= State
::StartTransmitting
;
329 State
::StartTransmitting
=> {}
330 State
::Transmitting
=> {}
331 State
::Ending
=> state
= State
::EndingWithPttRestart
,
332 State
::EndingWithPttRestart
=> {}
335 Event
::PttOff
=> match state
{
337 State
::StartTransmitting
=> state
= State
::Idle
,
338 State
::Transmitting
=> state
= State
::Ending
,
340 State
::EndingWithPttRestart
=> state
= State
::Ending
,
342 Event
::MicSamples(samples
) => {
345 State
::StartTransmitting
346 | State
::Transmitting
348 | State
::EndingWithPttRestart
=> {
349 acc
.handle_samples(&samples
);
350 while let Some(frame
) = acc
.try_next_frame() {
351 let mut stream_data
= [0u8; 16];
352 codec2
.encode(&mut stream_data
[0..8], &frame
[0..160]);
353 codec2
.encode(&mut stream_data
[8..16], &frame
[160..320]);
355 if state
== State
::StartTransmitting
{
356 handle
.transmit_stream_start(&link_setup
);
359 state
= State
::Transmitting
;
362 let end_of_stream
= state
!= State
::Transmitting
;
363 handle
.transmit_stream_next(&StreamFrame
{
365 lich_part
: link_setup
.lich_part(lich_idx
),
371 lich_idx
= (lich_idx
+ 1) % 6;
378 if state
== State
::Ending
{
379 // when finished sending final stream frame
380 let _
= stream
.pause();
384 if state
== State
::EndingWithPttRestart
{
386 codec2
= Codec2
::new(Codec2Mode
::MODE_3200
);
387 state
= State
::StartTransmitting
;
393 // assume PTT etc. will clean up itself responsibly on close
400 #[derive(Debug, PartialEq, Eq)]
404 /// PTT engaged but we are collecting the first full frame of audio data before starting the stream
406 /// Streaming voice frames
408 /// PTT disengaged; we are collecting the next frame of audio to use as a final frame
410 /// PTT was re-enaged while ending; we will send final frame then go back to StartTransmitting
411 EndingWithPttRestart
,
414 fn resampler_params() -> SincInterpolationParameters
{
415 SincInterpolationParameters
{
418 oversampling_factor
: 128,
419 interpolation
: rubato
::SincInterpolationType
::Cubic
,
420 window
: rubato
::WindowFunction
::BlackmanHarris2
,
425 fn handle_samples(&mut self, samples
: &[i16]);
426 /// Return 320 samples, enough for two Codec2 frames
427 fn try_next_frame(&mut self) -> Option
<Vec
<i16>>;
431 struct DirectAccumulator
{
435 impl DirectAccumulator
{
437 Self { buffer
: Vec
::new() }
441 impl Accumulator
for DirectAccumulator
{
442 fn handle_samples(&mut self, samples
: &[i16]) {
443 self.buffer
.extend_from_slice(samples
);
446 fn try_next_frame(&mut self) -> Option
<Vec
<i16>> {
447 if self.buffer
.len() >= 320 {
448 let part
= self.buffer
.split_off(320);
449 Some(std
::mem
::replace(&mut self.buffer
, part
))
455 fn reset(&mut self) {
460 struct ResamplingAccumulator
{
463 resampler
: SincFixedOut
<f32>,
466 impl ResamplingAccumulator
{
467 fn new(input_rate
: f64) -> Self {
471 resampler
: make_resampler(input_rate
),
476 impl Accumulator
for ResamplingAccumulator
{
477 fn handle_samples(&mut self, samples
: &[i16]) {
478 self.buffer
.extend_from_slice(samples
);
481 fn try_next_frame(&mut self) -> Option
<Vec
<i16>> {
482 let required
= self.resampler
.inp
ut
_frames
_next
();
483 if self.buffer
.len() >= required
{
484 let mut part
= self.buffer
.split_off(required
);
485 std
::mem
::swap(&mut self.buffer
, &mut part
);
486 let samples_f
: Vec
<f32> = part
.iter
().map(|s
| *s
as f32 / 16384.0f32).collect();
487 let out
= self.resampler
.process(&[samples_f
], None
).unwrap
();
488 Some(out
[0].iter
().map(|s
| (*s
* 16383.0f32) as i16).collect())
494 fn reset(&mut self) {
496 self.resampler
= make_resampler(self.inp
ut
_rate
);
500 fn make_resampler(input_rate
: f64) -> SincFixedOut
<f32> {
501 // want 320 samples at a time to create 2x Codec2 frames per M17 Voice frame
502 SincFixedOut
::new(8000f64 / input_rate
, 1.0, resampler_params(), 320, 1).unwrap
()