mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-01-12 15:33:22 -05:00
use extracted ffmpeg library
This commit is contained in:
parent
c6fe1b66a1
commit
ad13935ed6
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1300,6 +1300,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "moonfire-ffmpeg"
|
||||
version = "0.0.1"
|
||||
source = "git+https://github.com/scottlamb/moonfire-ffmpeg#c517aa782867c8b882524a2d21361753ca845f06"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
|
@ -15,7 +15,7 @@ nightly = ["db/nightly", "parking_lot/nightly", "smallvec/union"]
|
||||
bundled = ["rusqlite/bundled"]
|
||||
|
||||
[workspace]
|
||||
members = ["base", "db", "ffmpeg"]
|
||||
members = ["base", "db"]
|
||||
|
||||
[dependencies]
|
||||
base = { package = "moonfire-base", path = "base" }
|
||||
@ -28,7 +28,7 @@ cursive = "0.14.0"
|
||||
db = { package = "moonfire-db", path = "db" }
|
||||
docopt = "1.0"
|
||||
failure = "0.1.1"
|
||||
ffmpeg = { package = "moonfire-ffmpeg", path = "ffmpeg" }
|
||||
ffmpeg = { package = "moonfire-ffmpeg", git = "https://github.com/scottlamb/moonfire-ffmpeg" }
|
||||
futures = "0.3"
|
||||
fnv = "1.0"
|
||||
h264-reader = { git = "https://github.com/dholroyd/h264-reader" }
|
||||
|
@ -1,19 +0,0 @@
|
||||
[package]
|
||||
name = "moonfire-ffmpeg"
|
||||
version = "0.0.1"
|
||||
authors = ["Scott Lamb <slamb@slamb.org>"]
|
||||
readme = "../README.md"
|
||||
edition = "2018"
|
||||
#links = "ffmpeg"
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2"
|
||||
log = { version = "0.4", features = ["release_max_level_info"] }
|
||||
parking_lot = { version = "0.9", features = [] }
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1.0"
|
||||
pkg-config = "0.3"
|
@ -1,49 +0,0 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2017 The Moonfire NVR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
fn main() {
|
||||
let libraries = [
|
||||
pkg_config::Config::new().atleast_version("54.1").probe("libavutil").unwrap(),
|
||||
pkg_config::Config::new().atleast_version("56.0").probe("libavcodec").unwrap(),
|
||||
pkg_config::Config::new().atleast_version("57.5").probe("libavformat").unwrap(),
|
||||
];
|
||||
let mut wrapper = cc::Build::new();
|
||||
|
||||
for lib in &libraries {
|
||||
// Pass include paths on to gcc. It'd be nice if pkg-config allowed fetching CFLAGS and
|
||||
// passing that on; see <https://github.com/alexcrichton/pkg-config-rs/issues/43>. But
|
||||
// the include paths are likely all that's included/significant for compilation.
|
||||
for p in &lib.include_paths {
|
||||
wrapper.include(p);
|
||||
}
|
||||
}
|
||||
|
||||
wrapper.file("wrapper.c").compile("libwrapper.a");
|
||||
}
|
504
ffmpeg/lib.rs
504
ffmpeg/lib.rs
@ -1,504 +0,0 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2017 The Moonfire NVR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use log::info;
|
||||
use parking_lot::Once;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::ffi::CStr;
|
||||
use std::fmt::{self, Write};
|
||||
use std::ptr;
|
||||
|
||||
static START: Once = Once::new();
|
||||
|
||||
//#[link(name = "avcodec")]
|
||||
extern "C" {
|
||||
fn avcodec_version() -> libc::c_int;
|
||||
fn av_init_packet(p: *mut AVPacket);
|
||||
fn av_packet_unref(p: *mut AVPacket);
|
||||
|
||||
fn moonfire_ffmpeg_codecpar_codec_id(ctx: *const AVCodecParameters) -> libc::c_int;
|
||||
fn moonfire_ffmpeg_codecpar_codec_type(ctx: *const AVCodecParameters) -> libc::c_int;
|
||||
fn moonfire_ffmpeg_codecpar_extradata(ctx: *const AVCodecParameters) -> DataLen;
|
||||
fn moonfire_ffmpeg_codecpar_height(ctx: *const AVCodecParameters) -> libc::c_int;
|
||||
fn moonfire_ffmpeg_codecpar_width(ctx: *const AVCodecParameters) -> libc::c_int;
|
||||
}
|
||||
|
||||
//#[link(name = "avformat")]
|
||||
extern "C" {
|
||||
fn avformat_version() -> libc::c_int;
|
||||
|
||||
fn avformat_open_input(ctx: *mut *mut AVFormatContext, url: *const libc::c_char,
|
||||
fmt: *const AVInputFormat, options: *mut *mut AVDictionary)
|
||||
-> libc::c_int;
|
||||
fn avformat_close_input(ctx: *mut *mut AVFormatContext);
|
||||
fn avformat_find_stream_info(ctx: *mut AVFormatContext, options: *mut *mut AVDictionary)
|
||||
-> libc::c_int;
|
||||
fn av_read_frame(ctx: *mut AVFormatContext, p: *mut AVPacket) -> libc::c_int;
|
||||
fn av_register_all();
|
||||
fn avformat_network_init() -> libc::c_int;
|
||||
|
||||
fn moonfire_ffmpeg_fctx_streams(ctx: *const AVFormatContext) -> StreamsLen;
|
||||
|
||||
fn moonfire_ffmpeg_stream_codecpar(stream: *const AVStream) -> *const AVCodecParameters;
|
||||
fn moonfire_ffmpeg_stream_time_base(stream: *const AVStream) -> AVRational;
|
||||
}
|
||||
|
||||
//#[link(name = "avutil")]
|
||||
extern "C" {
|
||||
fn avutil_version() -> libc::c_int;
|
||||
fn av_strerror(e: libc::c_int, b: *mut libc::c_char, s: libc::size_t) -> libc::c_int;
|
||||
fn av_dict_count(d: *const AVDictionary) -> libc::c_int;
|
||||
fn av_dict_get(d: *const AVDictionary, key: *const libc::c_char, prev: *mut AVDictionaryEntry,
|
||||
flags: libc::c_int) -> *mut AVDictionaryEntry;
|
||||
fn av_dict_set(d: *mut *mut AVDictionary, key: *const libc::c_char, value: *const libc::c_char,
|
||||
flags: libc::c_int) -> libc::c_int;
|
||||
fn av_dict_free(d: *mut *mut AVDictionary);
|
||||
}
|
||||
|
||||
//#[link(name = "wrapper")]
|
||||
extern "C" {
|
||||
static moonfire_ffmpeg_compiled_libavcodec_version: libc::c_int;
|
||||
static moonfire_ffmpeg_compiled_libavformat_version: libc::c_int;
|
||||
static moonfire_ffmpeg_compiled_libavutil_version: libc::c_int;
|
||||
static moonfire_ffmpeg_av_dict_ignore_suffix: libc::c_int;
|
||||
static moonfire_ffmpeg_av_nopts_value: i64;
|
||||
|
||||
static moonfire_ffmpeg_av_codec_id_h264: libc::c_int;
|
||||
static moonfire_ffmpeg_avmedia_type_video: libc::c_int;
|
||||
|
||||
static moonfire_ffmpeg_averror_eof: libc::c_int;
|
||||
|
||||
fn moonfire_ffmpeg_init();
|
||||
|
||||
fn moonfire_ffmpeg_packet_alloc() -> *mut AVPacket;
|
||||
fn moonfire_ffmpeg_packet_free(p: *mut AVPacket);
|
||||
fn moonfire_ffmpeg_packet_is_key(p: *const AVPacket) -> bool;
|
||||
fn moonfire_ffmpeg_packet_pts(p: *const AVPacket) -> i64;
|
||||
fn moonfire_ffmpeg_packet_dts(p: *const AVPacket) -> i64;
|
||||
fn moonfire_ffmpeg_packet_duration(p: *const AVPacket) -> libc::c_int;
|
||||
fn moonfire_ffmpeg_packet_set_pts(p: *mut AVPacket, pts: i64);
|
||||
fn moonfire_ffmpeg_packet_set_dts(p: *mut AVPacket, dts: i64);
|
||||
fn moonfire_ffmpeg_packet_set_duration(p: *mut AVPacket, dur: libc::c_int);
|
||||
fn moonfire_ffmpeg_packet_data(p: *const AVPacket) -> DataLen;
|
||||
fn moonfire_ffmpeg_packet_stream_index(p: *const AVPacket) -> libc::c_uint;
|
||||
}
|
||||
|
||||
pub struct Ffmpeg {}
|
||||
|
||||
// No accessors here; seems reasonable to assume ABI stability of this simple struct.
|
||||
#[repr(C)]
|
||||
struct AVDictionaryEntry {
|
||||
key: *mut libc::c_char,
|
||||
value: *mut libc::c_char,
|
||||
}
|
||||
|
||||
// Likewise, seems reasonable to assume this struct has a stable ABI.
|
||||
#[repr(C)]
|
||||
pub struct AVRational {
|
||||
pub num: libc::c_int,
|
||||
pub den: libc::c_int,
|
||||
}
|
||||
|
||||
// No ABI stability assumption here; use heap allocation/deallocation and accessors only.
|
||||
enum AVCodecParameters {}
|
||||
enum AVDictionary {}
|
||||
enum AVFormatContext {}
|
||||
enum AVInputFormat {}
|
||||
enum AVPacket {}
|
||||
enum AVStream {}
|
||||
|
||||
pub struct InputFormatContext {
|
||||
ctx: *mut AVFormatContext,
|
||||
pkt: RefCell<*mut AVPacket>,
|
||||
}
|
||||
|
||||
impl InputFormatContext {
|
||||
pub fn open(source: &CStr, dict: &mut Dictionary) -> Result<InputFormatContext, Error> {
|
||||
let mut ctx = ptr::null_mut();
|
||||
Error::wrap(unsafe {
|
||||
avformat_open_input(&mut ctx, source.as_ptr(), ptr::null(), &mut dict.0)
|
||||
})?;
|
||||
let pkt = unsafe { moonfire_ffmpeg_packet_alloc() };
|
||||
if pkt.is_null() {
|
||||
panic!("malloc failed");
|
||||
}
|
||||
unsafe { av_init_packet(pkt) };
|
||||
Ok(InputFormatContext{
|
||||
ctx,
|
||||
pkt: RefCell::new(pkt),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn find_stream_info(&mut self) -> Result<(), Error> {
|
||||
Error::wrap(unsafe { avformat_find_stream_info(self.ctx, ptr::null_mut()) })
|
||||
}
|
||||
|
||||
// XXX: non-mut because of lexical lifetime woes in the caller. This is also why we need a
|
||||
// RefCell.
|
||||
pub fn read_frame<'i>(&'i self) -> Result<Packet<'i>, Error> {
|
||||
let pkt = self.pkt.borrow();
|
||||
Error::wrap(unsafe { av_read_frame(self.ctx, *pkt) })?;
|
||||
Ok(Packet(pkt))
|
||||
}
|
||||
|
||||
pub fn streams<'i>(&'i self) -> Streams<'i> {
|
||||
Streams(unsafe {
|
||||
let s = moonfire_ffmpeg_fctx_streams(self.ctx);
|
||||
std::slice::from_raw_parts(s.streams, s.len as usize)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl Send for InputFormatContext {}
|
||||
|
||||
impl Drop for InputFormatContext {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
moonfire_ffmpeg_packet_free(*self.pkt.borrow());
|
||||
avformat_close_input(&mut self.ctx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches moonfire_ffmpeg_data_len
|
||||
#[repr(C)]
|
||||
struct DataLen {
|
||||
data: *const u8,
|
||||
len: libc::size_t,
|
||||
}
|
||||
|
||||
// matches moonfire_ffmpeg_streams_len
|
||||
#[repr(C)]
|
||||
struct StreamsLen {
|
||||
streams: *const *const AVStream,
|
||||
len: libc::size_t,
|
||||
}
|
||||
|
||||
pub struct Packet<'i>(Ref<'i, *mut AVPacket>);
|
||||
|
||||
impl<'i> Packet<'i> {
|
||||
pub fn is_key(&self) -> bool { unsafe { moonfire_ffmpeg_packet_is_key(*self.0) } }
|
||||
pub fn pts(&self) -> Option<i64> {
|
||||
match unsafe { moonfire_ffmpeg_packet_pts(*self.0) } {
|
||||
v if v == unsafe { moonfire_ffmpeg_av_nopts_value } => None,
|
||||
v => Some(v),
|
||||
}
|
||||
}
|
||||
pub fn set_pts(&mut self, pts: Option<i64>) {
|
||||
let real_pts = match pts {
|
||||
None => unsafe { moonfire_ffmpeg_av_nopts_value },
|
||||
Some(v) => v,
|
||||
};
|
||||
unsafe { moonfire_ffmpeg_packet_set_pts(*self.0, real_pts); }
|
||||
}
|
||||
pub fn dts(&self) -> i64 { unsafe { moonfire_ffmpeg_packet_dts(*self.0) } }
|
||||
pub fn set_dts(&mut self, dts: i64) {
|
||||
unsafe { moonfire_ffmpeg_packet_set_dts(*self.0, dts); }
|
||||
}
|
||||
pub fn duration(&self) -> i32 { unsafe { moonfire_ffmpeg_packet_duration(*self.0) } }
|
||||
pub fn set_duration(&mut self, dur: i32) {
|
||||
unsafe { moonfire_ffmpeg_packet_set_duration(*self.0, dur) }
|
||||
}
|
||||
pub fn stream_index(&self) -> usize {
|
||||
unsafe { moonfire_ffmpeg_packet_stream_index(*self.0) as usize }
|
||||
}
|
||||
pub fn data(&self) -> Option<&[u8]> {
|
||||
unsafe {
|
||||
let d = moonfire_ffmpeg_packet_data(*self.0);
|
||||
if d.data.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(::std::slice::from_raw_parts(d.data, d.len))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'i> Drop for Packet<'i> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
av_packet_unref(*self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Streams<'owner>(&'owner [*const AVStream]);
|
||||
|
||||
impl<'owner> Streams<'owner> {
|
||||
pub fn get(&self, i: usize) -> Stream<'owner> { Stream(unsafe { self.0[i].as_ref() }.unwrap()) }
|
||||
pub fn len(&self) -> usize { self.0.len() }
|
||||
}
|
||||
|
||||
pub struct Stream<'o>(&'o AVStream);
|
||||
|
||||
impl<'o> Stream<'o> {
|
||||
pub fn codecpar<'s>(&'s self) -> CodecParameters<'s> {
|
||||
CodecParameters(unsafe { moonfire_ffmpeg_stream_codecpar(self.0).as_ref() }.unwrap())
|
||||
}
|
||||
|
||||
pub fn time_base(&self) -> AVRational {
|
||||
unsafe { moonfire_ffmpeg_stream_time_base(self.0) }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CodecParameters<'s>(&'s AVCodecParameters);
|
||||
|
||||
impl<'s> CodecParameters<'s> {
|
||||
pub fn extradata(&self) -> &[u8] {
|
||||
unsafe {
|
||||
let d = moonfire_ffmpeg_codecpar_extradata(self.0);
|
||||
::std::slice::from_raw_parts(d.data, d.len)
|
||||
}
|
||||
}
|
||||
pub fn width(&self) -> libc::c_int { unsafe { moonfire_ffmpeg_codecpar_width(self.0) } }
|
||||
pub fn height(&self) -> libc::c_int { unsafe { moonfire_ffmpeg_codecpar_height(self.0) } }
|
||||
pub fn codec_id(&self) -> CodecId {
|
||||
CodecId(unsafe { moonfire_ffmpeg_codecpar_codec_id(self.0) })
|
||||
}
|
||||
pub fn codec_type(&self) -> MediaType {
|
||||
MediaType(unsafe { moonfire_ffmpeg_codecpar_codec_type(self.0) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct CodecId(libc::c_int);
|
||||
|
||||
impl CodecId {
|
||||
pub fn is_h264(self) -> bool { self.0 == unsafe { moonfire_ffmpeg_av_codec_id_h264 } }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct MediaType(libc::c_int);
|
||||
|
||||
impl MediaType {
|
||||
pub fn is_video(self) -> bool { self.0 == unsafe { moonfire_ffmpeg_avmedia_type_video } }
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Error(libc::c_int);
|
||||
|
||||
impl Error {
|
||||
pub fn eof() -> Self { Error(unsafe { moonfire_ffmpeg_averror_eof }) }
|
||||
|
||||
fn wrap(raw: libc::c_int) -> Result<(), Error> {
|
||||
match raw {
|
||||
0 => Ok(()),
|
||||
r => Err(Error(r)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_eof(self) -> bool { return self.0 == unsafe { moonfire_ffmpeg_averror_eof } }
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
// TODO: pull out some common cases.
|
||||
"ffmpeg error"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> { None }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Error({} /* {} */)", self.0, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
const ARRAYLEN: usize = 64;
|
||||
let mut buf = [0; ARRAYLEN];
|
||||
let s = unsafe {
|
||||
// Note av_strerror uses strlcpy, so it guarantees a trailing NUL byte.
|
||||
av_strerror(self.0, buf.as_mut_ptr(), ARRAYLEN);
|
||||
CStr::from_ptr(buf.as_ptr())
|
||||
};
|
||||
f.write_str(&s.to_string_lossy())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Version(libc::c_int);
|
||||
|
||||
impl Version {
|
||||
fn major(self) -> libc::c_int { (self.0 >> 16) & 0xFF }
|
||||
fn minor(self) -> libc::c_int { (self.0 >> 8) & 0xFF }
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", (self.0 >> 16) & 0xFF, (self.0 >> 8) & 0xFF, self.0 & 0xFF)
|
||||
}
|
||||
}
|
||||
|
||||
struct Library {
|
||||
name: &'static str,
|
||||
compiled: Version,
|
||||
running: Version,
|
||||
}
|
||||
|
||||
impl Library {
|
||||
fn new(name: &'static str, compiled: libc::c_int, running: libc::c_int) -> Self {
|
||||
Library {
|
||||
name,
|
||||
compiled: Version(compiled),
|
||||
running: Version(running),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_compatible(&self) -> bool {
|
||||
self.running.major() == self.compiled.major() &&
|
||||
self.running.minor() >= self.compiled.minor()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Library {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: running={} compiled={}", self.name, self.running, self.compiled)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dictionary(*mut AVDictionary);
|
||||
|
||||
impl Dictionary {
|
||||
pub fn new() -> Dictionary { Dictionary(ptr::null_mut()) }
|
||||
pub fn size(&self) -> usize { (unsafe { av_dict_count(self.0) }) as usize }
|
||||
pub fn empty(&self) -> bool { self.size() == 0 }
|
||||
pub fn set(&mut self, key: &CStr, value: &CStr) -> Result<(), Error> {
|
||||
Error::wrap(unsafe { av_dict_set(&mut self.0, key.as_ptr(), value.as_ptr(), 0) })
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Dictionary {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut ent = ptr::null_mut();
|
||||
let mut first = true;
|
||||
loop {
|
||||
unsafe {
|
||||
let c = 0;
|
||||
ent = av_dict_get(self.0, &c, ent, moonfire_ffmpeg_av_dict_ignore_suffix);
|
||||
if ent.is_null() {
|
||||
break;
|
||||
}
|
||||
if first {
|
||||
first = false;
|
||||
} else {
|
||||
write!(f, ", ")?;
|
||||
}
|
||||
write!(f, "{}={}", CStr::from_ptr((*ent).key).to_string_lossy(),
|
||||
CStr::from_ptr((*ent).value).to_string_lossy())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Dictionary {
|
||||
fn drop(&mut self) { unsafe { av_dict_free(&mut self.0) } }
|
||||
}
|
||||
|
||||
impl Ffmpeg {
|
||||
pub fn new() -> Ffmpeg {
|
||||
START.call_once(|| unsafe {
|
||||
let libs = &[
|
||||
Library::new("avutil", moonfire_ffmpeg_compiled_libavutil_version,
|
||||
avutil_version()),
|
||||
Library::new("avcodec", moonfire_ffmpeg_compiled_libavcodec_version,
|
||||
avcodec_version()),
|
||||
Library::new("avformat", moonfire_ffmpeg_compiled_libavformat_version,
|
||||
avformat_version()),
|
||||
];
|
||||
let mut msg = String::new();
|
||||
let mut compatible = true;
|
||||
for l in libs {
|
||||
write!(&mut msg, "\n{}", l).unwrap();
|
||||
if !l.is_compatible() {
|
||||
compatible = false;
|
||||
msg.push_str(" <- not ABI-compatible!");
|
||||
}
|
||||
}
|
||||
if !compatible {
|
||||
panic!("Incompatible ffmpeg versions:{}", msg);
|
||||
}
|
||||
moonfire_ffmpeg_init();
|
||||
av_register_all();
|
||||
if avformat_network_init() < 0 {
|
||||
panic!("avformat_network_init failed");
|
||||
}
|
||||
info!("Initialized ffmpeg. Versions:{}", msg);
|
||||
});
|
||||
Ffmpeg{}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ffi::CString;
|
||||
use super::Error;
|
||||
|
||||
/// Just tests that this doesn't crash with an ABI compatibility error.
|
||||
#[test]
|
||||
fn test_init() { super::Ffmpeg::new(); }
|
||||
|
||||
#[test]
|
||||
fn test_is_compatible() {
|
||||
// compiled major/minor/patch, running major/minor/patch, expected compatible
|
||||
use ::libc::c_int;
|
||||
struct Test(c_int, c_int, c_int, c_int, c_int, c_int, bool);
|
||||
|
||||
let tests = &[
|
||||
Test(55, 1, 2, 55, 1, 2, true), // same version, compatible
|
||||
Test(55, 1, 2, 55, 2, 1, true), // newer minor version, compatible
|
||||
Test(55, 1, 3, 55, 1, 2, true), // older patch version, compatible (but weird)
|
||||
Test(55, 2, 2, 55, 1, 2, false), // older minor version, incompatible
|
||||
Test(55, 1, 2, 56, 1, 2, false), // newer major version, incompatible
|
||||
Test(56, 1, 2, 55, 1, 2, false), // older major version, incompatible
|
||||
];
|
||||
|
||||
for t in tests {
|
||||
let l = super::Library::new(
|
||||
"avutil", (t.0 << 16) | (t.1 << 8) | t.2, (t.3 << 16) | (t.4 << 8) | t.5);
|
||||
assert!(l.is_compatible() == t.6, "{} expected={}", l, t.6);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error() {
|
||||
let eof_formatted = format!("{}", Error::eof());
|
||||
assert!(eof_formatted.contains("End of file"), "eof formatted is: {}", eof_formatted);
|
||||
let eof_debug = format!("{:?}", Error::eof());
|
||||
assert!(eof_debug.contains("End of file"), "eof debug is: {}", eof_debug);
|
||||
|
||||
// Errors should be round trippable to a CString. (This will fail if they contain NUL
|
||||
// bytes.)
|
||||
CString::new(eof_formatted).unwrap();
|
||||
}
|
||||
}
|
151
ffmpeg/wrapper.c
151
ffmpeg/wrapper.c
@ -1,151 +0,0 @@
|
||||
// This file is part of Moonfire NVR, a security camera network video recorder.
|
||||
// Copyright (C) 2017 The Moonfire NVR Authors
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// In addition, as a special exception, the copyright holders give
|
||||
// permission to link the code of portions of this program with the
|
||||
// OpenSSL library under certain conditions as described in each
|
||||
// individual source file, and distribute linked combinations including
|
||||
// the two.
|
||||
//
|
||||
// You must obey the GNU General Public License in all respects for all
|
||||
// of the code used other than OpenSSL. If you modify file(s) with this
|
||||
// exception, you may extend this exception to your version of the
|
||||
// file(s), but you are not obligated to do so. If you do not wish to do
|
||||
// so, delete this exception statement from your version. If you delete
|
||||
// this exception statement from all source files in the program, then
|
||||
// also delete it here.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/* vim: set sw=4 et: */
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavformat/version.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavutil/dict.h>
|
||||
#include <libavutil/version.h>
|
||||
#include <pthread.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
const int moonfire_ffmpeg_compiled_libavcodec_version = LIBAVCODEC_VERSION_INT;
|
||||
const int moonfire_ffmpeg_compiled_libavformat_version = LIBAVFORMAT_VERSION_INT;
|
||||
const int moonfire_ffmpeg_compiled_libavutil_version = LIBAVUTIL_VERSION_INT;
|
||||
|
||||
const int moonfire_ffmpeg_av_dict_ignore_suffix = AV_DICT_IGNORE_SUFFIX;
|
||||
|
||||
const int64_t moonfire_ffmpeg_av_nopts_value = AV_NOPTS_VALUE;
|
||||
|
||||
const int moonfire_ffmpeg_avmedia_type_video = AVMEDIA_TYPE_VIDEO;
|
||||
|
||||
const int moonfire_ffmpeg_av_codec_id_h264 = AV_CODEC_ID_H264;
|
||||
|
||||
const int moonfire_ffmpeg_averror_eof = AVERROR_EOF;
|
||||
|
||||
// Prior to libavcodec 58.9.100, multithreaded callers were expected to supply
|
||||
// a lock callback. That release deprecated this API. It also introduced a
|
||||
// FF_API_LOCKMGR #define to track its removal:
|
||||
//
|
||||
// * older builds (in which the lock callback is needed) don't define it.
|
||||
// * middle builds (in which the callback is deprecated) define it as 1.
|
||||
// value of 1.
|
||||
// * future builds (in which the callback removed) will define
|
||||
// it as 0.
|
||||
//
|
||||
// so (counterintuitively) use the lock manager when FF_API_LOCKMGR is
|
||||
// undefined.
|
||||
|
||||
#ifndef FF_API_LOCKMGR
|
||||
static int lock_callback(void **mutex, enum AVLockOp op) {
|
||||
switch (op) {
|
||||
case AV_LOCK_CREATE:
|
||||
*mutex = malloc(sizeof(pthread_mutex_t));
|
||||
if (*mutex == NULL)
|
||||
return -1;
|
||||
if (pthread_mutex_init(*mutex, NULL) != 0)
|
||||
return -1;
|
||||
break;
|
||||
case AV_LOCK_DESTROY:
|
||||
if (pthread_mutex_destroy(*mutex) != 0)
|
||||
return -1;
|
||||
free(*mutex);
|
||||
*mutex = NULL;
|
||||
break;
|
||||
case AV_LOCK_OBTAIN:
|
||||
if (pthread_mutex_lock(*mutex) != 0)
|
||||
return -1;
|
||||
break;
|
||||
case AV_LOCK_RELEASE:
|
||||
if (pthread_mutex_unlock(*mutex) != 0)
|
||||
return -1;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
void moonfire_ffmpeg_init(void) {
|
||||
#ifndef FF_API_LOCKMGR
|
||||
if (av_lockmgr_register(&lock_callback) < 0) {
|
||||
abort();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct moonfire_ffmpeg_streams {
|
||||
AVStream** streams;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct moonfire_ffmpeg_data {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
struct moonfire_ffmpeg_streams moonfire_ffmpeg_fctx_streams(AVFormatContext *ctx) {
|
||||
struct moonfire_ffmpeg_streams s = {ctx->streams, ctx->nb_streams};
|
||||
return s;
|
||||
}
|
||||
|
||||
AVPacket *moonfire_ffmpeg_packet_alloc(void) { return malloc(sizeof(AVPacket)); }
|
||||
void moonfire_ffmpeg_packet_free(AVPacket *pkt) { free(pkt); }
|
||||
bool moonfire_ffmpeg_packet_is_key(AVPacket *pkt) { return (pkt->flags & AV_PKT_FLAG_KEY) != 0; }
|
||||
int64_t moonfire_ffmpeg_packet_pts(AVPacket *pkt) { return pkt->pts; }
|
||||
void moonfire_ffmpeg_packet_set_dts(AVPacket *pkt, int64_t dts) { pkt->dts = dts; }
|
||||
void moonfire_ffmpeg_packet_set_pts(AVPacket *pkt, int64_t pts) { pkt->pts = pts; }
|
||||
void moonfire_ffmpeg_packet_set_duration(AVPacket *pkt, int dur) { pkt->duration = dur; }
|
||||
int64_t moonfire_ffmpeg_packet_dts(AVPacket *pkt) { return pkt->dts; }
|
||||
int moonfire_ffmpeg_packet_duration(AVPacket *pkt) { return pkt->duration; }
|
||||
int moonfire_ffmpeg_packet_stream_index(AVPacket *pkt) { return pkt->stream_index; }
|
||||
struct moonfire_ffmpeg_data moonfire_ffmpeg_packet_data(AVPacket *pkt) {
|
||||
struct moonfire_ffmpeg_data d = {pkt->data, pkt->size};
|
||||
return d;
|
||||
}
|
||||
|
||||
AVCodecParameters *moonfire_ffmpeg_stream_codecpar(AVStream *stream) { return stream->codecpar; }
|
||||
AVRational moonfire_ffmpeg_stream_time_base(AVStream *stream) { return stream->time_base; }
|
||||
|
||||
int moonfire_ffmpeg_codecpar_codec_id(AVCodecParameters *codecpar) { return codecpar->codec_id; }
|
||||
int moonfire_ffmpeg_codecpar_codec_type(AVCodecParameters *codecpar) {
|
||||
return codecpar->codec_type;
|
||||
}
|
||||
struct moonfire_ffmpeg_data moonfire_ffmpeg_codecpar_extradata(AVCodecParameters *codecpar) {
|
||||
struct moonfire_ffmpeg_data d = {codecpar->extradata, codecpar->extradata_size};
|
||||
return d;
|
||||
}
|
||||
int moonfire_ffmpeg_codecpar_height(AVCodecParameters *codecpar) { return codecpar->height; }
|
||||
int moonfire_ffmpeg_codecpar_width(AVCodecParameters *codecpar) { return codecpar->width; }
|
@ -34,6 +34,7 @@ use failure::{Error, bail};
|
||||
use ffmpeg;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{debug, info, warn};
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::CString;
|
||||
use std::result::Result;
|
||||
|
||||
@ -61,29 +62,25 @@ pub trait Opener<S : Stream> : Sync {
|
||||
|
||||
pub trait Stream {
|
||||
fn get_extra_data(&self) -> Result<h264::ExtraData, Error>;
|
||||
fn get_next<'p>(&'p mut self) -> Result<ffmpeg::Packet<'p>, ffmpeg::Error>;
|
||||
fn get_next<'p>(&'p mut self) -> Result<ffmpeg::avcodec::Packet<'p>, ffmpeg::Error>;
|
||||
}
|
||||
|
||||
pub struct Ffmpeg {}
|
||||
|
||||
impl Ffmpeg {
|
||||
fn new() -> Ffmpeg {
|
||||
START.call_once(|| {
|
||||
ffmpeg::Ffmpeg::new();
|
||||
//ffmpeg::init().unwrap();
|
||||
//ffmpeg::format::network::init();
|
||||
});
|
||||
START.call_once(|| { ffmpeg::Ffmpeg::new(); });
|
||||
Ffmpeg{}
|
||||
}
|
||||
}
|
||||
|
||||
impl Opener<FfmpegStream> for Ffmpeg {
|
||||
fn open(&self, src: Source) -> Result<FfmpegStream, Error> {
|
||||
use ffmpeg::InputFormatContext;
|
||||
use ffmpeg::avformat::InputFormatContext;
|
||||
let (mut input, discard_first) = match src {
|
||||
#[cfg(test)]
|
||||
Source::File(filename) => {
|
||||
let mut open_options = ffmpeg::Dictionary::new();
|
||||
let mut open_options = ffmpeg::avutil::Dictionary::new();
|
||||
|
||||
// Work around https://github.com/scottlamb/moonfire-nvr/issues/10
|
||||
open_options.set(cstr!("advanced_editlist"), cstr!("false")).unwrap();
|
||||
@ -97,7 +94,7 @@ impl Opener<FfmpegStream> for Ffmpeg {
|
||||
(i, false)
|
||||
}
|
||||
Source::Rtsp{url, redacted_url} => {
|
||||
let mut open_options = ffmpeg::Dictionary::new();
|
||||
let mut open_options = ffmpeg::avutil::Dictionary::new();
|
||||
open_options.set(cstr!("rtsp_transport"), cstr!("tcp")).unwrap();
|
||||
open_options.set(cstr!("user-agent"), cstr!("moonfire-nvr")).unwrap();
|
||||
// 10-second socket timeout, in microseconds.
|
||||
@ -150,7 +147,7 @@ impl Opener<FfmpegStream> for Ffmpeg {
|
||||
}
|
||||
|
||||
pub struct FfmpegStream {
|
||||
input: ffmpeg::InputFormatContext,
|
||||
input: ffmpeg::avformat::InputFormatContext,
|
||||
video_i: usize,
|
||||
}
|
||||
|
||||
@ -166,10 +163,12 @@ impl Stream for FfmpegStream {
|
||||
if !codec_id.is_h264() {
|
||||
bail!("stream's video codec {:?} is not h264", codec_id);
|
||||
}
|
||||
h264::ExtraData::parse(codec.extradata(), codec.width() as u16, codec.height() as u16)
|
||||
let dims = codec.dims();
|
||||
h264::ExtraData::parse(codec.extradata(), u16::try_from(dims.width)?,
|
||||
u16::try_from(dims.height)?)
|
||||
}
|
||||
|
||||
fn get_next<'i>(&'i mut self) -> Result<ffmpeg::Packet<'i>, ffmpeg::Error> {
|
||||
fn get_next<'i>(&'i mut self) -> Result<ffmpeg::avcodec::Packet<'i>, ffmpeg::Error> {
|
||||
loop {
|
||||
let p = self.input.read_frame()?;
|
||||
if p.stream_index() == self.video_i {
|
||||
|
@ -236,7 +236,7 @@ mod tests {
|
||||
}
|
||||
|
||||
impl<'a> Stream for ProxyingStream<'a> {
|
||||
fn get_next(&mut self) -> Result<ffmpeg::Packet, ffmpeg::Error> {
|
||||
fn get_next(&mut self) -> Result<ffmpeg::avcodec::Packet, ffmpeg::Error> {
|
||||
if self.pkts_left == 0 {
|
||||
return Err(ffmpeg::Error::eof());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user