mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Add a gstreamer servosrc plugin
This commit is contained in:
parent
a562808ebb
commit
69acec137d
11 changed files with 1079 additions and 6 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
@ -581,10 +581,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.2"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1cce36c92cb605414e9b824f866f5babe0a0368e39ea07393b9b63cf3844c0e6"
|
||||
checksum = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"time",
|
||||
|
@ -1753,6 +1754,21 @@ dependencies = [
|
|||
"lzw",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "git2"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb400360e8a4d61b10e648285bbfa919bbf9519d0d5d5720354456f44349226"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
"libgit2-sys",
|
||||
"log",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gl_generator"
|
||||
version = "0.11.0"
|
||||
|
@ -1915,6 +1931,17 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gst-plugin-version-helper"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90db297b7d445b643b411be6c43e33b78670d0579bec256aeff644b693b0eccb"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"git2",
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer"
|
||||
version = "0.14.5"
|
||||
|
@ -2820,9 +2847,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "lazycell"
|
||||
|
@ -2872,6 +2899,20 @@ dependencies = [
|
|||
"take_mut",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libgit2-sys"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c179ed6d19cd3a051e68c177fbbc214e79ac4724fac3a850ec9f3d3eb8a5578"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libssh2-sys",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.5.0"
|
||||
|
@ -2955,6 +2996,20 @@ dependencies = [
|
|||
"webxr-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libssh2-sys"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fcd5a428a31cbbfe059812d74f4b6cd3b9b7426c2bdaec56993c5365da1c328"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"openssl-sys",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.0.25"
|
||||
|
@ -3642,6 +3697,12 @@ dependencies = [
|
|||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.35"
|
||||
|
@ -4714,6 +4775,29 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo-gst-plugin"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"euclid",
|
||||
"gleam 0.6.18",
|
||||
"glib",
|
||||
"gst-plugin-version-helper",
|
||||
"gstreamer",
|
||||
"gstreamer-base",
|
||||
"gstreamer-gl",
|
||||
"gstreamer-video",
|
||||
"lazy_static",
|
||||
"libservo",
|
||||
"log",
|
||||
"servo-media",
|
||||
"sparkle",
|
||||
"surfman",
|
||||
"surfman-chains",
|
||||
"surfman-chains-api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "servo-media"
|
||||
version = "0.1.0"
|
||||
|
@ -5389,9 +5473,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "surfman-chains"
|
||||
version = "0.2.0"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b0d399c15d8f4aad59cd98edbf58e4c96a3b711cf8daf1b006acab8aec97cb"
|
||||
checksum = "a2c1b5976b229a807a9e79b3b5248da577948b9882c77f2afce27cf562f80e22"
|
||||
dependencies = [
|
||||
"euclid",
|
||||
"fnv",
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"ports/glutin",
|
||||
"ports/gstplugin",
|
||||
"ports/libsimpleservo/capi/",
|
||||
"ports/libsimpleservo/jniapi/",
|
||||
"ports/libmlservo/",
|
||||
|
|
|
@ -207,6 +207,7 @@ def linux_tidy_unit():
|
|||
./mach build --dev --features canvas2d-raqote
|
||||
./mach build --dev --features layout-2020
|
||||
./mach build --dev --libsimpleservo
|
||||
./mach build --dev -p servo-gst-plugin
|
||||
./mach test-tidy --no-progress --self-test
|
||||
|
||||
./etc/memory_reports_over_time.py --test
|
||||
|
@ -485,6 +486,8 @@ def windows_unit(cached=True):
|
|||
"mach smoketest --angle",
|
||||
"mach package --dev",
|
||||
"mach build --dev --libsimpleservo",
|
||||
"mach build --dev -p servo-gst-plugin",
|
||||
|
||||
)
|
||||
.with_artifacts("repo/target/debug/msi/Servo.exe",
|
||||
"repo/target/debug/msi/Servo.zip")
|
||||
|
|
38
ports/gstplugin/Cargo.toml
Normal file
38
ports/gstplugin/Cargo.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
[package]
|
||||
name = "servo-gst-plugin"
|
||||
description = "A GStreamer plugin that provides servosrc"
|
||||
version = "0.0.1"
|
||||
authors = ["The Servo Project Developers"]
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
repository = "https://github.com/servo/servo/"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "gstservoplugin"
|
||||
crate-type = ["cdylib"]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
crossbeam-channel = "0.3"
|
||||
euclid = "0.20"
|
||||
gleam = "0.6"
|
||||
glib = { version = "0.8", features = ["subclassing"] }
|
||||
gstreamer = { version = "0.14", features = ["subclassing"] }
|
||||
gstreamer-base = { version = "0.14", features = ["subclassing"] }
|
||||
gstreamer-gl = { version = "0.14", features = ["v1_16"] }
|
||||
gstreamer-video = { version = "0.14", features = ["subclassing"] }
|
||||
log = "0.4"
|
||||
lazy_static = "1.4"
|
||||
libservo = {path = "../../components/servo"}
|
||||
servo-media = {git = "https://github.com/servo/media"}
|
||||
sparkle = "0.1"
|
||||
# NOTE: the sm-angle-default feature only enables angle on windows, not other platforms!
|
||||
surfman = { version = "0.1", features = ["sm-angle-default", "sm-osmesa"] }
|
||||
surfman-chains-api = "0.2"
|
||||
surfman-chains = "0.2.1"
|
||||
|
||||
[build-dependencies]
|
||||
gst-plugin-version-helper = "0.1"
|
||||
|
131
ports/gstplugin/README.md
Normal file
131
ports/gstplugin/README.md
Normal file
|
@ -0,0 +1,131 @@
|
|||
# A GStreamer plugin which runs servo
|
||||
|
||||
## Build
|
||||
|
||||
```
|
||||
./mach build -r -p servo-gst-plugin
|
||||
```
|
||||
|
||||
## Install
|
||||
|
||||
By default, gstreamer's plugin finder will complain about any libraries it finds that aren't
|
||||
gstreamer plugins, so we need to have a directory just for plugins:
|
||||
```
|
||||
mkdir target/gstplugins
|
||||
```
|
||||
|
||||
To install:
|
||||
```
|
||||
cp target/release/libgstservoplugin.* target/gstplugins
|
||||
```
|
||||
## Run
|
||||
|
||||
To run locally:
|
||||
```
|
||||
GST_PLUGIN_PATH=target/gstplugins \
|
||||
gst-launch-1.0 servosrc \
|
||||
! queue \
|
||||
! video/x-raw,framerate=25/1,width=512,height=512 \
|
||||
! videoflip video-direction=vert \
|
||||
! autovideosink
|
||||
```
|
||||
|
||||
To stream over the network:
|
||||
```
|
||||
GST_PLUGIN_PATH=target/gstplugins \
|
||||
gst-launch-1.0 servosrc \
|
||||
! queue \
|
||||
! video/x-raw,framerate=25/1,width=512,height=512 \
|
||||
! videoconvert \
|
||||
! videoflip video-direction=vert \
|
||||
! theoraenc \
|
||||
! oggmux \
|
||||
! tcpserversink host=127.0.0.1 port=8080
|
||||
```
|
||||
|
||||
To save to a file:
|
||||
```
|
||||
GST_PLUGIN_PATH=target/gstplugins \
|
||||
gst-launch-1.0 servosrc num-buffers=2000 \
|
||||
! queue \
|
||||
! video/x-raw,framerate=25/1,width=512,height=512 \
|
||||
! videoconvert \
|
||||
! videoflip video-direction=vert \
|
||||
! theoraenc \
|
||||
! oggmux \
|
||||
! filesink location=test.ogg
|
||||
```
|
||||
|
||||
*Note*: killing the gstreamer pipeline with control-C sometimes locks up macOS to the point
|
||||
of needing a power cycle. Killing the pipeline by closing the window seems to work.
|
||||
|
||||
## Troubleshooting building the plugin
|
||||
|
||||
You may need to make sure rust picks up the right gstreamer, for example:
|
||||
```
|
||||
PKG_CONFIG_PATH=$PWD/support/linux/gstreamer/gst/lib \
|
||||
LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib \
|
||||
./mach build -r -p servo-gst-plugin
|
||||
```
|
||||
|
||||
## Troubleshooting running the plugin
|
||||
|
||||
*Currently x11 support is broken!*
|
||||
|
||||
First try:
|
||||
```
|
||||
GST_PLUGIN_PATH=target/gstplugins \
|
||||
gst-inspect-1.0 servosrc
|
||||
```
|
||||
|
||||
If that doesn't work, try:
|
||||
```
|
||||
GST_PLUGIN_PATH=target/gstplugins \
|
||||
gst-in2spect-1.0 target/gstplugins/libgstservoplugin.so
|
||||
```
|
||||
|
||||
If you get reports about the plugin being blacklisted, remove the (global!) gstreamer cache, e.g. under Linux:
|
||||
```
|
||||
rm -r ~/.cache/gstreamer-1.0
|
||||
```
|
||||
|
||||
If you get complaints about not being able to find libraries, set `LD_LIBRARY_PATH`, e.g. to use Servo's Linux gstreamer:
|
||||
```
|
||||
LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib
|
||||
```
|
||||
|
||||
If you get complaints `cannot allocate memory in static TLS block` this is caused by gstreamer initializing threads using
|
||||
the system alloc, which causes problems if those threads run Rust code that uses jemalloc. The fix is to preload the plugin:
|
||||
```
|
||||
LD_PRELOAD=$PWD/target/gstplugins/libgstservoplugin.so
|
||||
```
|
||||
|
||||
You may need to set `GST_PLUGIN_SCANNER`, e.g. to use Servo's:
|
||||
```
|
||||
GST_PLUGIN_SCANNER=$PWD/support/linux/gstreamer/gst/libexec/gstreamer-1.0/gst-plugin-scanner
|
||||
```
|
||||
|
||||
You may need to include other directories on the plugin search path, e.g. Servo's gstreamer:
|
||||
```
|
||||
GST_PLUGIN_PATH=$PWD/target/gstplugins/:$PWD/support/linux/gstreamer/gst/lib
|
||||
```
|
||||
|
||||
Under X11 you may get complaints about X11 threads not being initialized:
|
||||
```
|
||||
GST_GL_XINITTHREADS=1
|
||||
```
|
||||
|
||||
Under x11 you may get a frozen display from `autovideosink`, try `ximagesink` instead.
|
||||
|
||||
Putting that all together:
|
||||
```
|
||||
GST_GL_XINITTHREADS=1 \
|
||||
GST_PLUGIN_PATH=$PWD/target/gstplugins/:$PWD/support/linux/gstreamer/gst/lib \
|
||||
GST_PLUGIN_SCANNER=$PWD/support/linux/gstreamer/gst/libexec/gstreamer-1.0/gst-plugin-scanner \
|
||||
LD_LIBRARY_PATH=$PWD/support/linux/gstreamer/gst/lib \
|
||||
LD_PRELOAD=$PWD/target/gstplugins/libgstservoplugin.so \
|
||||
gst-launch-1.0 servosrc \
|
||||
! queue \
|
||||
! videoflip video-direction=vert \
|
||||
! ximagesink
|
||||
```
|
7
ports/gstplugin/build.rs
Normal file
7
ports/gstplugin/build.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
fn main() {
|
||||
gst_plugin_version_helper::get_info()
|
||||
}
|
40
ports/gstplugin/lib.rs
Normal file
40
ports/gstplugin/lib.rs
Normal file
|
@ -0,0 +1,40 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use glib::subclass::types::ObjectSubclass;
|
||||
use gstreamer::gst_plugin_define;
|
||||
use servosrc::ServoSrc;
|
||||
|
||||
mod logging;
|
||||
mod resources;
|
||||
mod servosrc;
|
||||
|
||||
gst_plugin_define!(
|
||||
servoplugin,
|
||||
env!("CARGO_PKG_DESCRIPTION"),
|
||||
plugin_init,
|
||||
concat!(env!("CARGO_PKG_VERSION"), "-", env!("COMMIT_ID")),
|
||||
"MPL",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_NAME"),
|
||||
env!("CARGO_PKG_REPOSITORY"),
|
||||
env!("BUILD_REL_DATE")
|
||||
);
|
||||
|
||||
fn plugin_init(plugin: &gstreamer::Plugin) -> Result<(), glib::BoolError> {
|
||||
gstreamer::gst_debug!(logging::CATEGORY, "Initializing logging");
|
||||
log::set_logger(&logging::LOGGER).expect("Failed to set logger");
|
||||
log::set_max_level(log::LevelFilter::Debug);
|
||||
|
||||
log::debug!("Initializing resources");
|
||||
resources::init();
|
||||
|
||||
log::debug!("Registering plugin");
|
||||
gstreamer::Element::register(
|
||||
Some(plugin),
|
||||
"servosrc",
|
||||
gstreamer::Rank::None,
|
||||
ServoSrc::get_type(),
|
||||
)
|
||||
}
|
43
ports/gstplugin/logging.rs
Normal file
43
ports/gstplugin/logging.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use gstreamer::DebugCategory;
|
||||
use gstreamer::DebugColorFlags;
|
||||
use gstreamer::DebugLevel;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref CATEGORY: DebugCategory =
|
||||
DebugCategory::new("servosrc", DebugColorFlags::empty(), Some("Servo"));
|
||||
}
|
||||
|
||||
pub static LOGGER: ServoSrcLogger = ServoSrcLogger;
|
||||
|
||||
pub struct ServoSrcLogger;
|
||||
|
||||
impl log::Log for ServoSrcLogger {
|
||||
fn enabled(&self, _metadata: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
let lvl = match record.level() {
|
||||
log::Level::Error => DebugLevel::Error,
|
||||
log::Level::Warn => DebugLevel::Warning,
|
||||
log::Level::Info => DebugLevel::Info,
|
||||
log::Level::Debug => DebugLevel::Debug,
|
||||
log::Level::Trace => DebugLevel::Trace,
|
||||
};
|
||||
CATEGORY.log::<gstreamer::Object>(
|
||||
None,
|
||||
lvl,
|
||||
record.file().unwrap_or(""),
|
||||
record.module_path().unwrap_or(""),
|
||||
record.line().unwrap_or(0),
|
||||
record.args().clone(),
|
||||
);
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
92
ports/gstplugin/resources.rs
Normal file
92
ports/gstplugin/resources.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This is a copy of the resource loader from the glutin port
|
||||
// TODO: move this to somewhere where it can be shared.
|
||||
// https://github.com/servo/servo/issues/24853
|
||||
|
||||
use lazy_static::lazy_static;
|
||||
use servo::embedder_traits::resources::{self, Resource};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Mutex;
|
||||
|
||||
lazy_static! {
|
||||
static ref CMD_RESOURCE_DIR: Mutex<Option<String>> = Mutex::new(None);
|
||||
}
|
||||
|
||||
struct ResourceReader;
|
||||
|
||||
fn filename(file: Resource) -> &'static str {
|
||||
match file {
|
||||
Resource::Preferences => "prefs.json",
|
||||
Resource::BluetoothBlocklist => "gatt_blocklist.txt",
|
||||
Resource::DomainList => "public_domains.txt",
|
||||
Resource::HstsPreloadList => "hsts_preload.json",
|
||||
Resource::SSLCertificates => "certs",
|
||||
Resource::BadCertHTML => "badcert.html",
|
||||
Resource::NetErrorHTML => "neterror.html",
|
||||
Resource::UserAgentCSS => "user-agent.css",
|
||||
Resource::ServoCSS => "servo.css",
|
||||
Resource::PresentationalHintsCSS => "presentational-hints.css",
|
||||
Resource::QuirksModeCSS => "quirks-mode.css",
|
||||
Resource::RippyPNG => "rippy.png",
|
||||
Resource::MediaControlsCSS => "media-controls.css",
|
||||
Resource::MediaControlsJS => "media-controls.js",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
resources::set(Box::new(ResourceReader));
|
||||
}
|
||||
|
||||
fn resources_dir_path() -> io::Result<PathBuf> {
|
||||
// This needs to be called before the process is sandboxed
|
||||
// as we only give permission to read inside the resources directory,
|
||||
// not the permissions the "search" for the resources directory.
|
||||
let mut dir = CMD_RESOURCE_DIR.lock().unwrap();
|
||||
if let Some(ref path) = *dir {
|
||||
return Ok(PathBuf::from(path));
|
||||
}
|
||||
|
||||
// FIXME: Find a way to not rely on the executable being
|
||||
// under `<servo source>[/$target_triple]/target/debug`
|
||||
// or `<servo source>[/$target_triple]/target/release`.
|
||||
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||
// Follow symlink
|
||||
path = path.canonicalize()?;
|
||||
|
||||
while path.pop() {
|
||||
path.push("resources");
|
||||
if path.is_dir() {
|
||||
break;
|
||||
}
|
||||
path.pop();
|
||||
// Check for Resources on mac when using a case sensitive filesystem.
|
||||
path.push("Resources");
|
||||
if path.is_dir() {
|
||||
break;
|
||||
}
|
||||
path.pop();
|
||||
}
|
||||
*dir = Some(path.to_str().unwrap().to_owned());
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
impl resources::ResourceReaderMethods for ResourceReader {
|
||||
fn read(&self, file: Resource) -> Vec<u8> {
|
||||
let file = filename(file);
|
||||
let mut path = resources_dir_path().expect("Can't find resources directory");
|
||||
path.push(file);
|
||||
fs::read(path.clone()).expect(&format!("Can't read file {:?}", path))
|
||||
}
|
||||
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
|
||||
vec![resources_dir_path().expect("Can't find resources directory")]
|
||||
}
|
||||
fn sandbox_access_files(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
}
|
618
ports/gstplugin/servosrc.rs
Normal file
618
ports/gstplugin/servosrc.rs
Normal file
|
@ -0,0 +1,618 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
use crate::logging::CATEGORY;
|
||||
|
||||
use crossbeam_channel::Receiver;
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
use euclid::Point2D;
|
||||
use euclid::Rect;
|
||||
use euclid::Scale;
|
||||
use euclid::Size2D;
|
||||
|
||||
use glib::glib_bool_error;
|
||||
use glib::glib_object_impl;
|
||||
use glib::glib_object_subclass;
|
||||
use glib::object::Cast;
|
||||
use glib::subclass::object::ObjectImpl;
|
||||
use glib::subclass::object::ObjectImplExt;
|
||||
use glib::subclass::simple::ClassStruct;
|
||||
use glib::subclass::types::ObjectSubclass;
|
||||
use gstreamer::gst_element_error;
|
||||
use gstreamer::gst_loggable_error;
|
||||
use gstreamer::subclass::element::ElementClassSubclassExt;
|
||||
use gstreamer::subclass::element::ElementImpl;
|
||||
use gstreamer::subclass::ElementInstanceStruct;
|
||||
use gstreamer::BufferRef;
|
||||
use gstreamer::Caps;
|
||||
use gstreamer::CoreError;
|
||||
use gstreamer::ErrorMessage;
|
||||
use gstreamer::FlowError;
|
||||
use gstreamer::FlowSuccess;
|
||||
use gstreamer::Format;
|
||||
use gstreamer::Fraction;
|
||||
use gstreamer::FractionRange;
|
||||
use gstreamer::IntRange;
|
||||
use gstreamer::LoggableError;
|
||||
use gstreamer::PadDirection;
|
||||
use gstreamer::PadPresence;
|
||||
use gstreamer::PadTemplate;
|
||||
use gstreamer_base::subclass::base_src::BaseSrcImpl;
|
||||
use gstreamer_base::BaseSrc;
|
||||
use gstreamer_base::BaseSrcExt;
|
||||
use gstreamer_video::VideoFormat;
|
||||
use gstreamer_video::VideoFrameRef;
|
||||
use gstreamer_video::VideoInfo;
|
||||
|
||||
use log::debug;
|
||||
use log::info;
|
||||
|
||||
use servo::compositing::windowing::AnimationState;
|
||||
use servo::compositing::windowing::EmbedderCoordinates;
|
||||
use servo::compositing::windowing::EmbedderMethods;
|
||||
use servo::compositing::windowing::WindowEvent;
|
||||
use servo::compositing::windowing::WindowMethods;
|
||||
use servo::embedder_traits::EventLoopWaker;
|
||||
use servo::msg::constellation_msg::TopLevelBrowsingContextId;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_api::units::DevicePixel;
|
||||
use servo::Servo;
|
||||
|
||||
use sparkle::gl;
|
||||
use sparkle::gl::types::GLuint;
|
||||
use sparkle::gl::Gl;
|
||||
|
||||
use surfman::platform::generic::universal::context::Context;
|
||||
use surfman::platform::generic::universal::device::Device;
|
||||
use surfman::SurfaceAccess;
|
||||
use surfman::SurfaceType;
|
||||
|
||||
use surfman_chains::SwapChain;
|
||||
use surfman_chains_api::SwapChainAPI;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryFrom;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Mutex;
|
||||
use std::thread;
|
||||
|
||||
pub struct ServoSrc {
|
||||
sender: Sender<ServoSrcMsg>,
|
||||
swap_chain: SwapChain,
|
||||
info: Mutex<Option<VideoInfo>>,
|
||||
}
|
||||
|
||||
struct ServoSrcGfx {
|
||||
device: Device,
|
||||
context: Context,
|
||||
gl: Rc<Gl>,
|
||||
fbo: GLuint,
|
||||
}
|
||||
|
||||
impl ServoSrcGfx {
|
||||
fn new() -> ServoSrcGfx {
|
||||
let version = surfman::GLVersion { major: 4, minor: 3 };
|
||||
let flags = surfman::ContextAttributeFlags::empty();
|
||||
let attributes = surfman::ContextAttributes { version, flags };
|
||||
|
||||
let connection = surfman::Connection::new().expect("Failed to create connection");
|
||||
let adapter = surfman::Adapter::default().expect("Failed to create adapter");
|
||||
let mut device =
|
||||
surfman::Device::new(&connection, &adapter).expect("Failed to create device");
|
||||
let descriptor = device
|
||||
.create_context_descriptor(&attributes)
|
||||
.expect("Failed to create descriptor");
|
||||
let context = device
|
||||
.create_context(&descriptor)
|
||||
.expect("Failed to create context");
|
||||
let gl = Gl::gl_fns(gl::ffi_gl::Gl::load_with(|s| {
|
||||
device.get_proc_address(&context, s)
|
||||
}));
|
||||
|
||||
// This is a workaround for surfman having a different bootstrap API with Angle
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut device = device;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut device = Device::Hardware(device);
|
||||
#[cfg(target_os = "windows")]
|
||||
let mut context = context;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
let mut context = Context::Hardware(context);
|
||||
|
||||
device.make_context_current(&context).unwrap();
|
||||
|
||||
let size = Size2D::new(512, 512);
|
||||
let surface_type = SurfaceType::Generic { size };
|
||||
let surface = device
|
||||
.create_surface(&mut context, SurfaceAccess::GPUCPU, &surface_type)
|
||||
.expect("Failed to create surface");
|
||||
|
||||
gl.viewport(0, 0, size.width, size.height);
|
||||
debug_assert_eq!(gl.get_error(), gl::NO_ERROR);
|
||||
|
||||
device
|
||||
.bind_surface_to_context(&mut context, surface)
|
||||
.expect("Failed to bind surface");
|
||||
let fbo = device
|
||||
.context_surface_info(&context)
|
||||
.expect("Failed to get context info")
|
||||
.expect("Failed to get context info")
|
||||
.framebuffer_object;
|
||||
gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
|
||||
debug_assert_eq!(
|
||||
(gl.check_framebuffer_status(gl::FRAMEBUFFER), gl.get_error()),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
let fbo = gl.gen_framebuffers(1)[0];
|
||||
debug_assert_eq!(
|
||||
(gl.check_framebuffer_status(gl::FRAMEBUFFER), gl.get_error()),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
device.make_no_context_current().unwrap();
|
||||
|
||||
Self {
|
||||
device,
|
||||
context,
|
||||
gl,
|
||||
fbo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ServoSrcGfx {
|
||||
fn drop(&mut self) {
|
||||
let _ = self.device.destroy_context(&mut self.context);
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
static GFX: RefCell<ServoSrcGfx> = RefCell::new(ServoSrcGfx::new());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ServoSrcMsg {
|
||||
GetSwapChain(Sender<SwapChain>),
|
||||
Resize(Size2D<i32, DevicePixel>),
|
||||
Heartbeat,
|
||||
Quit,
|
||||
}
|
||||
|
||||
const DEFAULT_URL: &'static str =
|
||||
"https://rawcdn.githack.com/mrdoob/three.js/r105/examples/webgl_animation_cloth.html";
|
||||
|
||||
struct ServoThread {
|
||||
receiver: Receiver<ServoSrcMsg>,
|
||||
swap_chain: SwapChain,
|
||||
servo: Servo<ServoSrcWindow>,
|
||||
}
|
||||
|
||||
impl ServoThread {
|
||||
fn new(receiver: Receiver<ServoSrcMsg>) -> Self {
|
||||
let embedder = Box::new(ServoSrcEmbedder);
|
||||
let window = Rc::new(ServoSrcWindow::new());
|
||||
let swap_chain = window.swap_chain.clone();
|
||||
let servo = Servo::new(embedder, window);
|
||||
Self {
|
||||
receiver,
|
||||
swap_chain,
|
||||
servo,
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.new_browser();
|
||||
while let Ok(msg) = self.receiver.recv() {
|
||||
debug!("Servo thread handling message {:?}", msg);
|
||||
match msg {
|
||||
ServoSrcMsg::GetSwapChain(sender) => sender
|
||||
.send(self.swap_chain.clone())
|
||||
.expect("Failed to send swap chain"),
|
||||
ServoSrcMsg::Resize(size) => self.resize(size),
|
||||
ServoSrcMsg::Heartbeat => self.servo.handle_events(vec![]),
|
||||
ServoSrcMsg::Quit => break,
|
||||
}
|
||||
}
|
||||
self.servo.handle_events(vec![WindowEvent::Quit]);
|
||||
}
|
||||
|
||||
fn new_browser(&mut self) {
|
||||
let id = TopLevelBrowsingContextId::new();
|
||||
let url = ServoUrl::parse(DEFAULT_URL).unwrap();
|
||||
self.servo
|
||||
.handle_events(vec![WindowEvent::NewBrowser(url, id)]);
|
||||
}
|
||||
|
||||
fn resize(&mut self, size: Size2D<i32, DevicePixel>) {
|
||||
GFX.with(|gfx| {
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
self.swap_chain
|
||||
.resize(&mut gfx.device, &mut gfx.context, size.to_untyped())
|
||||
.expect("Failed to resize");
|
||||
gfx.gl.viewport(0, 0, size.width, size.height);
|
||||
let fbo = gfx
|
||||
.device
|
||||
.context_surface_info(&gfx.context)
|
||||
.expect("Failed to get context info")
|
||||
.expect("Failed to get context info")
|
||||
.framebuffer_object;
|
||||
gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
});
|
||||
self.servo.handle_events(vec![WindowEvent::Resize]);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ServoThread {
|
||||
fn drop(&mut self) {
|
||||
GFX.with(|gfx| {
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
self.swap_chain
|
||||
.destroy(&mut gfx.device, &mut gfx.context)
|
||||
.expect("Failed to destroy swap chain")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct ServoSrcEmbedder;
|
||||
|
||||
impl EmbedderMethods for ServoSrcEmbedder {
|
||||
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(ServoSrcEmbedder)
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker for ServoSrcEmbedder {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(ServoSrcEmbedder)
|
||||
}
|
||||
|
||||
fn wake(&self) {}
|
||||
}
|
||||
|
||||
struct ServoSrcWindow {
|
||||
swap_chain: SwapChain,
|
||||
gl: Rc<dyn gleam::gl::Gl>,
|
||||
}
|
||||
|
||||
impl ServoSrcWindow {
|
||||
fn new() -> Self {
|
||||
GFX.with(|gfx| {
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
let access = SurfaceAccess::GPUCPU;
|
||||
gfx.device
|
||||
.make_context_current(&mut gfx.context)
|
||||
.expect("Failed to make context current");
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
let swap_chain = SwapChain::create_attached(&mut gfx.device, &mut gfx.context, access)
|
||||
.expect("Failed to create swap chain");
|
||||
let fbo = gfx
|
||||
.device
|
||||
.context_surface_info(&gfx.context)
|
||||
.expect("Failed to get context info")
|
||||
.expect("Failed to get context info")
|
||||
.framebuffer_object;
|
||||
gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
let gl = unsafe {
|
||||
gleam::gl::GlFns::load_with(|s| gfx.device.get_proc_address(&gfx.context, s))
|
||||
};
|
||||
Self { swap_chain, gl }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for ServoSrcWindow {
|
||||
fn present(&self) {
|
||||
GFX.with(|gfx| {
|
||||
debug!("EMBEDDER present");
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
gfx.device
|
||||
.make_context_current(&mut gfx.context)
|
||||
.expect("Failed to make context current");
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
let _ = self
|
||||
.swap_chain
|
||||
.swap_buffers(&mut gfx.device, &mut gfx.context);
|
||||
let fbo = gfx
|
||||
.device
|
||||
.context_surface_info(&gfx.context)
|
||||
.expect("Failed to get context info")
|
||||
.expect("Failed to get context info")
|
||||
.framebuffer_object;
|
||||
gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, fbo);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
let _ = gfx.device.make_no_context_current();
|
||||
})
|
||||
}
|
||||
|
||||
fn make_gl_context_current(&self) {
|
||||
GFX.with(|gfx| {
|
||||
debug!("EMBEDDER make_context_current");
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
gfx.device
|
||||
.make_context_current(&mut gfx.context)
|
||||
.expect("Failed to make context current");
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn gl(&self) -> Rc<dyn gleam::gl::Gl> {
|
||||
self.gl.clone()
|
||||
}
|
||||
|
||||
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||
let size = Size2D::from_untyped(self.swap_chain.size());
|
||||
info!("EMBEDDER coordinates {}", size);
|
||||
let origin = Point2D::origin();
|
||||
EmbedderCoordinates {
|
||||
hidpi_factor: Scale::new(1.0),
|
||||
screen: size,
|
||||
screen_avail: size,
|
||||
window: (size, origin),
|
||||
framebuffer: size,
|
||||
viewport: Rect::new(origin, size),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_animation_state(&self, _: AnimationState) {}
|
||||
|
||||
fn get_gl_context(&self) -> servo_media::player::context::GlContext {
|
||||
servo_media::player::context::GlContext::Unknown
|
||||
}
|
||||
|
||||
fn get_native_display(&self) -> servo_media::player::context::NativeDisplay {
|
||||
servo_media::player::context::NativeDisplay::Unknown
|
||||
}
|
||||
|
||||
fn get_gl_api(&self) -> servo_media::player::context::GlApi {
|
||||
servo_media::player::context::GlApi::OpenGL3
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectSubclass for ServoSrc {
|
||||
const NAME: &'static str = "ServoSrc";
|
||||
// gstreamer-gl doesn't have support for GLBaseSrc yet
|
||||
// https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/issues/219
|
||||
type ParentType = BaseSrc;
|
||||
type Instance = ElementInstanceStruct<Self>;
|
||||
type Class = ClassStruct<Self>;
|
||||
|
||||
fn new() -> Self {
|
||||
let (sender, receiver) = crossbeam_channel::bounded(1);
|
||||
thread::spawn(move || ServoThread::new(receiver).run());
|
||||
let (acks, ackr) = crossbeam_channel::bounded(1);
|
||||
let _ = sender.send(ServoSrcMsg::GetSwapChain(acks));
|
||||
let swap_chain = ackr.recv().expect("Failed to get swap chain");
|
||||
let info = Mutex::new(None);
|
||||
Self {
|
||||
sender,
|
||||
swap_chain,
|
||||
info,
|
||||
}
|
||||
}
|
||||
|
||||
fn class_init(klass: &mut ClassStruct<Self>) {
|
||||
klass.set_metadata(
|
||||
"Servo as a gstreamer src",
|
||||
"Filter/Effect/Converter/Video",
|
||||
"The Servo web browser",
|
||||
env!("CARGO_PKG_AUTHORS"),
|
||||
);
|
||||
|
||||
let src_caps = Caps::new_simple(
|
||||
"video/x-raw",
|
||||
&[
|
||||
("format", &VideoFormat::Bgrx.to_string()),
|
||||
("width", &IntRange::<i32>::new(1, std::i32::MAX)),
|
||||
("height", &IntRange::<i32>::new(1, std::i32::MAX)),
|
||||
(
|
||||
"framerate",
|
||||
&FractionRange::new(
|
||||
Fraction::new(1, std::i32::MAX),
|
||||
Fraction::new(std::i32::MAX, 1),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
let src_pad_template =
|
||||
PadTemplate::new("src", PadDirection::Src, PadPresence::Always, &src_caps).unwrap();
|
||||
klass.add_pad_template(src_pad_template);
|
||||
}
|
||||
|
||||
glib_object_subclass!();
|
||||
}
|
||||
|
||||
impl ObjectImpl for ServoSrc {
|
||||
glib_object_impl!();
|
||||
|
||||
fn constructed(&self, obj: &glib::Object) {
|
||||
self.parent_constructed(obj);
|
||||
let basesrc = obj.downcast_ref::<BaseSrc>().unwrap();
|
||||
basesrc.set_live(true);
|
||||
basesrc.set_format(Format::Time);
|
||||
basesrc.set_do_timestamp(true);
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementImpl for ServoSrc {}
|
||||
|
||||
impl BaseSrcImpl for ServoSrc {
|
||||
fn set_caps(&self, _src: &BaseSrc, outcaps: &Caps) -> Result<(), LoggableError> {
|
||||
let info = VideoInfo::from_caps(outcaps)
|
||||
.ok_or_else(|| gst_loggable_error!(CATEGORY, "Failed to get video info"))?;
|
||||
let size = Size2D::new(info.width(), info.height()).to_i32();
|
||||
debug!("Setting servosrc buffer size to {}", size,);
|
||||
self.sender
|
||||
.send(ServoSrcMsg::Resize(size))
|
||||
.map_err(|_| gst_loggable_error!(CATEGORY, "Failed to send video info"))?;
|
||||
*self.info.lock().unwrap() = Some(info);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_size(&self, _src: &BaseSrc) -> Option<u64> {
|
||||
u64::try_from(self.info.lock().ok()?.as_ref()?.size()).ok()
|
||||
}
|
||||
|
||||
fn start(&self, _src: &BaseSrc) -> Result<(), ErrorMessage> {
|
||||
info!("Starting");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&self, _src: &BaseSrc) -> Result<(), ErrorMessage> {
|
||||
info!("Stopping");
|
||||
let _ = self.sender.send(ServoSrcMsg::Quit);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn fill(
|
||||
&self,
|
||||
src: &BaseSrc,
|
||||
_offset: u64,
|
||||
_length: u32,
|
||||
buffer: &mut BufferRef,
|
||||
) -> Result<FlowSuccess, FlowError> {
|
||||
let guard = self.info.lock().map_err(|_| {
|
||||
gst_element_error!(src, CoreError::Negotiation, ["Lock poisoned"]);
|
||||
FlowError::NotNegotiated
|
||||
})?;
|
||||
let info = guard.as_ref().ok_or_else(|| {
|
||||
gst_element_error!(src, CoreError::Negotiation, ["Caps not set yet"]);
|
||||
FlowError::NotNegotiated
|
||||
})?;
|
||||
let mut frame = VideoFrameRef::from_buffer_ref_writable(buffer, info).ok_or_else(|| {
|
||||
gst_element_error!(
|
||||
src,
|
||||
CoreError::Failed,
|
||||
["Failed to map output buffer writable"]
|
||||
);
|
||||
FlowError::Error
|
||||
})?;
|
||||
let height = frame.height() as i32;
|
||||
let width = frame.width() as i32;
|
||||
let format = frame.format();
|
||||
debug!(
|
||||
"Filling servosrc buffer {}x{} {:?} {:?}",
|
||||
width, height, format, frame,
|
||||
);
|
||||
let data = frame.plane_data_mut(0).unwrap();
|
||||
|
||||
GFX.with(|gfx| {
|
||||
let mut gfx = gfx.borrow_mut();
|
||||
let gfx = &mut *gfx;
|
||||
if let Some(surface) = self.swap_chain.take_surface() {
|
||||
gfx.device.make_context_current(&gfx.context).unwrap();
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
gfx.gl.viewport(0, 0, width, height);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
let surface_texture = gfx
|
||||
.device
|
||||
.create_surface_texture(&mut gfx.context, surface)
|
||||
.unwrap();
|
||||
let texture_id = surface_texture.gl_texture();
|
||||
|
||||
gfx.gl.bind_framebuffer(gl::FRAMEBUFFER, gfx.fbo);
|
||||
gfx.gl.framebuffer_texture_2d(
|
||||
gl::FRAMEBUFFER,
|
||||
gl::COLOR_ATTACHMENT0,
|
||||
gfx.device.surface_gl_texture_target(),
|
||||
texture_id,
|
||||
0,
|
||||
);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
// TODO: use GL memory to avoid readback
|
||||
gfx.gl.read_pixels_into_buffer(
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
gl::BGRA,
|
||||
gl::UNSIGNED_BYTE,
|
||||
data,
|
||||
);
|
||||
debug_assert_eq!(
|
||||
(
|
||||
gfx.gl.check_framebuffer_status(gl::FRAMEBUFFER),
|
||||
gfx.gl.get_error()
|
||||
),
|
||||
(gl::FRAMEBUFFER_COMPLETE, gl::NO_ERROR)
|
||||
);
|
||||
|
||||
gfx.device.make_no_context_current().unwrap();
|
||||
|
||||
let surface = gfx
|
||||
.device
|
||||
.destroy_surface_texture(&mut gfx.context, surface_texture)
|
||||
.unwrap();
|
||||
self.swap_chain.recycle_surface(surface);
|
||||
}
|
||||
});
|
||||
let _ = self.sender.send(ServoSrcMsg::Heartbeat);
|
||||
Ok(FlowSuccess::Ok)
|
||||
}
|
||||
}
|
16
ports/gstplugin/test.html
Normal file
16
ports/gstplugin/test.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<video controls>
|
||||
<source src="http://localhost:8080/" type="video/ogg">
|
||||
</video>
|
||||
|
||||
<p>Start the video stream with:</p>
|
||||
|
||||
<pre>
|
||||
gst-launch-1.0 servosrc \
|
||||
! queue \
|
||||
! video/x-raw,framerate=25/1,width=512,height=512 \
|
||||
! videoconvert \
|
||||
! videoflip video-direction=vert \
|
||||
! theoraenc \
|
||||
! oggmux \
|
||||
! tcpserversink host=127.0.0.1 port=8080
|
||||
</pre>
|
Loading…
Add table
Add a link
Reference in a new issue