support --rtsp-library=retina (#37)

This isn't well-tested and doesn't yet support an initial connection
timeout. But in a quick test, it successfully returns video!

I'd like to do some more aggressive code restructuring for zero-copy
and to have only one writer thread per sample file directory (rather
than the syncer thread + one writer thread per RTSP stream). But I'll
likely wait until I drop support for ffmpeg entirely.
This commit is contained in:
Scott Lamb
2021-06-07 14:36:53 -07:00
parent 7699696bd9
commit 032bd76577
10 changed files with 582 additions and 200 deletions

View File

@@ -2,7 +2,7 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use crate::stream::{self, Opener, Stream};
use crate::stream::{self, Opener};
use base::strutil::{decode_size, encode_size};
use cursive::traits::{Boxable, Finder, Identifiable};
use cursive::views;
@@ -35,24 +35,30 @@ fn get_change(siv: &mut Cursive) -> db::CameraChange {
.get_content()
.as_str()
.into();
let u = siv
let username = match siv
.find_name::<views::EditView>("username")
.unwrap()
.get_content()
.as_str()
.into();
let p = siv
{
"" => None,
u => Some(u.to_owned()),
};
let password = match siv
.find_name::<views::EditView>("password")
.unwrap()
.get_content()
.as_str()
.into();
{
"" => None,
p => Some(p.to_owned()),
};
let mut c = db::CameraChange {
short_name: sn,
description: d,
onvif_host: h,
username: u,
password: p,
username,
password,
streams: Default::default(),
};
for &t in &db::ALL_STREAM_TYPES {
@@ -114,12 +120,16 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
}
}
fn press_test_inner(url: &Url) -> Result<String, Error> {
let stream = stream::FFMPEG.open(stream::Source::Rtsp {
url: url.as_str(),
redacted_url: url.as_str(), // don't need redaction in config UI.
fn press_test_inner(
url: Url,
username: Option<String>,
password: Option<String>,
) -> Result<String, Error> {
let (extra_data, _stream) = stream::FFMPEG.open(stream::Source::Rtsp {
url,
username,
password,
})?;
let extra_data = stream.get_extra_data()?;
Ok(format!(
"{}x{} video stream",
extra_data.entry.width, extra_data.entry.height
@@ -128,7 +138,7 @@ fn press_test_inner(url: &Url) -> Result<String, Error> {
fn press_test(siv: &mut Cursive, t: db::StreamType) {
let c = get_change(siv);
let mut url = match Url::parse(&c.streams[t.index()].rtsp_url) {
let url = match Url::parse(&c.streams[t.index()].rtsp_url) {
Ok(u) => u,
Err(e) => {
siv.add_layer(
@@ -139,11 +149,9 @@ fn press_test(siv: &mut Cursive, t: db::StreamType) {
return;
}
};
let username = c.username;
let password = c.password;
if !c.username.is_empty() {
let _ = url.set_username(&c.username);
let _ = url.set_password(Some(&c.password));
}
siv.add_layer(
views::Dialog::text(format!(
"Testing {} stream at {}. This may take a while \
@@ -159,7 +167,7 @@ fn press_test(siv: &mut Cursive, t: db::StreamType) {
siv.set_fps(5);
let sink = siv.cb_sink().clone();
::std::thread::spawn(move || {
let r = press_test_inner(&url);
let r = press_test_inner(url.clone(), username, password);
sink.send(Box::new(move |siv: &mut Cursive| {
// Polling is no longer necessary.
siv.set_fps(0);
@@ -442,8 +450,8 @@ fn edit_camera_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: &Option<i
for &(view_id, content) in &[
("short_name", &*camera.short_name),
("onvif_host", &*camera.onvif_host),
("username", &*camera.username),
("password", &*camera.password),
("username", camera.username.as_deref().unwrap_or("")),
("password", camera.password.as_deref().unwrap_or("")),
] {
dialog
.call_on_name(view_id, |v: &mut views::EditView| {

View File

@@ -2,7 +2,6 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use crate::stream;
use crate::streamer;
use crate::web;
use base::clock;
@@ -65,6 +64,14 @@ pub struct Args {
/// --http-addr=127.0.0.1:8080.
#[structopt(long)]
trust_forward_hdrs: bool,
/// RTSP library to use for fetching the cameras' video stream.
/// Moonfire NVR is in the process of switching from `ffmpeg` (the current
/// default, used since the beginning of the project) to `retina` (a
/// pure-Rust RTSP library developed by Moonfire NVR's author). `retina`
/// is still experimental.
#[structopt(long, default_value = "ffmpeg", parse(try_from_str))]
rtsp_library: crate::stream::RtspLibrary,
}
// These are used in a hack to get the name of the current time zone (e.g. America/Los_Angeles).
@@ -203,7 +210,7 @@ pub async fn run(args: &Args) -> Result<i32, Error> {
let streams = l.streams_by_id().len();
let env = streamer::Environment {
db: &db,
opener: &*stream::FFMPEG,
opener: args.rtsp_library.opener(),
shutdown: &shutdown_streamers,
};
@@ -227,6 +234,7 @@ pub async fn run(args: &Args) -> Result<i32, Error> {
}
// Then start up streams.
let handle = tokio::runtime::Handle::current();
let l = db.lock();
for (i, (id, stream)) in l.streams_by_id().iter().enumerate() {
if !stream.record {
@@ -259,10 +267,12 @@ pub async fn run(args: &Args) -> Result<i32, Error> {
)?;
info!("Starting streamer for {}", streamer.short_name());
let name = format!("s-{}", streamer.short_name());
let handle = handle.clone();
streamers.push(
thread::Builder::new()
.name(name)
.spawn(move || {
let _enter = handle.enter();
streamer.run();
})
.expect("can't create thread"),