mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-11-28 13:09:10 -05:00
drop ffmpeg support
* switch the config interface over to use Retina and make the test button honor rtsp_transport = udp. * adjust the threading model of the Retina streaming code. Before, it spawned a background future that read from the runtime and wrote to a channel. Other calls read from this channel. After, it does work directly from within the block_on calls (no channels). The immediate motivation was that the config interface didn't have another runtime handy. And passing in a current thread runtime deadlocked. I later learned this is a difference between Runtime::block_on and Handle::block_on. The former will drive IO and timers; the latter will not. But this is also more efficient to avoid so many thread hand-offs. Both the context switches and the extra spinning that tokio appears to do as mentioned here: https://github.com/scottlamb/retina/issues/5#issuecomment-871971550 This may not be the final word on the threading model. Eventually I may not have per-stream writing threads at all. But I think it will be easier to look at this after getting rid of the separate `moonfire-nvr config` subcommand in favor of a web interface. * in tests, read `.mp4` files via the `mp4` crate rather than ffmpeg. The annoying part is that this doesn't parse edit lists; oh well. * simplify the `Opener` interface. Formerly, it'd take either a RTSP URL or a path to a `.mp4` file, and they'd share some code because they both sometimes used ffmpeg. Now, they're totally different libraries (`retina` vs `mp4`). Pull the latter out to a `testutil` module with a different interface that exposes more of the `mp4` stuff. Now `Opener` is just for RTSP. * simplify the h264 module. It had a lot of logic to deal with Annex B. Retina doesn't use this encoding. Fixes #36 Fixes #126
This commit is contained in:
@@ -10,6 +10,7 @@ use cursive::Cursive;
|
||||
use db::writer;
|
||||
use failure::{bail, format_err, Error, ResultExt};
|
||||
use std::collections::BTreeMap;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
@@ -202,32 +203,44 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn press_test_inner(url: Url, username: String, password: String) -> Result<String, Error> {
|
||||
let pass_creds = !username.is_empty();
|
||||
let (extra_data, _stream) = stream::FFMPEG.open(
|
||||
fn press_test_inner(
|
||||
url: Url,
|
||||
username: String,
|
||||
password: String,
|
||||
transport: retina::client::Transport,
|
||||
) -> Result<String, Error> {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.enable_io()
|
||||
.build()?;
|
||||
let _guard = rt.enter();
|
||||
let (extra_data, _stream) = stream::OPENER.open(
|
||||
&rt,
|
||||
"test stream".to_owned(),
|
||||
stream::Source::Rtsp {
|
||||
url,
|
||||
username: if pass_creds { Some(username) } else { None },
|
||||
password: if pass_creds { Some(password) } else { None },
|
||||
transport: retina::client::Transport::Tcp,
|
||||
session_group: Default::default(),
|
||||
},
|
||||
url,
|
||||
retina::client::SessionOptions::default()
|
||||
.creds(if username.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(retina::client::Credentials { username, password })
|
||||
})
|
||||
.transport(transport),
|
||||
)?;
|
||||
Ok(format!(
|
||||
"{}x{} video stream",
|
||||
extra_data.entry.width, extra_data.entry.height
|
||||
extra_data.width, extra_data.height
|
||||
))
|
||||
}
|
||||
|
||||
fn press_test(siv: &mut Cursive, t: db::StreamType) {
|
||||
let c = get_camera(siv);
|
||||
let url = &c.streams[t.index()].url;
|
||||
let url = match parse_url(url, &["rtsp"]) {
|
||||
let s = &c.streams[t.index()];
|
||||
let transport = retina::client::Transport::from_str(s.rtsp_transport).unwrap_or_default();
|
||||
let url = match parse_url(&s.url, &["rtsp"]) {
|
||||
Ok(Some(u)) => u,
|
||||
_ => panic!(
|
||||
"test button should only be enabled with valid URL, not {:?}",
|
||||
url
|
||||
&s.url
|
||||
),
|
||||
};
|
||||
let username = c.username;
|
||||
@@ -248,7 +261,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.clone(), username, password);
|
||||
let r = press_test_inner(url.clone(), username, password, transport);
|
||||
sink.send(Box::new(move |siv: &mut Cursive| {
|
||||
// Polling is no longer necessary.
|
||||
siv.set_fps(0);
|
||||
|
||||
@@ -35,13 +35,6 @@ pub struct ConfigFile {
|
||||
/// Defaults to the number of cores on the system.
|
||||
#[serde(default)]
|
||||
pub worker_threads: Option<usize>,
|
||||
|
||||
/// RTSP library to use for fetching the cameras' video stream.
|
||||
/// Moonfire NVR is in the process of switching from `ffmpeg` (used since
|
||||
/// the beginning of the project) to `retina` (a pure-Rust RTSP library
|
||||
/// developed by Moonfire NVR's author).
|
||||
#[serde(default)]
|
||||
pub rtsp_library: crate::stream::RtspLibrary,
|
||||
}
|
||||
|
||||
/// Per-bind configuration.
|
||||
|
||||
@@ -270,7 +270,7 @@ async fn inner(
|
||||
let streams = l.streams_by_id().len();
|
||||
let env = streamer::Environment {
|
||||
db: &db,
|
||||
opener: config.rtsp_library.opener(),
|
||||
opener: &crate::stream::OPENER,
|
||||
shutdown_rx: &shutdown_rx,
|
||||
};
|
||||
|
||||
@@ -302,7 +302,6 @@ async fn inner(
|
||||
}
|
||||
|
||||
// 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.config.mode != db::json::STREAM_MODE_RECORD {
|
||||
@@ -340,14 +339,10 @@ async fn inner(
|
||||
)?;
|
||||
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();
|
||||
})
|
||||
.spawn(move || streamer.run())
|
||||
.expect("can't create thread"),
|
||||
);
|
||||
}
|
||||
@@ -397,7 +392,9 @@ async fn inner(
|
||||
let db = db.clone();
|
||||
move || {
|
||||
for streamer in streamers.drain(..) {
|
||||
streamer.join().unwrap();
|
||||
if streamer.join().is_err() {
|
||||
log::error!("streamer panicked; look for previous panic message");
|
||||
}
|
||||
}
|
||||
if let Some(mut ss) = syncers {
|
||||
// The syncers shut down when all channels to them have been dropped.
|
||||
|
||||
Reference in New Issue
Block a user