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:
Scott Lamb
2022-03-18 10:30:23 -07:00
parent be3a5b200e
commit 307a3884a0
14 changed files with 403 additions and 840 deletions

View File

@@ -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);

View File

@@ -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.

View File

@@ -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.