upgrade to Retina 0.4.0
This commit is contained in:
parent
0d2cda5c18
commit
14f70ff4ce
|
@ -1654,9 +1654,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "retina"
|
||||
version = "0.3.10"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01358b10b0e442f1cbe1417a888698c88969bfef230290c0aaec65228238a8ca"
|
||||
checksum = "57bdeafed8d429e892754895f028852e666cdf77c3fb248c3780d70606e3e2c7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitreader",
|
||||
|
@ -1670,7 +1670,6 @@ dependencies = [
|
|||
"pin-project",
|
||||
"pretty-hex",
|
||||
"rand",
|
||||
"rtp-rs",
|
||||
"rtsp-types",
|
||||
"sdp-types",
|
||||
"smallvec",
|
||||
|
@ -1707,12 +1706,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtp-rs"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4ed274a5b3d36c4434cff6a4de1b42f43e64ae326b1cfa72d13d9037a314355"
|
||||
|
||||
[[package]]
|
||||
name = "rtsp-types"
|
||||
version = "0.0.3"
|
||||
|
|
|
@ -47,7 +47,7 @@ parking_lot = "0.12.0"
|
|||
password-hash = "0.3.2"
|
||||
protobuf = "3.0"
|
||||
reffers = "0.7.0"
|
||||
retina = "0.3.9"
|
||||
retina = "0.4.0"
|
||||
ring = "0.16.2"
|
||||
rusqlite = "0.27.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
|
|
@ -211,17 +211,15 @@ fn press_test_inner(
|
|||
transport: retina::client::Transport,
|
||||
) -> Result<String, Error> {
|
||||
let _enter = handle.enter();
|
||||
let stream = stream::OPENER.open(
|
||||
"test stream".to_owned(),
|
||||
url,
|
||||
retina::client::SessionOptions::default()
|
||||
.creds(if username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(retina::client::Credentials { username, password })
|
||||
})
|
||||
.transport(transport),
|
||||
)?;
|
||||
let options = stream::Options {
|
||||
session: retina::client::SessionOptions::default().creds(if username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(retina::client::Credentials { username, password })
|
||||
}),
|
||||
setup: retina::client::SetupOptions::default().transport(transport),
|
||||
};
|
||||
let stream = stream::OPENER.open("test stream".to_owned(), url, options)?;
|
||||
let video_sample_entry = stream.video_sample_entry();
|
||||
Ok(format!(
|
||||
"codec: {}\n\
|
||||
|
|
|
@ -15,18 +15,18 @@ use url::Url;
|
|||
|
||||
static RETINA_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(30);
|
||||
|
||||
pub struct Options {
|
||||
pub session: retina::client::SessionOptions,
|
||||
pub setup: retina::client::SetupOptions,
|
||||
}
|
||||
|
||||
/// Opens a RTSP stream. This is a trait for test injection.
|
||||
pub trait Opener: Send + Sync {
|
||||
/// Opens the given RTSP URL.
|
||||
///
|
||||
/// Note: despite the blocking interface, this expects to be called from
|
||||
/// the context of a multithreaded tokio runtime with IO and time enabled.
|
||||
fn open(
|
||||
&self,
|
||||
label: String,
|
||||
url: Url,
|
||||
options: retina::client::SessionOptions,
|
||||
) -> Result<Box<dyn Stream>, Error>;
|
||||
fn open(&self, label: String, url: Url, options: Options) -> Result<Box<dyn Stream>, Error>;
|
||||
}
|
||||
|
||||
pub struct VideoFrame {
|
||||
|
@ -57,9 +57,11 @@ impl Opener for RealOpener {
|
|||
&self,
|
||||
label: String,
|
||||
url: Url,
|
||||
options: retina::client::SessionOptions,
|
||||
mut options: Options,
|
||||
) -> Result<Box<dyn Stream>, Error> {
|
||||
let options = options.user_agent(format!("Moonfire NVR {}", env!("CARGO_PKG_VERSION")));
|
||||
options.session = options
|
||||
.session
|
||||
.user_agent(format!("Moonfire NVR {}", env!("CARGO_PKG_VERSION")));
|
||||
let rt_handle = tokio::runtime::Handle::current();
|
||||
let (inner, first_frame) = rt_handle
|
||||
.block_on(rt_handle.spawn(tokio::time::timeout(
|
||||
|
@ -111,29 +113,16 @@ impl RetinaStreamInner {
|
|||
async fn play(
|
||||
label: String,
|
||||
url: Url,
|
||||
options: retina::client::SessionOptions,
|
||||
options: Options,
|
||||
) -> Result<(Box<Self>, retina::codec::VideoFrame), Error> {
|
||||
let mut session = retina::client::Session::describe(url, options).await?;
|
||||
let mut session = retina::client::Session::describe(url, options.session).await?;
|
||||
log::debug!("connected to {:?}, tool {:?}", &label, session.tool());
|
||||
let (video_i, mut video_params) = session
|
||||
let video_i = session
|
||||
.streams()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find_map(|(i, s)| {
|
||||
if s.media == "video" && s.encoding_name == "h264" {
|
||||
Some((
|
||||
i,
|
||||
s.parameters().and_then(|p| match p {
|
||||
retina::codec::Parameters::Video(v) => Some(Box::new(v.clone())),
|
||||
_ => None,
|
||||
}),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.position(|s| s.media() == "video" && s.encoding_name() == "h264")
|
||||
.ok_or_else(|| format_err!("couldn't find H.264 video stream"))?;
|
||||
session.setup(video_i).await?;
|
||||
session.setup(video_i, options.setup).await?;
|
||||
let session = session.play(retina::client::PlayOptions::default()).await?;
|
||||
let mut session = session.demuxed()?;
|
||||
|
||||
|
@ -142,22 +131,20 @@ impl RetinaStreamInner {
|
|||
match Pin::new(&mut session).next().await {
|
||||
None => bail!("stream closed before first frame"),
|
||||
Some(Err(e)) => return Err(e.into()),
|
||||
Some(Ok(CodecItem::VideoFrame(mut v))) => {
|
||||
if let Some(v) = v.new_parameters.take() {
|
||||
video_params = Some(v);
|
||||
}
|
||||
if v.is_random_access_point {
|
||||
Some(Ok(CodecItem::VideoFrame(v))) => {
|
||||
if v.is_random_access_point() {
|
||||
break v;
|
||||
}
|
||||
}
|
||||
Some(Ok(_)) => {}
|
||||
}
|
||||
};
|
||||
let video_sample_entry = h264::parse_extra_data(
|
||||
video_params
|
||||
.ok_or_else(|| format_err!("couldn't find H.264 parameters"))?
|
||||
.extra_data(),
|
||||
)?;
|
||||
let video_params = match session.streams()[video_i].parameters() {
|
||||
Some(retina::codec::ParametersRef::Video(v)) => v.clone(),
|
||||
Some(_) => unreachable!(),
|
||||
None => bail!("couldn't find H.264 parameters"),
|
||||
};
|
||||
let video_sample_entry = h264::parse_extra_data(video_params.extra_data())?;
|
||||
let self_ = Box::new(Self {
|
||||
label,
|
||||
session,
|
||||
|
@ -169,20 +156,35 @@ impl RetinaStreamInner {
|
|||
/// Fetches a non-initial frame.
|
||||
async fn fetch_next_frame(
|
||||
mut self: Box<Self>,
|
||||
) -> Result<(Box<Self>, retina::codec::VideoFrame), Error> {
|
||||
) -> Result<
|
||||
(
|
||||
Box<Self>,
|
||||
retina::codec::VideoFrame,
|
||||
Option<retina::codec::VideoParameters>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
loop {
|
||||
match Pin::new(&mut self.session).next().await.transpose()? {
|
||||
None => bail!("end of stream"),
|
||||
Some(CodecItem::VideoFrame(v)) => {
|
||||
if v.loss > 0 {
|
||||
if v.loss() > 0 {
|
||||
log::warn!(
|
||||
"{}: lost {} RTP packets @ {}",
|
||||
&self.label,
|
||||
v.loss,
|
||||
v.loss(),
|
||||
v.start_ctx()
|
||||
);
|
||||
}
|
||||
return Ok((self, v));
|
||||
let p = if v.has_new_parameters() {
|
||||
Some(match self.session.streams()[v.stream_id()].parameters() {
|
||||
Some(retina::codec::ParametersRef::Video(v)) => v.clone(),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
return Ok((self, v, p));
|
||||
}
|
||||
Some(_) => {}
|
||||
}
|
||||
|
@ -206,7 +208,7 @@ impl Stream for RetinaStream {
|
|||
.map(|f| Ok((f, false)))
|
||||
.unwrap_or_else(move || {
|
||||
let inner = self.inner.take().unwrap();
|
||||
let (mut inner, mut frame) = self
|
||||
let (mut inner, frame, new_parameters) = self
|
||||
.rt_handle
|
||||
.block_on(self.rt_handle.spawn(tokio::time::timeout(
|
||||
RETINA_TIMEOUT,
|
||||
|
@ -215,7 +217,7 @@ impl Stream for RetinaStream {
|
|||
.expect("fetch_next_frame task panicked, see earlier error")
|
||||
.map_err(|_| format_err!("timeout getting next frame"))??;
|
||||
let mut new_video_sample_entry = false;
|
||||
if let Some(p) = frame.new_parameters.take() {
|
||||
if let Some(p) = new_parameters {
|
||||
let video_sample_entry = h264::parse_extra_data(p.extra_data())?;
|
||||
if video_sample_entry != inner.video_sample_entry {
|
||||
log::debug!(
|
||||
|
@ -232,10 +234,10 @@ impl Stream for RetinaStream {
|
|||
Ok::<_, failure::Error>((frame, new_video_sample_entry))
|
||||
})?;
|
||||
Ok(VideoFrame {
|
||||
pts: frame.timestamp.elapsed(),
|
||||
pts: frame.timestamp().elapsed(),
|
||||
duration: 0,
|
||||
is_key: frame.is_random_access_point,
|
||||
data: frame.into_data(),
|
||||
is_key: frame.is_random_access_point(),
|
||||
data: frame.into_data().into(),
|
||||
new_video_sample_entry,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -161,10 +161,8 @@ where
|
|||
|
||||
let mut stream = {
|
||||
let _t = TimerGuard::new(&clocks, || format!("opening {}", self.url.as_str()));
|
||||
self.opener.open(
|
||||
self.short_name.clone(),
|
||||
self.url.clone(),
|
||||
retina::client::SessionOptions::default()
|
||||
let options = stream::Options {
|
||||
session: retina::client::SessionOptions::default()
|
||||
.creds(if self.username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
|
@ -173,9 +171,11 @@ where
|
|||
password: self.password.clone(),
|
||||
})
|
||||
})
|
||||
.transport(self.transport)
|
||||
.session_group(self.session_group.clone()),
|
||||
)?
|
||||
setup: retina::client::SetupOptions::default().transport(self.transport.clone()),
|
||||
};
|
||||
self.opener
|
||||
.open(self.short_name.clone(), self.url.clone(), options)?
|
||||
};
|
||||
let realtime_offset = self.db.clocks().realtime() - clocks.monotonic();
|
||||
let mut video_sample_entry_id = {
|
||||
|
@ -382,7 +382,7 @@ mod tests {
|
|||
&self,
|
||||
_label: String,
|
||||
url: url::Url,
|
||||
_options: retina::client::SessionOptions,
|
||||
_options: stream::Options,
|
||||
) -> Result<Box<dyn stream::Stream>, Error> {
|
||||
assert_eq!(&url, &self.expected_url);
|
||||
let mut l = self.streams.lock();
|
||||
|
|
Loading…
Reference in New Issue