2022-05-19 21:45:25 +08:00
|
|
|
use std::{
|
|
|
|
sync::{Arc, Mutex},
|
|
|
|
time::Instant,
|
|
|
|
};
|
|
|
|
|
2022-07-05 22:17:34 +08:00
|
|
|
use hbb_common::{log, message_proto::{VideoFrame, video_frame}};
|
2022-05-19 21:45:25 +08:00
|
|
|
|
|
|
|
const MAX_LATENCY: i64 = 500;
|
|
|
|
const MIN_LATENCY: i64 = 100;
|
|
|
|
|
2022-05-28 03:56:42 +08:00
|
|
|
/// Latency controller for syncing audio with the video stream.
|
|
|
|
/// Only sync the audio to video, not the other way around.
|
2022-05-19 21:45:25 +08:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct LatencyController {
|
|
|
|
last_video_remote_ts: i64, // generated on remote deivce
|
|
|
|
update_time: Instant,
|
|
|
|
allow_audio: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for LatencyController {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
last_video_remote_ts: Default::default(),
|
|
|
|
update_time: Instant::now(),
|
|
|
|
allow_audio: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LatencyController {
|
2022-05-28 03:56:42 +08:00
|
|
|
/// Create a new latency controller.
|
2022-05-19 21:45:25 +08:00
|
|
|
pub fn new() -> Arc<Mutex<LatencyController>> {
|
|
|
|
Arc::new(Mutex::new(LatencyController::default()))
|
|
|
|
}
|
|
|
|
|
2022-05-28 03:56:42 +08:00
|
|
|
/// Update the latency controller with the latest video timestamp.
|
2022-05-19 21:45:25 +08:00
|
|
|
pub fn update_video(&mut self, timestamp: i64) {
|
|
|
|
self.last_video_remote_ts = timestamp;
|
|
|
|
self.update_time = Instant::now();
|
|
|
|
}
|
|
|
|
|
2022-05-28 03:56:42 +08:00
|
|
|
/// Check if the audio should be played based on the current latency.
|
2022-05-19 21:45:25 +08:00
|
|
|
pub fn check_audio(&mut self, timestamp: i64) -> bool {
|
2022-05-28 03:56:42 +08:00
|
|
|
// Compute audio latency.
|
2022-05-20 00:22:43 +08:00
|
|
|
let expected = self.update_time.elapsed().as_millis() as i64 + self.last_video_remote_ts;
|
2022-05-19 21:45:25 +08:00
|
|
|
let latency = expected - timestamp;
|
2022-05-28 03:56:42 +08:00
|
|
|
// Set MAX and MIN, avoid fixing too frequently.
|
2022-05-19 21:45:25 +08:00
|
|
|
if self.allow_audio {
|
|
|
|
if latency.abs() > MAX_LATENCY {
|
|
|
|
log::debug!("LATENCY > {}ms cut off, latency:{}", MAX_LATENCY, latency);
|
|
|
|
self.allow_audio = false;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if latency.abs() < MIN_LATENCY {
|
|
|
|
log::debug!("LATENCY < {}ms resume, latency:{}", MIN_LATENCY, latency);
|
|
|
|
self.allow_audio = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.allow_audio
|
|
|
|
}
|
|
|
|
}
|
2022-07-05 22:17:34 +08:00
|
|
|
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
|
|
pub enum CodecFormat {
|
|
|
|
VP9,
|
|
|
|
H264,
|
|
|
|
H265,
|
|
|
|
Unknown,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&VideoFrame> for CodecFormat {
|
|
|
|
fn from(it: &VideoFrame) -> Self {
|
|
|
|
match it.union {
|
2022-07-14 17:20:01 +08:00
|
|
|
Some(video_frame::Union::Vp9s(_)) => CodecFormat::VP9,
|
|
|
|
Some(video_frame::Union::H264s(_)) => CodecFormat::H264,
|
|
|
|
Some(video_frame::Union::H265s(_)) => CodecFormat::H265,
|
2022-07-05 22:17:34 +08:00
|
|
|
_ => CodecFormat::Unknown,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ToString for CodecFormat {
|
|
|
|
fn to_string(&self) -> String {
|
|
|
|
match self {
|
|
|
|
CodecFormat::VP9 => "VP9".into(),
|
|
|
|
CodecFormat::H264 => "H264".into(),
|
|
|
|
CodecFormat::H265 => "H265".into(),
|
|
|
|
CodecFormat::Unknown => "Unknow".into(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|