allow setting rtsp transport per-stream

This commit is contained in:
Scott Lamb 2021-10-27 14:27:23 -07:00
parent 981cee0706
commit 1c9a55653d
5 changed files with 64 additions and 8 deletions

View File

@ -10,6 +10,7 @@ Each release is tagged in Git and on the Docker repository
* bugfix: editing a camera from `nvr config` would erroneously clear the
sample file directory associated with its streams.
* RTSP transport (TCP or UDP) can be set per-stream from `nvr config`.
## `v0.7.0` (2021-10-27)

View File

@ -209,6 +209,10 @@ pub struct StreamConfig {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<Url>,
/// The RTSP transport (`tcp` or `udp`) to use.
#[serde(default, skip_serializing_if = "String::is_empty")]
pub rtsp_transport: String,
/// The number of bytes of video to retain, excluding the
/// currently-recording file.
///

View File

@ -13,6 +13,7 @@ use std::collections::BTreeMap;
use std::sync::Arc;
use url::Url;
#[derive(Debug)]
struct Camera {
short_name: String,
description: String,
@ -22,11 +23,12 @@ struct Camera {
streams: [Stream; db::NUM_STREAM_TYPES],
}
#[derive(Default)]
#[derive(Debug, Default)]
struct Stream {
url: String,
record: bool,
flush_if_sec: String,
rtsp_transport: &'static str,
sample_file_dir_id: Option<i32>,
}
@ -82,6 +84,11 @@ fn get_camera(siv: &mut Cursive) -> Camera {
.find_name::<views::Checkbox>(&format!("{}_record", t.as_str()))
.unwrap()
.is_checked();
let rtsp_transport = *siv
.find_name::<views::SelectView<&'static str>>(&format!("{}_rtsp_transport", t.as_str()))
.unwrap()
.selection()
.unwrap();
let flush_if_sec = siv
.find_name::<views::EditView>(&format!("{}_flush_if_sec", t.as_str()))
.unwrap()
@ -97,9 +104,11 @@ fn get_camera(siv: &mut Cursive) -> Camera {
url,
record,
flush_if_sec,
rtsp_transport,
sample_file_dir_id,
};
}
log::trace!("camera is: {:#?}", &camera);
camera
}
@ -155,6 +164,7 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
})
.to_owned();
stream_change.config.url = parse_url(&stream.url, &["rtsp"])?;
stream_change.config.rtsp_transport = stream.rtsp_transport.to_owned();
stream_change.sample_file_dir_id = stream.sample_file_dir_id;
stream_change.config.flush_if_sec = if stream.flush_if_sec.is_empty() {
0
@ -175,9 +185,13 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
})();
if let Err(e) = result {
siv.add_layer(
views::Dialog::text(format!("Unable to add camera: {}", e))
.title("Error")
.dismiss_button("Abort"),
views::Dialog::text(format!(
"Unable to {} camera: {}",
if id.is_some() { "edit" } else { "add" },
e
))
.title("Error")
.dismiss_button("Abort"),
);
} else {
siv.pop_layer(); // get rid of the add/edit camera dialog.
@ -457,6 +471,13 @@ fn edit_camera_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: &Option<i
"record",
views::Checkbox::new().with_name(format!("{}_record", type_.as_str())),
)
.child(
"rtsp_transport",
views::SelectView::<&str>::new()
.with_all([("(default)", ""), ("tcp", "tcp"), ("udp", "udp")])
.popup()
.with_name(format!("{}_rtsp_transport", type_.as_str())),
)
.child(
"flush_if_sec",
views::EditView::new().with_name(format!("{}_flush_if_sec", type_.as_str())),
@ -529,11 +550,22 @@ fn edit_camera_dialog(db: &Arc<db::Database>, siv: &mut Cursive, item: &Option<i
v.set_checked(s.config.mode == db::json::STREAM_MODE_RECORD)
},
);
dialog.call_on_name(
&format!("{}_rtsp_transport", t.as_str()),
|v: &mut views::SelectView<&'static str>| {
v.set_selection(match s.config.rtsp_transport.as_str() {
"tcp" => 1,
"udp" => 2,
_ => 0,
})
},
);
dialog.call_on_name(
&format!("{}_flush_if_sec", t.as_str()),
|v: &mut views::EditView| v.set_content(s.config.flush_if_sec.to_string()),
);
}
log::debug!("setting {} dir to {}", t.as_str(), selected_dir);
dialog.call_on_name(
&format!("{}_sample_file_dir", t.as_str()),
|v: &mut views::SelectView<Option<i32>>| v.set_selection(selected_dir),

View File

@ -76,6 +76,8 @@ pub struct Args {
#[structopt(long, default_value = "retina", parse(try_from_str))]
rtsp_library: crate::stream::RtspLibrary,
/// The RTSP transport (`tcp` or `udp`) to use when none is specified in the
/// per-stream configuration.
#[structopt(long, default_value)]
rtsp_transport: retina::client::Transport,
}
@ -265,7 +267,7 @@ async fn inner(args: Args, shutdown_rx: base::shutdown::Receiver) -> Result<i32,
let env = streamer::Environment {
db: &db,
opener: args.rtsp_library.opener(),
transport: args.rtsp_transport,
default_transport: args.rtsp_transport,
shutdown_rx: &shutdown_rx,
};

View File

@ -8,6 +8,7 @@ use db::{dir, recording, writer, Camera, Database, Stream};
use failure::{bail, format_err, Error};
use log::{debug, info, trace, warn};
use std::result::Result;
use std::str::FromStr;
use std::sync::Arc;
use url::Url;
@ -19,7 +20,7 @@ where
C: Clocks + Clone,
{
pub opener: &'a dyn stream::Opener,
pub transport: retina::client::Transport,
pub default_transport: retina::client::Transport,
pub db: &'tmp Arc<Database<C>>,
pub shutdown_rx: &'tmp base::shutdown::Receiver,
}
@ -71,6 +72,22 @@ where
if !url.username().is_empty() || url.password().is_some() {
bail!("RTSP URL shouldn't include credentials");
}
let stream_transport = if s.config.rtsp_transport.is_empty() {
None
} else {
match retina::client::Transport::from_str(&s.config.rtsp_transport) {
Ok(t) => Some(t),
Err(_) => {
log::warn!(
"Unable to parse configured transport {:?} for {}/{}; ignoring.",
&s.config.rtsp_transport,
&c.short_name,
s.type_
);
None
}
}
};
Ok(Streamer {
shutdown_rx: env.shutdown_rx.clone(),
rotate_offset_sec,
@ -79,7 +96,7 @@ where
dir,
syncer_channel,
opener: env.opener,
transport: env.transport,
transport: stream_transport.unwrap_or(env.default_transport),
stream_id,
session_group,
short_name: format!("{}-{}", c.short_name, s.type_.as_str()),
@ -417,7 +434,7 @@ mod tests {
opener: &opener,
db: &db.db,
shutdown_rx: &shutdown_rx,
transport: retina::client::Transport::Tcp,
default_transport: retina::client::Transport::Tcp,
};
let mut stream;
{