mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-03 09:55:59 -05:00
use my own ffmpeg crate
This significantly improves safety of the ffmpeg interface. The complex ABIs aren't accessed directly from Rust. Instead, I have a small C wrapper which uses the ffmpeg C API and the C headers at compile-time to determine the proper ABI in the same way any C program using ffmpeg would, so that the ABI doesn't have to be duplicated in Rust code. I've tested with ffmpeg 2.x and ffmpeg 3.x; it seems to work properly with both where before ffmpeg 3.x caused segfaults. It still depends on ABI compatibility between the compiled and running versions. C programs need this, too, and normal shared library versioning practices provide this guarantee. But log both versions on startup for diagnosing problems with this. Fixes #7
This commit is contained in:
parent
8ff1d0dcb8
commit
857a66f29c
49
Cargo.lock
generated
49
Cargo.lock
generated
@ -5,8 +5,6 @@ dependencies = [
|
|||||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"cursive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cursive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"docopt 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"docopt 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ffmpeg 0.2.0-alpha.2 (git+https://github.com/scottlamb/rust-ffmpeg?branch=2.x)",
|
|
||||||
"ffmpeg-sys 2.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"http-entity 0.0.1 (git+https://github.com/scottlamb/http-entity?branch=hyper-0.11.x)",
|
"http-entity 0.0.1 (git+https://github.com/scottlamb/http-entity?branch=hyper-0.11.x)",
|
||||||
@ -17,6 +15,7 @@ dependencies = [
|
|||||||
"lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"memmap 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"mime 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"mime 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"moonfire-ffmpeg 0.0.1",
|
||||||
"mylog 0.1.0 (git+https://github.com/scottlamb/mylog)",
|
"mylog 0.1.0 (git+https://github.com/scottlamb/mylog)",
|
||||||
"openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
"openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
@ -66,11 +65,6 @@ dependencies = [
|
|||||||
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "0.9.1"
|
version = "0.9.1"
|
||||||
@ -161,25 +155,6 @@ name = "dtoa"
|
|||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ffmpeg"
|
|
||||||
version = "0.2.0-alpha.2"
|
|
||||||
source = "git+https://github.com/scottlamb/rust-ffmpeg?branch=2.x#a67d25cb85369597e0e4820172330e8a3168589f"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"ffmpeg-sys 2.8.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ffmpeg-sys"
|
|
||||||
version = "2.8.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.5"
|
version = "1.0.5"
|
||||||
@ -472,6 +447,16 @@ dependencies = [
|
|||||||
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moonfire-ffmpeg"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"gcc 0.3.50 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mylog"
|
name = "mylog"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -582,14 +567,6 @@ name = "num-traits"
|
|||||||
version = "0.1.39"
|
version = "0.1.39"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "num_cpus"
|
|
||||||
version = "0.2.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"libc 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num_cpus"
|
name = "num_cpus"
|
||||||
version = "1.5.1"
|
version = "1.5.1"
|
||||||
@ -1140,7 +1117,6 @@ dependencies = [
|
|||||||
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
|
"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
|
||||||
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
|
"checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
|
||||||
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
||||||
"checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c"
|
|
||||||
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
||||||
"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
|
"checksum byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c40977b0ee6b9885c9013cd41d9feffdd22deb3bb4dc3a71d901cc7a77de18c8"
|
||||||
"checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14"
|
"checksum bytes 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8b24f16593f445422331a5eed46b72f7f171f910fead4f2ea8f17e727e9c5c14"
|
||||||
@ -1152,8 +1128,6 @@ dependencies = [
|
|||||||
"checksum cursive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b717f6e01158b6d8bc6cad90badc2adfa8ce8a45594125efb90b854abc98ed93"
|
"checksum cursive 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b717f6e01158b6d8bc6cad90badc2adfa8ce8a45594125efb90b854abc98ed93"
|
||||||
"checksum docopt 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63e408eee8a772c5c61f62353992e3ebf51ef5c832dd04d986b3dc7d48c5b440"
|
"checksum docopt 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "63e408eee8a772c5c61f62353992e3ebf51ef5c832dd04d986b3dc7d48c5b440"
|
||||||
"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
|
"checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90"
|
||||||
"checksum ffmpeg 0.2.0-alpha.2 (git+https://github.com/scottlamb/rust-ffmpeg?branch=2.x)" = "<none>"
|
|
||||||
"checksum ffmpeg-sys 2.8.9 (registry+https://github.com/rust-lang/crates.io-index)" = "683f0fa41ebe43e7162184d172190e24318af66f7065006a36c2cc877f0e0edd"
|
|
||||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||||
"checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d"
|
"checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d"
|
||||||
"checksum fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab76cfd2aaa59b7bf6688ad9ba15bbae64bff97f04ea02144cfd3443e5c2866"
|
"checksum fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab76cfd2aaa59b7bf6688ad9ba15bbae64bff97f04ea02144cfd3443e5c2866"
|
||||||
@ -1200,7 +1174,6 @@ dependencies = [
|
|||||||
"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e"
|
"checksum num-iter 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d1891bd7b936f12349b7d1403761c8a0b85a18b148e9da4429d5d102c1a41e"
|
||||||
"checksum num-rational 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "33c881e104a26e1accc09449374c095ff2312c8e0c27fab7bbefe16eac7c776d"
|
"checksum num-rational 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "33c881e104a26e1accc09449374c095ff2312c8e0c27fab7bbefe16eac7c776d"
|
||||||
"checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6"
|
"checksum num-traits 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "1708c0628602a98b52fad936cf3edb9a107af06e52e49fdf0707e884456a6af6"
|
||||||
"checksum num_cpus 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "cee7e88156f3f9e19bdd598f8d6c9db7bf4078f99f8381f43a55b09648d1a6e3"
|
|
||||||
"checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a"
|
"checksum num_cpus 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e416ba127a4bb3ff398cb19546a8d0414f73352efe2857f4060d36f5fe5983a"
|
||||||
"checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba"
|
"checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba"
|
||||||
"checksum openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b34cd77cf91301fff3123fbd46b065c3b728b17a392835de34c397315dce5586"
|
"checksum openssl 0.9.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b34cd77cf91301fff3123fbd46b065c3b728b17a392835de34c397315dce5586"
|
||||||
|
12
Cargo.toml
12
Cargo.toml
@ -26,6 +26,7 @@ log = { version = "0.3", features = ["release_max_level_info"] }
|
|||||||
lru-cache = "0.1"
|
lru-cache = "0.1"
|
||||||
memmap = "0.5"
|
memmap = "0.5"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
|
moonfire-ffmpeg = { path = "ffmpeg" }
|
||||||
mylog = { git = "https://github.com/scottlamb/mylog" }
|
mylog = { git = "https://github.com/scottlamb/mylog" }
|
||||||
openssl = "0.9"
|
openssl = "0.9"
|
||||||
parking_lot = { version = "0.4", features = [] }
|
parking_lot = { version = "0.4", features = [] }
|
||||||
@ -51,17 +52,6 @@ version = "0.5"
|
|||||||
#default-features = false
|
#default-features = false
|
||||||
#features = ["termion"]
|
#features = ["termion"]
|
||||||
|
|
||||||
[dependencies.ffmpeg]
|
|
||||||
git = "https://github.com/scottlamb/rust-ffmpeg"
|
|
||||||
branch = "2.x"
|
|
||||||
default-features = false
|
|
||||||
features = ["codec", "format"]
|
|
||||||
|
|
||||||
[dependencies.ffmpeg-sys]
|
|
||||||
version = "2.8"
|
|
||||||
default-features = false
|
|
||||||
features = ["avcodec"]
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
||||||
|
30
README.md
30
README.md
@ -63,8 +63,6 @@ edge version from the command line via git:
|
|||||||
There are no binary packages of Moonfire NVR available yet, so it must be built
|
There are no binary packages of Moonfire NVR available yet, so it must be built
|
||||||
from source.
|
from source.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Moonfire NVR is written in the [Rust Programming
|
Moonfire NVR is written in the [Rust Programming
|
||||||
Language](https://www.rust-lang.org/en-US/). In the long term, I expect this
|
Language](https://www.rust-lang.org/en-US/). In the long term, I expect this
|
||||||
will result in a more secure, full-featured, easy-to-install software. In the
|
will result in a more secure, full-featured, easy-to-install software. In the
|
||||||
@ -74,18 +72,14 @@ project.
|
|||||||
|
|
||||||
You will need the following C libraries installed:
|
You will need the following C libraries installed:
|
||||||
|
|
||||||
* [ffmpeg](http://ffmpeg.org/) version 2.x, including `libavutil`,
|
* [ffmpeg](http://ffmpeg.org/) version 2.x or 3.x, including `libavutil`,
|
||||||
`libavcodec` (to inspect H.264 frames), and `libavformat` (to connect to RTSP
|
`libavcodec` (to inspect H.264 frames), and `libavformat` (to connect to RTSP
|
||||||
servers and write `.mp4` files).
|
servers and write `.mp4` files).
|
||||||
|
|
||||||
Note ffmpeg 3.x isn't supported yet by the Rust `ffmpeg` crate; see
|
Note ffmpeg library versions older than 55.1.101, along with all versions of
|
||||||
[rust-ffmpeg/issues/64](https://github.com/meh/rust-ffmpeg/issues/64).
|
the competing project [libav](http://libav.org), don't not support socket
|
||||||
|
timeouts for RTSP. For reliable reconnections on error, it's strongly
|
||||||
Additionally, ffmpeg library versions older than 55.1.101, along with
|
recommended to use ffmpeg library versions >= 55.1.101.
|
||||||
55.1.101, along with all versions of the competing project
|
|
||||||
[libav](http://libav.org), don't not support socket timeouts for RTSP. For
|
|
||||||
reliable reconnections on error, it's strongly recommended to use ffmpeg
|
|
||||||
library versions >= 55.1.101.
|
|
||||||
|
|
||||||
* [SQLite3](https://www.sqlite.org/).
|
* [SQLite3](https://www.sqlite.org/).
|
||||||
|
|
||||||
@ -102,7 +96,9 @@ all non-Rust dependencies:
|
|||||||
libavutil-dev \
|
libavutil-dev \
|
||||||
libncurses5-dev \
|
libncurses5-dev \
|
||||||
libncursesw5-dev \
|
libncursesw5-dev \
|
||||||
libsqlite3-dev
|
libsqlite3-dev \
|
||||||
|
libssl-dev \
|
||||||
|
pkgconf
|
||||||
|
|
||||||
Next, you need Rust 1.15+ and Cargo. The easiest way to install them is by following
|
Next, you need Rust 1.15+ and Cargo. The easiest way to install them is by following
|
||||||
the instructions at [rustup.rs](https://www.rustup.rs/).
|
the instructions at [rustup.rs](https://www.rustup.rs/).
|
||||||
@ -181,7 +177,7 @@ database. If the daemon is running, you will need to stop it temporarily:
|
|||||||
|
|
||||||
You can configure the system through a text-based user interface:
|
You can configure the system through a text-based user interface:
|
||||||
|
|
||||||
$ sudo -u moonfire-nvr moonfire-nvr config
|
$ sudo -u moonfire-nvr moonfire-nvr config 2>debug-log
|
||||||
|
|
||||||
In the user interface, add your cameras under the "Edit cameras" dialog.
|
In the user interface, add your cameras under the "Edit cameras" dialog.
|
||||||
There's a "Test" button to verify your settings directly from the dialog.
|
There's a "Test" button to verify your settings directly from the dialog.
|
||||||
@ -251,19 +247,13 @@ and `systemctl` may be of particular interest.
|
|||||||
|
|
||||||
# Troubleshooting
|
# Troubleshooting
|
||||||
|
|
||||||
While Moonfire NVR is running, logs will be written to stdout. The
|
While Moonfire NVR is running, logs will be written to stderr. The
|
||||||
`MOONFIRE_LOG` environmental variable controls the log level;
|
`MOONFIRE_LOG` environmental variable controls the log level;
|
||||||
`MOONFIRE_LOG=info` is the default. `MOONFIRE_FORMAT` controls the
|
`MOONFIRE_LOG=info` is the default. `MOONFIRE_FORMAT` controls the
|
||||||
logging style; options are `google` (default, like the Google glog package)
|
logging style; options are `google` (default, like the Google glog package)
|
||||||
or `google-systemd` (formatted for the systemd journal). If running through
|
or `google-systemd` (formatted for the systemd journal). If running through
|
||||||
systemd, try `sudo journalctl --unit moonfire-nvr` to view the logs.
|
systemd, try `sudo journalctl --unit moonfire-nvr` to view the logs.
|
||||||
|
|
||||||
If Moonfire NVR crashes with a `SIGSEGV`, the problem is likely an
|
|
||||||
incompatible version of the C `ffmpeg` libraries; use the latest 2.x release
|
|
||||||
instead. This is one of the Rust growing pains mentioned above. While most
|
|
||||||
code written in Rust is "safe", the foreign function interface is not only
|
|
||||||
unsafe but currently error-prone.
|
|
||||||
|
|
||||||
# <a name="help"></a> Getting help and getting involved
|
# <a name="help"></a> Getting help and getting involved
|
||||||
|
|
||||||
Please email the
|
Please email the
|
||||||
|
35
ffmpeg/Cargo.lock
generated
Normal file
35
ffmpeg/Cargo.lock
generated
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
[root]
|
||||||
|
name = "moonfire-ffmpeg"
|
||||||
|
version = "0.0.1"
|
||||||
|
dependencies = [
|
||||||
|
"gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gcc"
|
||||||
|
version = "0.3.51"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.3.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a"
|
||||||
|
"checksum libc 0.2.24 (registry+https://github.com/rust-lang/crates.io-index)" = "38f5c2b18a287cf78b4097db62e20f43cace381dc76ae5c0a3073067f78b7ddc"
|
||||||
|
"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b"
|
||||||
|
"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903"
|
17
ffmpeg/Cargo.toml
Normal file
17
ffmpeg/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "moonfire-ffmpeg"
|
||||||
|
version = "0.0.1"
|
||||||
|
authors = ["Scott Lamb <slamb@slamb.org>"]
|
||||||
|
readme = "../README.md"
|
||||||
|
#links = "ffmpeg"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
libc = "0.2"
|
||||||
|
log = { version = "0.3", features = ["release_max_level_info"] }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
gcc = "0.3"
|
||||||
|
pkg-config = "0.3"
|
54
ffmpeg/build.rs
Normal file
54
ffmpeg/build.rs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||||
|
// Copyright (C) 2017 Scott Lamb <slamb@slamb.org>
|
||||||
|
//
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
extern crate gcc;
|
||||||
|
extern crate pkg_config;
|
||||||
|
|
||||||
|
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("56.0").probe("libavformat").unwrap(),
|
||||||
|
];
|
||||||
|
let mut wrapper = gcc::Config::new();
|
||||||
|
|
||||||
|
// Pass compilation flags on to gcc. It'd be nice if pkg-config made this easier; see
|
||||||
|
// <https://github.com/alexcrichton/pkg-config-rs/issues/43>.
|
||||||
|
for lib in &libraries {
|
||||||
|
for p in &lib.include_paths {
|
||||||
|
wrapper.include(p);
|
||||||
|
}
|
||||||
|
for l in &lib.libs {
|
||||||
|
println!("lib: {}", l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wrapper.file("wrapper.c").compile("libwrapper.a");
|
||||||
|
}
|
433
ffmpeg/lib.rs
Normal file
433
ffmpeg/lib.rs
Normal file
@ -0,0 +1,433 @@
|
|||||||
|
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||||
|
// Copyright (C) 2017 Scott Lamb <slamb@slamb.org>
|
||||||
|
//
|
||||||
|
// 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/>.
|
||||||
|
|
||||||
|
extern crate libc;
|
||||||
|
#[macro_use] extern crate log;
|
||||||
|
|
||||||
|
use std::cell::{Ref, RefCell};
|
||||||
|
use std::ffi::CStr;
|
||||||
|
use std::fmt;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync;
|
||||||
|
|
||||||
|
static START: sync::Once = sync::ONCE_INIT;
|
||||||
|
|
||||||
|
//#[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_cctx_codec_id(ctx: *const AVCodecContext) -> libc::c_int;
|
||||||
|
fn moonfire_ffmpeg_cctx_codec_type(ctx: *const AVCodecContext) -> libc::c_int;
|
||||||
|
fn moonfire_ffmpeg_cctx_extradata(ctx: *const AVCodecContext) -> DataLen;
|
||||||
|
fn moonfire_ffmpeg_cctx_height(ctx: *const AVCodecContext) -> libc::c_int;
|
||||||
|
fn moonfire_ffmpeg_cctx_width(ctx: *const AVCodecContext) -> 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_codec(stream: *const AVStream) -> *const AVCodecContext;
|
||||||
|
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 u8, 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: libc::int64_t;
|
||||||
|
|
||||||
|
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) -> libc::int64_t;
|
||||||
|
fn moonfire_ffmpeg_packet_dts(p: *const AVPacket) -> libc::int64_t;
|
||||||
|
fn moonfire_ffmpeg_packet_duration(p: *const AVPacket) -> libc::c_int;
|
||||||
|
fn moonfire_ffmpeg_packet_set_pts(p: *mut AVPacket, pts: libc::int64_t);
|
||||||
|
fn moonfire_ffmpeg_packet_set_dts(p: *mut AVPacket, dts: libc::int64_t);
|
||||||
|
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 AVCodecContext {}
|
||||||
|
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 { _ctx: PhantomData, pkt: pkt })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn streams<'i>(&'i self) -> Streams<'i> {
|
||||||
|
Streams {
|
||||||
|
_owner: PhantomData,
|
||||||
|
streams: unsafe { moonfire_ffmpeg_fctx_streams(self.ctx) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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> {
|
||||||
|
_ctx: PhantomData<&'i InputFormatContext>,
|
||||||
|
pkt: Ref<'i, *mut AVPacket>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i> Packet<'i> {
|
||||||
|
pub fn is_key(&self) -> bool { unsafe { moonfire_ffmpeg_packet_is_key(*self.pkt) } }
|
||||||
|
pub fn pts(&self) -> Option<i64> {
|
||||||
|
match unsafe { moonfire_ffmpeg_packet_pts(*self.pkt) } {
|
||||||
|
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.pkt, real_pts); }
|
||||||
|
}
|
||||||
|
pub fn dts(&self) -> i64 { unsafe { moonfire_ffmpeg_packet_dts(*self.pkt) } }
|
||||||
|
pub fn set_dts(&mut self, dts: i64) {
|
||||||
|
unsafe { moonfire_ffmpeg_packet_set_dts(*self.pkt, dts); }
|
||||||
|
}
|
||||||
|
pub fn duration(&self) -> i32 { unsafe { moonfire_ffmpeg_packet_duration(*self.pkt) } }
|
||||||
|
pub fn set_duration(&mut self, dur: i32) {
|
||||||
|
unsafe { moonfire_ffmpeg_packet_set_duration(*self.pkt, dur) }
|
||||||
|
}
|
||||||
|
pub fn stream_index(&self) -> usize {
|
||||||
|
unsafe { moonfire_ffmpeg_packet_stream_index(*self.pkt) as usize }
|
||||||
|
}
|
||||||
|
pub fn data(&self) -> Option<&[u8]> {
|
||||||
|
unsafe {
|
||||||
|
let d = moonfire_ffmpeg_packet_data(*self.pkt);
|
||||||
|
if d.data.is_null() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(::std::slice::from_raw_parts(d.data, d.len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//pub fn deref(self) -> &'i InputFormatContext { self.ctx }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'i> Drop for Packet<'i> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
av_packet_unref(*self.pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Streams<'owner> {
|
||||||
|
_owner: PhantomData<&'owner ()>,
|
||||||
|
streams: StreamsLen,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'owner> Streams<'owner> {
|
||||||
|
pub fn get(&self, i: usize) -> Stream<'owner> {
|
||||||
|
assert!(i < self.streams.len);
|
||||||
|
Stream {
|
||||||
|
_owner: PhantomData,
|
||||||
|
stream: unsafe { *self.streams.streams.offset(i as isize) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn len(&self) -> usize { self.streams.len }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Stream<'o> {
|
||||||
|
_owner: PhantomData<&'o ()>,
|
||||||
|
stream: *const AVStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'o> Stream<'o> {
|
||||||
|
pub fn codec<'s>(&'s self) -> CodecContext<'s> {
|
||||||
|
CodecContext {
|
||||||
|
_owner: PhantomData,
|
||||||
|
ctx: unsafe { moonfire_ffmpeg_stream_codec(self.stream) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn time_base(&self) -> AVRational {
|
||||||
|
unsafe { moonfire_ffmpeg_stream_time_base(self.stream) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CodecContext<'s> {
|
||||||
|
_owner: PhantomData<&'s ()>,
|
||||||
|
ctx: *const AVCodecContext,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'s> CodecContext<'s> {
|
||||||
|
pub fn extradata(&self) -> &[u8] {
|
||||||
|
unsafe {
|
||||||
|
let d = moonfire_ffmpeg_cctx_extradata(self.ctx);
|
||||||
|
::std::slice::from_raw_parts(d.data, d.len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn width(&self) -> libc::c_int { unsafe { moonfire_ffmpeg_cctx_width(self.ctx) } }
|
||||||
|
pub fn height(&self) -> libc::c_int { unsafe { moonfire_ffmpeg_cctx_height(self.ctx) } }
|
||||||
|
pub fn codec_id(&self) -> CodecId {
|
||||||
|
CodecId(unsafe { moonfire_ffmpeg_cctx_codec_id(self.ctx) })
|
||||||
|
}
|
||||||
|
pub fn codec_type(&self) -> MediaType {
|
||||||
|
MediaType(unsafe { moonfire_ffmpeg_cctx_codec_type(self.ctx) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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, Debug)]
|
||||||
|
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<&std::error::Error> { None }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
const ARRAYLEN: usize = 64;
|
||||||
|
let mut buf = [0u8; ARRAYLEN];
|
||||||
|
unsafe { av_strerror(self.0, buf.as_mut_ptr(), ARRAYLEN) };
|
||||||
|
f.write_str(std::str::from_utf8(&buf).map_err(|_| fmt::Error)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Version(libc::c_int);
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
moonfire_ffmpeg_init();
|
||||||
|
av_register_all();
|
||||||
|
if avformat_network_init() < 0 {
|
||||||
|
panic!("avformat_network_init failed");
|
||||||
|
}
|
||||||
|
info!("Initialized ffmpeg. Versions:\n\
|
||||||
|
avcodec compiled={} running={}\n\
|
||||||
|
avformat compiled={} running={}\n\
|
||||||
|
avutil compiled={} running={}",
|
||||||
|
Version(moonfire_ffmpeg_compiled_libavcodec_version),
|
||||||
|
Version(avcodec_version()),
|
||||||
|
Version(moonfire_ffmpeg_compiled_libavformat_version),
|
||||||
|
Version(avformat_version()),
|
||||||
|
Version(moonfire_ffmpeg_compiled_libavutil_version),
|
||||||
|
Version(avutil_version()));
|
||||||
|
});
|
||||||
|
Ffmpeg{}
|
||||||
|
}
|
||||||
|
}
|
128
ffmpeg/wrapper.c
Normal file
128
ffmpeg/wrapper.c
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// This file is part of Moonfire NVR, a security camera digital video recorder.
|
||||||
|
// Copyright (C) 2017 Scott Lamb <slamb@slamb.org>
|
||||||
|
//
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void moonfire_ffmpeg_init(void) {
|
||||||
|
if (av_lockmgr_register(&lock_callback) < 0) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct moonfire_ffmpeg_streams {
|
||||||
|
AVStream** streams;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct moonfire_ffmpeg_data {
|
||||||
|
const char *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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecContext *moonfire_ffmpeg_stream_codec(AVStream *stream) { return stream->codec; }
|
||||||
|
AVRational moonfire_ffmpeg_stream_time_base(AVStream *stream) { return stream->time_base; }
|
||||||
|
|
||||||
|
int moonfire_ffmpeg_cctx_codec_id(AVCodecContext *cctx) { return cctx->codec_id; }
|
||||||
|
int moonfire_ffmpeg_cctx_codec_type(AVCodecContext *cctx) { return cctx->codec_type; }
|
||||||
|
struct moonfire_ffmpeg_data moonfire_ffmpeg_cctx_extradata(AVCodecContext *cctx) {
|
||||||
|
struct moonfire_ffmpeg_data d = {cctx->extradata, cctx->extradata_size};
|
||||||
|
return d;
|
||||||
|
}
|
||||||
|
int moonfire_ffmpeg_cctx_height(AVCodecContext *cctx) { return cctx->height; }
|
||||||
|
int moonfire_ffmpeg_cctx_width(AVCodecContext *cctx) { return cctx->width; }
|
@ -182,8 +182,6 @@ impl SampleFileDir {
|
|||||||
write!(&mut buf[..36], "{}", uuid.hyphenated()).expect("can't format uuid to pathname buf");
|
write!(&mut buf[..36], "{}", uuid.hyphenated()).expect("can't format uuid to pathname buf");
|
||||||
|
|
||||||
// libc::c_char seems to be i8 on some platforms (Linux/arm) and u8 on others (Linux/amd64).
|
// libc::c_char seems to be i8 on some platforms (Linux/arm) and u8 on others (Linux/amd64).
|
||||||
// Transmute, suppressing the warning that happens on platforms in which it's already u8.
|
|
||||||
#[allow(useless_transmute)]
|
|
||||||
unsafe { mem::transmute::<[u8; 37], [libc::c_char; 37]>(buf) }
|
unsafe { mem::transmute::<[u8; 37], [libc::c_char; 37]>(buf) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,8 +34,8 @@ extern crate uuid;
|
|||||||
|
|
||||||
use core::ops::Deref;
|
use core::ops::Deref;
|
||||||
use core::num;
|
use core::num;
|
||||||
use ffmpeg;
|
|
||||||
use openssl::error::ErrorStack;
|
use openssl::error::ErrorStack;
|
||||||
|
use moonfire_ffmpeg;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
@ -127,9 +127,9 @@ impl From<serde_json::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ffmpeg::Error> for Error {
|
impl From<moonfire_ffmpeg::Error> for Error {
|
||||||
fn from(err: ffmpeg::Error) -> Self {
|
fn from(err: moonfire_ffmpeg::Error) -> Self {
|
||||||
Error{description: format!("{} ({})", err.description(), err), cause: Some(Box::new(err))}
|
Error{description: format!("ffmpeg: {}", err), cause: Some(Box::new(err))}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +33,6 @@
|
|||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate core;
|
extern crate core;
|
||||||
extern crate docopt;
|
extern crate docopt;
|
||||||
#[macro_use] extern crate ffmpeg;
|
|
||||||
extern crate ffmpeg_sys;
|
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
extern crate http_entity;
|
extern crate http_entity;
|
||||||
@ -47,6 +45,7 @@ extern crate reffers;
|
|||||||
extern crate rusqlite;
|
extern crate rusqlite;
|
||||||
extern crate memmap;
|
extern crate memmap;
|
||||||
extern crate mime;
|
extern crate mime;
|
||||||
|
extern crate moonfire_ffmpeg;
|
||||||
extern crate mylog;
|
extern crate mylog;
|
||||||
extern crate openssl;
|
extern crate openssl;
|
||||||
extern crate parking_lot;
|
extern crate parking_lot;
|
||||||
|
16
src/mp4.rs
16
src/mp4.rs
@ -83,7 +83,7 @@ use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
|||||||
use db;
|
use db;
|
||||||
use dir;
|
use dir;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use futures::{Future, Stream};
|
use futures::Stream;
|
||||||
use futures::stream;
|
use futures::stream;
|
||||||
use http_entity;
|
use http_entity;
|
||||||
use hyper::header;
|
use hyper::header;
|
||||||
@ -1242,8 +1242,8 @@ mod tests {
|
|||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use db;
|
use db;
|
||||||
use dir;
|
use dir;
|
||||||
|
use futures::Future;
|
||||||
use futures::Stream as FuturesStream;
|
use futures::Stream as FuturesStream;
|
||||||
use ffmpeg;
|
|
||||||
use hyper::header;
|
use hyper::header;
|
||||||
use openssl::hash;
|
use openssl::hash;
|
||||||
use recording::{self, TIME_UNITS_PER_SEC};
|
use recording::{self, TIME_UNITS_PER_SEC};
|
||||||
@ -1475,14 +1475,14 @@ mod tests {
|
|||||||
loop {
|
loop {
|
||||||
let pkt = match input.get_next() {
|
let pkt = match input.get_next() {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(ffmpeg::Error::Eof) => { break; },
|
Err(e) if e.is_eof() => { break; },
|
||||||
Err(e) => { panic!("unexpected input error: {}", e); },
|
Err(e) => { panic!("unexpected input error: {}", e); },
|
||||||
};
|
};
|
||||||
let pts = pkt.pts().unwrap();
|
let pts = pkt.pts().unwrap();
|
||||||
frame_time += recording::Duration(pkt.duration());
|
frame_time += recording::Duration(pkt.duration() as i64);
|
||||||
output.write(pkt.data().expect("packet without data"), frame_time, pts,
|
output.write(pkt.data().expect("packet without data"), frame_time, pts,
|
||||||
pkt.is_key()).unwrap();
|
pkt.is_key()).unwrap();
|
||||||
end_pts = Some(pts + pkt.duration());
|
end_pts = Some(pts + pkt.duration() as i64);
|
||||||
}
|
}
|
||||||
output.close(end_pts).unwrap();
|
output.close(end_pts).unwrap();
|
||||||
db.syncer_channel.flush();
|
db.syncer_channel.flush();
|
||||||
@ -1528,12 +1528,12 @@ mod tests {
|
|||||||
loop {
|
loop {
|
||||||
let orig_pkt = match orig.get_next() {
|
let orig_pkt = match orig.get_next() {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => Some(p),
|
||||||
Err(ffmpeg::Error::Eof) => None,
|
Err(e) if e.is_eof() => None,
|
||||||
Err(e) => { panic!("unexpected input error: {}", e); },
|
Err(e) => { panic!("unexpected input error: {}", e); },
|
||||||
};
|
};
|
||||||
let new_pkt = match new.get_next() {
|
let new_pkt = match new.get_next() {
|
||||||
Ok(p) => Some(p),
|
Ok(p) => Some(p),
|
||||||
Err(ffmpeg::Error::Eof) => { break; },
|
Err(e) if e.is_eof() => { break; },
|
||||||
Err(e) => { panic!("unexpected input error: {}", e); },
|
Err(e) => { panic!("unexpected input error: {}", e); },
|
||||||
};
|
};
|
||||||
let (orig_pkt, new_pkt) = match (orig_pkt, new_pkt) {
|
let (orig_pkt, new_pkt) = match (orig_pkt, new_pkt) {
|
||||||
@ -1545,7 +1545,7 @@ mod tests {
|
|||||||
assert_eq!(orig_pkt.dts(), new_pkt.dts() + pts_offset);
|
assert_eq!(orig_pkt.dts(), new_pkt.dts() + pts_offset);
|
||||||
assert_eq!(orig_pkt.data(), new_pkt.data());
|
assert_eq!(orig_pkt.data(), new_pkt.data());
|
||||||
assert_eq!(orig_pkt.is_key(), new_pkt.is_key());
|
assert_eq!(orig_pkt.is_key(), new_pkt.is_key());
|
||||||
final_durations = Some((orig_pkt.duration(), new_pkt.duration()));
|
final_durations = Some((orig_pkt.duration() as i64, new_pkt.duration() as i64));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((orig_dur, new_dur)) = final_durations {
|
if let Some((orig_dur, new_dur)) = final_durations {
|
||||||
|
118
src/stream.rs
118
src/stream.rs
@ -29,14 +29,10 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use ffmpeg::{self, format, media};
|
|
||||||
use ffmpeg_sys::{self, AVLockOp};
|
|
||||||
use h264;
|
use h264;
|
||||||
use libc::{self, c_int, c_void};
|
use moonfire_ffmpeg;
|
||||||
use std::mem;
|
use std::ffi::{CStr, CString};
|
||||||
use std::ptr;
|
|
||||||
use std::result::Result;
|
use std::result::Result;
|
||||||
use std::slice;
|
|
||||||
use std::sync;
|
use std::sync;
|
||||||
|
|
||||||
static START: sync::Once = sync::ONCE_INIT;
|
static START: sync::Once = sync::ONCE_INIT;
|
||||||
@ -52,41 +48,13 @@ pub enum Source<'a> {
|
|||||||
Rtsp(&'a str), // url, for production use.
|
Rtsp(&'a str), // url, for production use.
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: I think this should be provided by ffmpeg-sys. Otherwise, ffmpeg-sys is thread-hostile,
|
|
||||||
// which I believe is not allowed at all in Rust. (Also, this method's signature should include
|
|
||||||
// unsafe.)
|
|
||||||
extern "C" fn lock_callback(untyped_ptr: *mut *mut c_void, op: AVLockOp) -> c_int {
|
|
||||||
unsafe {
|
|
||||||
let ptr = mem::transmute::<*mut *mut c_void, *mut *mut libc::pthread_mutex_t>(untyped_ptr);
|
|
||||||
match op {
|
|
||||||
AVLockOp::AV_LOCK_CREATE => {
|
|
||||||
let m = Box::<libc::pthread_mutex_t>::new(mem::uninitialized());
|
|
||||||
*ptr = Box::into_raw(m);
|
|
||||||
libc::pthread_mutex_init(*ptr, ptr::null());
|
|
||||||
},
|
|
||||||
AVLockOp::AV_LOCK_DESTROY => {
|
|
||||||
libc::pthread_mutex_destroy(*ptr);
|
|
||||||
Box::from_raw(*ptr); // delete.
|
|
||||||
*ptr = ptr::null_mut();
|
|
||||||
},
|
|
||||||
AVLockOp::AV_LOCK_OBTAIN => {
|
|
||||||
libc::pthread_mutex_lock(*ptr);
|
|
||||||
},
|
|
||||||
AVLockOp::AV_LOCK_RELEASE => {
|
|
||||||
libc::pthread_mutex_unlock(*ptr);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Opener<S : Stream> : Sync {
|
pub trait Opener<S : Stream> : Sync {
|
||||||
fn open(&self, src: Source) -> Result<S, Error>;
|
fn open(&self, src: Source) -> Result<S, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Stream {
|
pub trait Stream {
|
||||||
fn get_extra_data(&self) -> Result<h264::ExtraData, Error>;
|
fn get_extra_data(&self) -> Result<h264::ExtraData, Error>;
|
||||||
fn get_next(&mut self) -> Result<ffmpeg::Packet, ffmpeg::Error>;
|
fn get_next<'p>(&'p mut self) -> Result<moonfire_ffmpeg::Packet<'p>, moonfire_ffmpeg::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Ffmpeg {}
|
pub struct Ffmpeg {}
|
||||||
@ -94,51 +62,68 @@ pub struct Ffmpeg {}
|
|||||||
impl Ffmpeg {
|
impl Ffmpeg {
|
||||||
fn new() -> Ffmpeg {
|
fn new() -> Ffmpeg {
|
||||||
START.call_once(|| {
|
START.call_once(|| {
|
||||||
unsafe { ffmpeg_sys::av_lockmgr_register(lock_callback); };
|
moonfire_ffmpeg::Ffmpeg::new();
|
||||||
ffmpeg::init().unwrap();
|
//ffmpeg::init().unwrap();
|
||||||
ffmpeg::format::network::init();
|
//ffmpeg::format::network::init();
|
||||||
});
|
});
|
||||||
Ffmpeg{}
|
Ffmpeg{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! c_str {
|
||||||
|
($s:expr) => { {
|
||||||
|
unsafe { CStr::from_ptr(concat!($s, "\0").as_ptr() as *const i8) }
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
|
||||||
impl Opener<FfmpegStream> for Ffmpeg {
|
impl Opener<FfmpegStream> for Ffmpeg {
|
||||||
fn open(&self, src: Source) -> Result<FfmpegStream, Error> {
|
fn open(&self, src: Source) -> Result<FfmpegStream, Error> {
|
||||||
let (input, discard_first) = match src {
|
use moonfire_ffmpeg::InputFormatContext;
|
||||||
|
let (mut input, discard_first) = match src {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
Source::File(filename) =>
|
Source::File(filename) =>
|
||||||
(format::input_with(&format!("file:{}", filename), ffmpeg::Dictionary::new())?,
|
(InputFormatContext::open(&CString::new(format!("file:{}", filename)).unwrap(),
|
||||||
|
&mut moonfire_ffmpeg::Dictionary::new())?,
|
||||||
false),
|
false),
|
||||||
Source::Rtsp(url) => {
|
Source::Rtsp(url) => {
|
||||||
let open_options = dict![
|
let mut open_options = moonfire_ffmpeg::Dictionary::new();
|
||||||
"rtsp_transport" => "tcp",
|
open_options.set(c_str!("rtsp_transport"), c_str!("tcp")).unwrap();
|
||||||
// https://trac.ffmpeg.org/ticket/5018 workaround attempt.
|
// https://trac.ffmpeg.org/ticket/5018 workaround attempt.
|
||||||
"probesize" => "262144",
|
open_options.set(c_str!("probesize"), c_str!("262144")).unwrap();
|
||||||
"user-agent" => "moonfire-nvr",
|
open_options.set(c_str!("user-agent"), c_str!("moonfire-nvr")).unwrap();
|
||||||
// 10-second socket timeout, in microseconds.
|
// 10-second socket timeout, in microseconds.
|
||||||
"stimeout" => "10000000"
|
open_options.set(c_str!("stimeout"), c_str!("10000000")).unwrap();
|
||||||
];
|
let i = InputFormatContext::open(&CString::new(url).unwrap(), &mut open_options)?;
|
||||||
(format::input_with(&url, open_options)?, true)
|
if !open_options.empty() {
|
||||||
|
warn!("While opening URL {}, some options were not understood: {}",
|
||||||
|
url, open_options);
|
||||||
|
}
|
||||||
|
(i, true)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
input.find_stream_info()?;
|
||||||
|
|
||||||
// Find the video stream.
|
// Find the video stream.
|
||||||
let mut video_i = None;
|
let mut video_i = None;
|
||||||
for (i, stream) in input.streams().enumerate() {
|
{
|
||||||
if stream.codec().medium() == media::Type::Video {
|
let s = input.streams();
|
||||||
|
for i in 0 .. s.len() {
|
||||||
|
if s.get(i).codec().codec_type().is_video() {
|
||||||
debug!("Video stream index is {}", i);
|
debug!("Video stream index is {}", i);
|
||||||
video_i = Some(i);
|
video_i = Some(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
let video_i = match video_i {
|
let video_i = match video_i {
|
||||||
Some(i) => i,
|
Some(i) => i,
|
||||||
None => { return Err(Error::new("no video stream".to_owned())) },
|
None => { return Err(Error::new("no video stream".to_owned())) },
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut stream = FfmpegStream{
|
let mut stream = FfmpegStream{
|
||||||
input: input,
|
input,
|
||||||
video_i: video_i,
|
video_i,
|
||||||
};
|
};
|
||||||
|
|
||||||
if discard_first {
|
if discard_first {
|
||||||
@ -151,30 +136,31 @@ impl Opener<FfmpegStream> for Ffmpeg {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct FfmpegStream {
|
pub struct FfmpegStream {
|
||||||
input: format::context::Input,
|
input: moonfire_ffmpeg::InputFormatContext,
|
||||||
video_i: usize,
|
video_i: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for FfmpegStream {
|
impl Stream for FfmpegStream {
|
||||||
fn get_extra_data(&self) -> Result<h264::ExtraData, Error> {
|
fn get_extra_data(&self) -> Result<h264::ExtraData, Error> {
|
||||||
let video = self.input.stream(self.video_i).expect("can't get video stream known to exist");
|
let video = self.input.streams().get(self.video_i);
|
||||||
|
let tb = video.time_base();
|
||||||
|
if tb.num != 1 || tb.den != 90000 {
|
||||||
|
return Err(Error::new(format!("video stream has timebase {}/{}; expected 1/90000",
|
||||||
|
tb.num, tb.den)));
|
||||||
|
}
|
||||||
let codec = video.codec();
|
let codec = video.codec();
|
||||||
let (extradata, width, height) = unsafe {
|
let codec_id = codec.codec_id();
|
||||||
let ptr = codec.as_ptr();
|
if !codec_id.is_h264() {
|
||||||
(slice::from_raw_parts((*ptr).extradata, (*ptr).extradata_size as usize),
|
return Err(Error::new(format!("stream's video codec {:?} is not h264", codec_id)));
|
||||||
(*ptr).width as u16,
|
}
|
||||||
(*ptr).height as u16)
|
h264::ExtraData::parse(codec.extradata(), codec.width() as u16, codec.height() as u16)
|
||||||
};
|
|
||||||
// TODO: verify video stream is h264.
|
|
||||||
h264::ExtraData::parse(extradata, width, height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_next(&mut self) -> Result<ffmpeg::Packet, ffmpeg::Error> {
|
fn get_next<'i>(&'i mut self) -> Result<moonfire_ffmpeg::Packet<'i>, moonfire_ffmpeg::Error> {
|
||||||
let mut pkt = ffmpeg::Packet::empty();
|
|
||||||
loop {
|
loop {
|
||||||
pkt.read(&mut self.input)?;
|
let p = self.input.read_frame()?;
|
||||||
if pkt.stream() == self.video_i {
|
if p.stream_index() == self.video_i {
|
||||||
return Ok(pkt);
|
return Ok(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,6 @@ impl<'a, C, S> Streamer<'a, C, S> where C: 'a + Clocks, S: 'a + stream::Stream {
|
|||||||
|
|
||||||
let mut stream = self.opener.open(stream::Source::Rtsp(&self.url))?;
|
let mut stream = self.opener.open(stream::Source::Rtsp(&self.url))?;
|
||||||
let realtime_offset = self.clocks.realtime() - self.clocks.monotonic();
|
let realtime_offset = self.clocks.realtime() - self.clocks.monotonic();
|
||||||
// TODO: verify time base.
|
|
||||||
// TODO: verify width/height.
|
// TODO: verify width/height.
|
||||||
let extra_data = stream.get_extra_data()?;
|
let extra_data = stream.get_extra_data()?;
|
||||||
let video_sample_entry_id =
|
let video_sample_entry_id =
|
||||||
@ -190,9 +189,8 @@ mod tests {
|
|||||||
use clock::{self, Clocks};
|
use clock::{self, Clocks};
|
||||||
use db;
|
use db;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use ffmpeg;
|
|
||||||
use ffmpeg::packet::Mut;
|
|
||||||
use h264;
|
use h264;
|
||||||
|
use moonfire_ffmpeg;
|
||||||
use recording;
|
use recording;
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
@ -228,9 +226,9 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Stream for ProxyingStream<'a> {
|
impl<'a> Stream for ProxyingStream<'a> {
|
||||||
fn get_next(&mut self) -> Result<ffmpeg::Packet, ffmpeg::Error> {
|
fn get_next(&mut self) -> Result<moonfire_ffmpeg::Packet, moonfire_ffmpeg::Error> {
|
||||||
if self.pkts_left == 0 {
|
if self.pkts_left == 0 {
|
||||||
return Err(ffmpeg::Error::Eof);
|
return Err(moonfire_ffmpeg::Error::eof());
|
||||||
}
|
}
|
||||||
self.pkts_left -= 1;
|
self.pkts_left -= 1;
|
||||||
|
|
||||||
@ -240,7 +238,7 @@ mod tests {
|
|||||||
// Avoid accumulating conversion error by tracking the total amount to sleep and how
|
// Avoid accumulating conversion error by tracking the total amount to sleep and how
|
||||||
// much we've already slept, rather than considering each frame in isolation.
|
// much we've already slept, rather than considering each frame in isolation.
|
||||||
{
|
{
|
||||||
let goal = pkt.pts().unwrap() + pkt.duration();
|
let goal = pkt.pts().unwrap() + pkt.duration() as i64;
|
||||||
let goal = time::Duration::nanoseconds(
|
let goal = time::Duration::nanoseconds(
|
||||||
goal * 1_000_000_000 / recording::TIME_UNITS_PER_SEC);
|
goal * 1_000_000_000 / recording::TIME_UNITS_PER_SEC);
|
||||||
let duration = goal - self.slept;
|
let duration = goal - self.slept;
|
||||||
@ -254,15 +252,12 @@ mod tests {
|
|||||||
self.ts_offset_pkts_left -= 1;
|
self.ts_offset_pkts_left -= 1;
|
||||||
let old_pts = pkt.pts().unwrap();
|
let old_pts = pkt.pts().unwrap();
|
||||||
let old_dts = pkt.dts();
|
let old_dts = pkt.dts();
|
||||||
unsafe {
|
pkt.set_pts(Some(old_pts + self.ts_offset));
|
||||||
let pkt = pkt.as_mut_ptr();
|
pkt.set_dts(old_dts + self.ts_offset);
|
||||||
(*pkt).pts = old_pts + self.ts_offset;
|
|
||||||
(*pkt).dts = old_dts + self.ts_offset;
|
|
||||||
|
|
||||||
// In a real rtsp stream, the duration of a packet is not known until the
|
// In a real rtsp stream, the duration of a packet is not known until the
|
||||||
// next packet. ffmpeg's duration is an unreliable estimate.
|
// next packet. ffmpeg's duration is an unreliable estimate.
|
||||||
(*pkt).duration = recording::TIME_UNITS_PER_SEC as i32;
|
pkt.set_duration(recording::TIME_UNITS_PER_SEC as i32);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(pkt)
|
Ok(pkt)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user