Skip to content

Commit

Permalink
Merge pull request #366 from HEnquist/twoway_gadget_vol
Browse files Browse the repository at this point in the history
Twoway gadget vol
  • Loading branch information
HEnquist authored Sep 26, 2024
2 parents 24d1516 + 42e5c67 commit 8ee647e
Show file tree
Hide file tree
Showing 13 changed files with 180 additions and 30 deletions.
31 changes: 24 additions & 7 deletions backend_alsa.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,18 @@ but are supported by very few devices. Therefore these are checked last.
Please also see [Find valid playback and capture parameters](#find-valid-playback-and-capture-parameters).

### Linking volume control to device volume
It is possible to let CamillaDSP follow the a volume control of the capture device.
It is possible to let CamillaDSP link its volume and mute controls to controls on the capture device.
This is mostly useful when capturing from the USB Audio Gadget,
which provides a control named `PCM Capture Volume` that is controlled by the USB host.
which provides a volume control named `PCM Capture Volume`
and a mute control called `PCM Capture Switch` that are controlled by the USB host.

This does not alter the signal, and can be used to forward the volume setting from a player to CamillaDSP.
To enable this, set the `follow_volume_control` setting to the name of the volume control.
Any change of the volume then gets applied to the CamillaDSP main volume control.
This volume control does not alter the signal,
and can be used to forward the volume setting from a player to CamillaDSP.
To enable this, set the `link_volume_control` setting to the name of the volume control.
The corresponding setting for the mute control is `link_mute_control`.
Any change of the volume or mute then gets applied to the CamillaDSP main volume control.
The link works in both directions, so that volume and mute changes requested
over the websocket interface also get sent to the USB host.

The available controls for a device can be listed with `amixer`.
List controls for card 1:
Expand All @@ -198,16 +203,28 @@ List controls with values and more details:
amixer -c 1 contents
```

The chosen control should be one that does not affect the signal volume,
The chosen volume control should be one that does not affect the signal volume,
otherwise the volume gets applied twice.
It must also have a scale in decibel like in this example:
It must also have a scale in decibel, and take a single value (`values=1`).

Example:
```
numid=15,iface=MIXER,name='Master Playback Volume'
; type=INTEGER,access=rw---R--,values=1,min=0,max=87,step=0
: values=52
| dBscale-min=-65.25dB,step=0.75dB,mute=0
```

The mute control shoule be a _switch_, meaning that is has states `on` and `off`,
where `on` is not muted and `off` is muted.
It must also take a single value (`values=1`).

Example:
```
numid=6,iface=MIXER,name='PCM Capture Switch'
; type=BOOLEAN,access=rw------,values=1
: values=on
```

### Subscribe to Alsa control events
The Alsa capture device subscribes to control events from the USB Gadget and Loopback devices.
Expand Down
56 changes: 44 additions & 12 deletions src/alsadevice.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,17 @@ use crate::alsadevice_buffermanager::{
};
use crate::alsadevice_utils::{
find_elem, list_channels_as_text, list_device_names, list_formats_as_text,
list_samplerates_as_text, pick_preferred_format, process_events, state_desc, CaptureElements,
CaptureParams, CaptureResult, ElemData, FileDescriptors, PlaybackParams,
list_samplerates_as_text, pick_preferred_format, process_events, state_desc,
sync_linked_controls, CaptureElements, CaptureParams, CaptureResult, ElemData, FileDescriptors,
PlaybackParams,
};
use crate::helpers::PIRateController;
use crate::CommandMessage;
use crate::PrcFmt;
use crate::ProcessingState;
use crate::Res;
use crate::StatusMessage;
use crate::{CaptureStatus, PlaybackStatus};
use crate::{CaptureStatus, PlaybackStatus, ProcessingParameters};

lazy_static! {
static ref ALSA_MUTEX: Mutex<()> = Mutex::new(());
Expand Down Expand Up @@ -67,7 +68,8 @@ pub struct AlsaCaptureDevice {
pub stop_on_rate_change: bool,
pub rate_measure_interval: f32,
pub stop_on_inactive: bool,
pub follow_volume_control: Option<String>,
pub link_volume_control: Option<String>,
pub link_mute_control: Option<String>,
}

struct CaptureChannels {
Expand Down Expand Up @@ -227,7 +229,8 @@ fn capture_buffer(
hctl: &Option<HCtl>,
elems: &CaptureElements,
status_channel: &crossbeam_channel::Sender<StatusMessage>,
params: &CaptureParams,
params: &mut CaptureParams,
processing_params: &Arc<ProcessingParameters>,
) -> Res<CaptureResult> {
let capture_state = pcmdevice.state_raw();
if capture_state == alsa_sys::SND_PCM_STATE_XRUN as i32 {
Expand Down Expand Up @@ -270,9 +273,10 @@ fn capture_buffer(
return Ok(CaptureResult::Stalled);
}
if pollresult.ctl {
trace!("Got a control events");
trace!("Got a control event");
if let Some(c) = ctl {
let event_result = process_events(c, elems, status_channel, params);
let event_result =
process_events(c, elems, status_channel, params, processing_params);
match event_result {
CaptureResult::Done => return Ok(event_result),
CaptureResult::Stalled => debug!("Capture device is stalled"),
Expand Down Expand Up @@ -699,9 +703,10 @@ fn drain_check_eos(audio: &mpsc::Receiver<AudioMessage>) -> Option<AudioMessage>
fn capture_loop_bytes(
channels: CaptureChannels,
pcmdevice: &alsa::PCM,
params: CaptureParams,
mut params: CaptureParams,
mut resampler: Option<Box<dyn VecResampler<PrcFmt>>>,
buf_manager: &mut CaptureBufferManager,
processing_params: &Arc<ProcessingParameters>,
) {
let io = pcmdevice.io_bytes();
let pcminfo = pcmdevice.info().unwrap();
Expand All @@ -727,6 +732,7 @@ fn capture_loop_bytes(
if let Some(c) = &ctl {
c.subscribe_events(true).unwrap();
}

if let Some(h) = &hctl {
let ctl_fds = h.get().unwrap();
file_descriptors.fds.extend(ctl_fds.iter());
Expand All @@ -747,18 +753,36 @@ fn capture_loop_bytes(
"Capture Pitch 1000000",
);

capture_elements.find_elements(h, device, subdevice, &params.follow_volume_control);
capture_elements.find_elements(
h,
device,
subdevice,
&params.link_volume_control,
&params.link_mute_control,
);
if let Some(c) = &ctl {
if let Some(ref vol_elem) = capture_elements.volume {
let vol_db = vol_elem.read_volume_in_db(c);
info!("Using initial volume from Alsa: {:?}", vol_db);
if let Some(vol) = vol_db {
params.linked_volume_value = Some(vol);
channels
.status
.send(StatusMessage::SetVolume(vol))
.unwrap_or_default();
}
}
if let Some(ref mute_elem) = capture_elements.mute {
let active = mute_elem.read_as_bool();
info!("Using initial active switch from Alsa: {:?}", active);
if let Some(active_val) = active {
params.linked_mute_value = Some(!active_val);
channels
.status
.send(StatusMessage::SetMute(!active_val))
.unwrap_or_default();
}
}
}
}
if element_loopback.is_some() || element_uac2_gadget.is_some() {
Expand Down Expand Up @@ -883,7 +907,8 @@ fn capture_loop_bytes(
&hctl,
&capture_elements,
&channels.status,
&params,
&mut params,
processing_params,
);
match capture_res {
Ok(CaptureResult::Normal) => {
Expand Down Expand Up @@ -1012,6 +1037,7 @@ fn capture_loop_bytes(
break;
}
}
sync_linked_controls(processing_params, &mut params, &mut capture_elements, &ctl);
}
if let Some(h) = thread_handle {
match demote_current_thread_from_real_time(h) {
Expand Down Expand Up @@ -1144,6 +1170,7 @@ impl CaptureDevice for AlsaCaptureDevice {
status_channel: crossbeam_channel::Sender<StatusMessage>,
command_channel: mpsc::Receiver<CommandMessage>,
capture_status: Arc<RwLock<CaptureStatus>>,
processing_params: Arc<ProcessingParameters>,
) -> Res<Box<thread::JoinHandle<()>>> {
let devname = self.devname.clone();
let samplerate = self.samplerate;
Expand All @@ -1159,7 +1186,8 @@ impl CaptureDevice for AlsaCaptureDevice {
let stop_on_rate_change = self.stop_on_rate_change;
let rate_measure_interval = self.rate_measure_interval;
let stop_on_inactive = self.stop_on_inactive;
let follow_volume_control = self.follow_volume_control.clone();
let link_volume_control = self.link_volume_control.clone();
let link_mute_control = self.link_mute_control.clone();
let mut buf_manager = CaptureBufferManager::new(
chunksize as Frames,
samplerate as f32 / capture_samplerate as f32,
Expand Down Expand Up @@ -1206,7 +1234,10 @@ impl CaptureDevice for AlsaCaptureDevice {
stop_on_rate_change,
rate_measure_interval,
stop_on_inactive,
follow_volume_control,
link_volume_control,
link_mute_control,
linked_mute_value: None,
linked_volume_value: None,
};
let cap_channels = CaptureChannels {
audio: channel,
Expand All @@ -1219,6 +1250,7 @@ impl CaptureDevice for AlsaCaptureDevice {
cap_params,
resampler,
&mut buf_manager,
&processing_params,
);
}
Err(err) => {
Expand Down
Loading

0 comments on commit 8ee647e

Please sign in to comment.