fix(audio): restore live playback timing
This commit is contained in:
parent
3903eeb3ff
commit
b6cf15767d
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.11.31"
|
||||
version = "0.11.32"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -975,7 +975,7 @@ fn remote_failsafe_timeout_from_env() -> Duration {
|
||||
let millis = std::env::var("LESAVKA_INPUT_REMOTE_FAILSAFE_MS")
|
||||
.ok()
|
||||
.and_then(|raw| raw.parse::<u64>().ok())
|
||||
.unwrap_or(5_000);
|
||||
.unwrap_or(60_000);
|
||||
Duration::from_millis(millis)
|
||||
}
|
||||
|
||||
|
||||
@ -111,8 +111,8 @@ const LESAVKA_ICON_SEARCH_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/ass
|
||||
const LAUNCHER_DEFAULT_WIDTH: i32 = 1380;
|
||||
const LAUNCHER_DEFAULT_HEIGHT: i32 = 860;
|
||||
const OPERATIONS_RAIL_WIDTH: i32 = 288;
|
||||
const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 144;
|
||||
const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 256;
|
||||
const CAMERA_PREVIEW_VIEWPORT_HEIGHT: i32 = 108;
|
||||
const CAMERA_PREVIEW_VIEWPORT_WIDTH: i32 = 192;
|
||||
|
||||
pub fn build_launcher_view(
|
||||
app: >k::Application,
|
||||
@ -211,7 +211,7 @@ pub fn build_launcher_view(
|
||||
build_panel_with_action("Device Staging", Some(device_refresh_button.upcast_ref()));
|
||||
devices_panel.set_hexpand(true);
|
||||
devices_panel.set_vexpand(false);
|
||||
devices_panel.set_valign(gtk::Align::Start);
|
||||
devices_panel.set_valign(gtk::Align::Fill);
|
||||
devices_body.set_spacing(8);
|
||||
|
||||
let control_group = build_subgroup("Control Inputs");
|
||||
@ -321,7 +321,8 @@ pub fn build_launcher_view(
|
||||
let (preview_panel, preview_body) = build_panel("Device Testing");
|
||||
preview_panel.set_hexpand(true);
|
||||
preview_panel.set_vexpand(false);
|
||||
preview_panel.set_valign(gtk::Align::Start);
|
||||
preview_panel.set_valign(gtk::Align::Fill);
|
||||
preview_body.set_vexpand(true);
|
||||
preview_body.set_spacing(6);
|
||||
let camera_preview = gtk::Picture::new();
|
||||
camera_preview.set_can_shrink(false);
|
||||
@ -355,6 +356,8 @@ pub fn build_launcher_view(
|
||||
preview_body.append(&webcam_group);
|
||||
|
||||
let playback_group = build_subgroup("Mic Playback");
|
||||
playback_group.set_vexpand(true);
|
||||
playback_group.set_valign(gtk::Align::Fill);
|
||||
let playback_body = gtk::Box::new(gtk::Orientation::Vertical, 6);
|
||||
let playback_row = gtk::Box::new(gtk::Orientation::Horizontal, 8);
|
||||
playback_row.set_homogeneous(false);
|
||||
|
||||
@ -18,8 +18,7 @@ pub struct AudioOut {
|
||||
|
||||
#[derive(Default)]
|
||||
struct AudioTimeline {
|
||||
first_remote_pts_us: Option<u64>,
|
||||
last_local_pts_us: u64,
|
||||
last_remote_pts_us: Option<u64>,
|
||||
packets: u64,
|
||||
}
|
||||
|
||||
@ -39,6 +38,7 @@ impl AudioOut {
|
||||
aacparse ! avdec_aac ! \
|
||||
audioconvert ! audioresample ! \
|
||||
audio/x-raw,format=S16LE,channels=2,rate=48000 ! \
|
||||
level name=remote_audio_level interval=1000000000 message=true ! \
|
||||
queue max-size-time=400000000 max-size-bytes=0 max-size-buffers=0 ! {}",
|
||||
sink,
|
||||
);
|
||||
@ -49,6 +49,7 @@ impl AudioOut {
|
||||
queue max-size-time=500000000 max-size-bytes=0 max-size-buffers=0 ! \
|
||||
aacparse ! avdec_aac ! audioconvert ! audioresample ! \
|
||||
audio/x-raw,format=S16LE,channels=2,rate=48000 ! \
|
||||
level name=remote_audio_level interval=1000000000 message=true ! \
|
||||
queue max-size-time=400000000 max-size-bytes=0 max-size-buffers=0 ! {} \
|
||||
t. ! queue ! filesink location=/tmp/lesavka-audio.aac",
|
||||
sink,
|
||||
@ -93,10 +94,15 @@ impl AudioOut {
|
||||
w.error(),
|
||||
w.debug().unwrap_or_default()
|
||||
),
|
||||
Element(e) => debug!(
|
||||
"🔎 gst element message: {}",
|
||||
e.structure().map(|s| s.to_string()).unwrap_or_default()
|
||||
),
|
||||
Element(e) => {
|
||||
if let Some(structure) = e.structure() {
|
||||
if structure.name() == "level" {
|
||||
info!("🔊 decoded audio level {}", structure);
|
||||
} else {
|
||||
debug!("🔎 gst element message: {}", structure);
|
||||
}
|
||||
}
|
||||
}
|
||||
StateChanged(s) if s.current() == gst::State::Playing => {
|
||||
if msg.src().map(|s| s.is::<gst::Pipeline>()).unwrap_or(false) {
|
||||
info!("🔊 audio pipeline ▶️ (sink='{}')", sink);
|
||||
@ -123,28 +129,7 @@ impl AudioOut {
|
||||
}
|
||||
|
||||
pub fn push(&self, pkt: AudioPacket) {
|
||||
let mut buf = gst::Buffer::from_slice(pkt.data);
|
||||
if let Ok(mut timeline) = self.timeline.lock() {
|
||||
let base = timeline.first_remote_pts_us.get_or_insert(pkt.pts);
|
||||
let mut local_pts_us = pkt.pts.saturating_sub(*base);
|
||||
if local_pts_us < timeline.last_local_pts_us {
|
||||
local_pts_us = timeline.last_local_pts_us.saturating_add(1);
|
||||
}
|
||||
timeline.last_local_pts_us = local_pts_us;
|
||||
timeline.packets = timeline.packets.saturating_add(1);
|
||||
if timeline.packets <= 8 || timeline.packets % 600 == 0 {
|
||||
debug!(
|
||||
packet = timeline.packets,
|
||||
remote_pts_us = pkt.pts,
|
||||
local_pts_us,
|
||||
bytes = buf.size(),
|
||||
"🔊 audio packet queued"
|
||||
);
|
||||
}
|
||||
buf.get_mut()
|
||||
.unwrap()
|
||||
.set_pts(Some(gst::ClockTime::from_useconds(local_pts_us)));
|
||||
}
|
||||
let buf = live_audio_buffer(pkt, &self.timeline);
|
||||
#[cfg(not(coverage))]
|
||||
if let Err(e) = self.src.push_buffer(buf) {
|
||||
warn!("📉 AppSrc push failed: {e:?}");
|
||||
@ -157,6 +142,27 @@ impl AudioOut {
|
||||
}
|
||||
}
|
||||
|
||||
fn live_audio_buffer(pkt: AudioPacket, timeline: &Mutex<AudioTimeline>) -> gst::Buffer {
|
||||
let buf = gst::Buffer::from_slice(pkt.data);
|
||||
if let Ok(mut timeline) = timeline.lock() {
|
||||
let remote_gap_us = timeline
|
||||
.last_remote_pts_us
|
||||
.map(|last| pkt.pts.saturating_sub(last));
|
||||
timeline.last_remote_pts_us = Some(pkt.pts);
|
||||
timeline.packets = timeline.packets.saturating_add(1);
|
||||
if timeline.packets <= 8 || timeline.packets % 600 == 0 {
|
||||
debug!(
|
||||
packet = timeline.packets,
|
||||
remote_pts_us = pkt.pts,
|
||||
remote_gap_us,
|
||||
bytes = buf.size(),
|
||||
"🔊 audio packet queued for live appsrc timestamping"
|
||||
);
|
||||
}
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
impl Drop for AudioOut {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.pipeline.set_state(gst::State::Null);
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.11.31"
|
||||
version = "0.11.32"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -17,6 +17,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn banner_includes_version() {
|
||||
assert_eq!(banner("0.11.31"), "lesavka-common CLI (v0.11.31)");
|
||||
assert_eq!(banner("0.11.32"), "lesavka-common CLI (v0.11.32)");
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.11.31"
|
||||
version = "0.11.32"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -466,7 +466,7 @@ mod inputs_contract {
|
||||
with_var("LESAVKA_INPUT_REMOTE_FAILSAFE_MS", None::<&str>, || {
|
||||
assert_eq!(
|
||||
remote_failsafe_timeout_from_env(),
|
||||
Duration::from_millis(5_000)
|
||||
Duration::from_millis(60_000)
|
||||
);
|
||||
});
|
||||
with_var("LESAVKA_INPUT_REMOTE_FAILSAFE_MS", Some("0"), || {
|
||||
|
||||
@ -80,7 +80,7 @@ exit 0
|
||||
sinks,
|
||||
vec![(
|
||||
"alsa_output.usb-DAC_1234-00.analog-stereo".to_string(),
|
||||
"UNKNOWN".to_string()
|
||||
"DEFAULT".to_string()
|
||||
)]
|
||||
);
|
||||
let sink = pick_sink_element().expect("pick sink");
|
||||
@ -168,4 +168,21 @@ exit 0
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn live_audio_buffer_leaves_pts_for_appsrc_timestamping() {
|
||||
let _ = gst::init();
|
||||
let timeline = std::sync::Mutex::new(AudioTimeline::default());
|
||||
let buffer = live_audio_buffer(
|
||||
AudioPacket {
|
||||
id: 0,
|
||||
pts: 42_666,
|
||||
data: vec![0xFF, 0xF1, 0x50, 0x80, 0x00, 0x1F, 0xFC],
|
||||
},
|
||||
&timeline,
|
||||
);
|
||||
|
||||
assert_eq!(buffer.pts(), None);
|
||||
assert_eq!(timeline.lock().expect("timeline").packets, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user