Add a gstreamer servosrc plugin

This commit is contained in:
Alan Jeffrey 2019-11-11 15:57:46 -06:00
parent a562808ebb
commit 69acec137d
11 changed files with 1079 additions and 6 deletions

96
Cargo.lock generated
View file

@ -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",

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"ports/glutin",
"ports/gstplugin",
"ports/libsimpleservo/capi/",
"ports/libsimpleservo/jniapi/",
"ports/libmlservo/",

View file

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

View 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
View 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
View 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
View 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(),
)
}

View 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) {}
}

View 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
View 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
View 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>