mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2024-11-10 20:31:10 +00:00
gtk4: Add support for rotations / flipping
Fixes https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/issues/284 Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs/-/merge_requests/1590>
This commit is contained in:
parent
2fe852166e
commit
8522c8a445
6 changed files with 359 additions and 31 deletions
|
@ -2622,6 +2622,18 @@
|
||||||
"type": "GdkGLContext",
|
"type": "GdkGLContext",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"orientation": {
|
||||||
|
"blurb": "Orientation of the video frames",
|
||||||
|
"conditionally-available": false,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "auto (0)",
|
||||||
|
"mutable": "null",
|
||||||
|
"readable": true,
|
||||||
|
"type": "GstGtk4PaintableSinkOrientation",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"scaling-filter": {
|
"scaling-filter": {
|
||||||
"blurb": "Scaling filter to use for rendering",
|
"blurb": "Scaling filter to use for rendering",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
@ -2647,6 +2659,56 @@
|
||||||
"writable": true
|
"writable": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"GstGtk4PaintableSinkOrientation": {
|
||||||
|
"kind": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"desc": "Auto",
|
||||||
|
"name": "auto",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Rotate0",
|
||||||
|
"name": "rotate0",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Rotate90",
|
||||||
|
"name": "rotate90",
|
||||||
|
"value": "2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Rotate180",
|
||||||
|
"name": "rotate180",
|
||||||
|
"value": "3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Rotate270",
|
||||||
|
"name": "rotate270",
|
||||||
|
"value": "4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "FlipRotate0",
|
||||||
|
"name": "flip-rotate0",
|
||||||
|
"value": "5"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "FlipRotate90",
|
||||||
|
"name": "flip-rotate90",
|
||||||
|
"value": "6"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "FlipRotate180",
|
||||||
|
"name": "flip-rotate180",
|
||||||
|
"value": "7"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "FlipRotate270",
|
||||||
|
"name": "flip-rotate270",
|
||||||
|
"value": "8"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"package": "gst-plugin-gtk4",
|
"package": "gst-plugin-gtk4",
|
||||||
|
|
|
@ -19,6 +19,7 @@ use gst::glib;
|
||||||
|
|
||||||
mod sink;
|
mod sink;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
pub use sink::frame::Orientation;
|
||||||
pub use sink::paintable::Paintable;
|
pub use sink::paintable::Paintable;
|
||||||
pub use sink::PaintableSink;
|
pub use sink::PaintableSink;
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ fn plugin_init(plugin: &gst::Plugin) -> Result<(), glib::BoolError> {
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
|
|
||||||
sink::paintable::Paintable::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
sink::paintable::Paintable::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
|
sink::frame::Orientation::static_type().mark_as_plugin_api(gst::PluginAPIFlags::empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "gtk_v4_10"))]
|
#[cfg(not(feature = "gtk_v4_10"))]
|
||||||
|
|
|
@ -72,11 +72,15 @@ pub enum TextureCacheId {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum MappedFrame {
|
enum MappedFrame {
|
||||||
SysMem(gst_video::VideoFrame<gst_video::video_frame::Readable>),
|
SysMem {
|
||||||
|
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
|
||||||
|
orientation: Orientation,
|
||||||
|
},
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
GL {
|
GL {
|
||||||
frame: gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>,
|
frame: gst_gl::GLVideoFrame<gst_gl::gl_video_frame::Readable>,
|
||||||
wrapped_context: gst_gl::GLContext,
|
wrapped_context: gst_gl::GLContext,
|
||||||
|
orientation: Orientation,
|
||||||
},
|
},
|
||||||
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
DmaBuf {
|
DmaBuf {
|
||||||
|
@ -88,13 +92,14 @@ enum MappedFrame {
|
||||||
strides: [usize; 4],
|
strides: [usize; 4],
|
||||||
width: u32,
|
width: u32,
|
||||||
height: u32,
|
height: u32,
|
||||||
|
orientation: Orientation,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MappedFrame {
|
impl MappedFrame {
|
||||||
fn buffer(&self) -> &gst::BufferRef {
|
fn buffer(&self) -> &gst::BufferRef {
|
||||||
match self {
|
match self {
|
||||||
MappedFrame::SysMem(frame) => frame.buffer(),
|
MappedFrame::SysMem { frame, .. } => frame.buffer(),
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
MappedFrame::GL { frame, .. } => frame.buffer(),
|
MappedFrame::GL { frame, .. } => frame.buffer(),
|
||||||
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
|
@ -104,7 +109,7 @@ impl MappedFrame {
|
||||||
|
|
||||||
fn width(&self) -> u32 {
|
fn width(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
MappedFrame::SysMem(frame) => frame.width(),
|
MappedFrame::SysMem { frame, .. } => frame.width(),
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
MappedFrame::GL { frame, .. } => frame.width(),
|
MappedFrame::GL { frame, .. } => frame.width(),
|
||||||
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
|
@ -114,7 +119,7 @@ impl MappedFrame {
|
||||||
|
|
||||||
fn height(&self) -> u32 {
|
fn height(&self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
MappedFrame::SysMem(frame) => frame.height(),
|
MappedFrame::SysMem { frame, .. } => frame.height(),
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
MappedFrame::GL { frame, .. } => frame.height(),
|
MappedFrame::GL { frame, .. } => frame.height(),
|
||||||
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
|
@ -124,13 +129,23 @@ impl MappedFrame {
|
||||||
|
|
||||||
fn format_info(&self) -> gst_video::VideoFormatInfo {
|
fn format_info(&self) -> gst_video::VideoFormatInfo {
|
||||||
match self {
|
match self {
|
||||||
MappedFrame::SysMem(frame) => frame.format_info(),
|
MappedFrame::SysMem { frame, .. } => frame.format_info(),
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
MappedFrame::GL { frame, .. } => frame.format_info(),
|
MappedFrame::GL { frame, .. } => frame.format_info(),
|
||||||
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
MappedFrame::DmaBuf { info, .. } => info.format_info(),
|
MappedFrame::DmaBuf { info, .. } => info.format_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn orientation(&self) -> Orientation {
|
||||||
|
match self {
|
||||||
|
MappedFrame::SysMem { orientation, .. } => *orientation,
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
|
MappedFrame::GL { orientation, .. } => *orientation,
|
||||||
|
#[cfg(all(target_os = "linux", feature = "dmabuf"))]
|
||||||
|
MappedFrame::DmaBuf { orientation, .. } => *orientation,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -139,6 +154,52 @@ pub(crate) struct Frame {
|
||||||
overlays: Vec<Overlay>,
|
overlays: Vec<Overlay>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, glib::Enum, PartialEq, Eq, Copy, Clone)]
|
||||||
|
#[repr(C)]
|
||||||
|
#[enum_type(name = "GstGtk4PaintableSinkOrientation")]
|
||||||
|
pub enum Orientation {
|
||||||
|
#[default]
|
||||||
|
Auto,
|
||||||
|
Rotate0,
|
||||||
|
Rotate90,
|
||||||
|
Rotate180,
|
||||||
|
Rotate270,
|
||||||
|
FlipRotate0,
|
||||||
|
FlipRotate90,
|
||||||
|
FlipRotate180,
|
||||||
|
FlipRotate270,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Orientation {
|
||||||
|
pub fn from_tags(tags: &gst::TagListRef) -> Option<Orientation> {
|
||||||
|
let orientation = tags
|
||||||
|
.generic("image-orientation")
|
||||||
|
.and_then(|v| v.get::<String>().ok())?;
|
||||||
|
|
||||||
|
Some(match orientation.as_str() {
|
||||||
|
"rotate-0" => Orientation::Rotate0,
|
||||||
|
"rotate-90" => Orientation::Rotate90,
|
||||||
|
"rotate-180" => Orientation::Rotate180,
|
||||||
|
"rotate-270" => Orientation::Rotate270,
|
||||||
|
"flip-rotate-0" => Orientation::FlipRotate0,
|
||||||
|
"flip-rotate-90" => Orientation::FlipRotate90,
|
||||||
|
"flip-rotate-180" => Orientation::FlipRotate180,
|
||||||
|
"flip-rotate-270" => Orientation::FlipRotate270,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_flip_width_height(self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Orientation::Rotate90
|
||||||
|
| Orientation::Rotate270
|
||||||
|
| Orientation::FlipRotate90
|
||||||
|
| Orientation::FlipRotate270
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Overlay {
|
struct Overlay {
|
||||||
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
|
frame: gst_video::VideoFrame<gst_video::video_frame::Readable>,
|
||||||
|
@ -158,6 +219,7 @@ pub(crate) struct Texture {
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
pub global_alpha: f32,
|
pub global_alpha: f32,
|
||||||
pub has_alpha: bool,
|
pub has_alpha: bool,
|
||||||
|
pub orientation: Orientation,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct FrameWrapper(gst_video::VideoFrame<gst_video::video_frame::Readable>);
|
struct FrameWrapper(gst_video::VideoFrame<gst_video::video_frame::Readable>);
|
||||||
|
@ -374,14 +436,16 @@ impl Frame {
|
||||||
let width = self.frame.width();
|
let width = self.frame.width();
|
||||||
let height = self.frame.height();
|
let height = self.frame.height();
|
||||||
let has_alpha = self.frame.format_info().has_alpha();
|
let has_alpha = self.frame.format_info().has_alpha();
|
||||||
|
let orientation = self.frame.orientation();
|
||||||
let (texture, pixel_aspect_ratio) = match self.frame {
|
let (texture, pixel_aspect_ratio) = match self.frame {
|
||||||
MappedFrame::SysMem(frame) => {
|
MappedFrame::SysMem { frame, .. } => {
|
||||||
video_frame_to_memory_texture(frame, cached_textures, &mut used_textures)
|
video_frame_to_memory_texture(frame, cached_textures, &mut used_textures)
|
||||||
}
|
}
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))]
|
||||||
MappedFrame::GL {
|
MappedFrame::GL {
|
||||||
frame,
|
frame,
|
||||||
wrapped_context,
|
wrapped_context,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
let Some(gdk_context) = gdk_context else {
|
let Some(gdk_context) = gdk_context else {
|
||||||
// This will fail badly if the video frame was actually mapped as GL texture
|
// This will fail badly if the video frame was actually mapped as GL texture
|
||||||
|
@ -407,6 +471,7 @@ impl Frame {
|
||||||
strides,
|
strides,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
..
|
||||||
} => video_frame_to_dmabuf_texture(
|
} => video_frame_to_dmabuf_texture(
|
||||||
buffer,
|
buffer,
|
||||||
cached_textures,
|
cached_textures,
|
||||||
|
@ -429,6 +494,7 @@ impl Frame {
|
||||||
height: height as f32,
|
height: height as f32,
|
||||||
global_alpha: 1.0,
|
global_alpha: 1.0,
|
||||||
has_alpha,
|
has_alpha,
|
||||||
|
orientation,
|
||||||
});
|
});
|
||||||
|
|
||||||
for overlay in self.overlays {
|
for overlay in self.overlays {
|
||||||
|
@ -444,6 +510,7 @@ impl Frame {
|
||||||
height: overlay.height as f32,
|
height: overlay.height as f32,
|
||||||
global_alpha: overlay.global_alpha,
|
global_alpha: overlay.global_alpha,
|
||||||
has_alpha,
|
has_alpha,
|
||||||
|
orientation: Orientation::Rotate0,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +525,7 @@ impl Frame {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
buffer: &gst::Buffer,
|
buffer: &gst::Buffer,
|
||||||
info: &VideoInfo,
|
info: &VideoInfo,
|
||||||
|
orientation: Orientation,
|
||||||
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option<
|
#[cfg(any(target_os = "macos", target_os = "windows", feature = "gst-gl"))] wrapped_context: Option<
|
||||||
&gst_gl::GLContext,
|
&gst_gl::GLContext,
|
||||||
>,
|
>,
|
||||||
|
@ -521,6 +589,7 @@ impl Frame {
|
||||||
strides,
|
strides,
|
||||||
width: vmeta.width(),
|
width: vmeta.width(),
|
||||||
height: vmeta.height(),
|
height: vmeta.height(),
|
||||||
|
orientation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -571,6 +640,7 @@ impl Frame {
|
||||||
frame = Some(MappedFrame::GL {
|
frame = Some(MappedFrame::GL {
|
||||||
frame: mapped_frame,
|
frame: mapped_frame,
|
||||||
wrapped_context: wrapped_context.unwrap().clone(),
|
wrapped_context: wrapped_context.unwrap().clone(),
|
||||||
|
orientation,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -580,10 +650,11 @@ impl Frame {
|
||||||
let mut frame = Self {
|
let mut frame = Self {
|
||||||
frame: match frame {
|
frame: match frame {
|
||||||
Some(frame) => frame,
|
Some(frame) => frame,
|
||||||
None => MappedFrame::SysMem(
|
None => MappedFrame::SysMem {
|
||||||
gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info)
|
frame: gst_video::VideoFrame::from_buffer_readable(buffer.clone(), info)
|
||||||
.map_err(|_| gst::FlowError::Error)?,
|
.map_err(|_| gst::FlowError::Error)?,
|
||||||
),
|
orientation,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
overlays: vec![],
|
overlays: vec![],
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
use super::SinkEvent;
|
use super::{frame, SinkEvent};
|
||||||
use crate::sink::frame::Frame;
|
use crate::sink::frame::Frame;
|
||||||
use crate::sink::paintable::Paintable;
|
use crate::sink::paintable::Paintable;
|
||||||
|
|
||||||
|
@ -58,11 +58,29 @@ pub(crate) static CAT: Lazy<gst::DebugCategory> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
struct StreamConfig {
|
||||||
|
info: Option<super::frame::VideoInfo>,
|
||||||
|
/// Orientation from a global scope tag
|
||||||
|
global_orientation: frame::Orientation,
|
||||||
|
/// Orientation from a stream scope tag
|
||||||
|
stream_orientation: Option<frame::Orientation>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StreamConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
StreamConfig {
|
||||||
|
info: None,
|
||||||
|
global_orientation: frame::Orientation::Rotate0,
|
||||||
|
stream_orientation: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PaintableSink {
|
pub struct PaintableSink {
|
||||||
paintable: Mutex<Option<ThreadGuard<Paintable>>>,
|
paintable: Mutex<Option<ThreadGuard<Paintable>>>,
|
||||||
window: Mutex<Option<ThreadGuard<gtk::Window>>>,
|
window: Mutex<Option<ThreadGuard<gtk::Window>>>,
|
||||||
info: Mutex<Option<super::frame::VideoInfo>>,
|
config: Mutex<StreamConfig>,
|
||||||
sender: Mutex<Option<async_channel::Sender<SinkEvent>>>,
|
sender: Mutex<Option<async_channel::Sender<SinkEvent>>>,
|
||||||
pending_frame: Mutex<Option<Frame>>,
|
pending_frame: Mutex<Option<Frame>>,
|
||||||
cached_caps: Mutex<Option<gst::Caps>>,
|
cached_caps: Mutex<Option<gst::Caps>>,
|
||||||
|
@ -376,7 +394,7 @@ impl ElementImpl for PaintableSink {
|
||||||
|
|
||||||
match transition {
|
match transition {
|
||||||
gst::StateChange::PausedToReady => {
|
gst::StateChange::PausedToReady => {
|
||||||
let _ = self.info.lock().unwrap().take();
|
*self.config.lock().unwrap() = StreamConfig::default();
|
||||||
let _ = self.pending_frame.lock().unwrap().take();
|
let _ = self.pending_frame.lock().unwrap().take();
|
||||||
|
|
||||||
// Flush frames from the GDK paintable but don't wait
|
// Flush frames from the GDK paintable but don't wait
|
||||||
|
@ -455,7 +473,7 @@ impl BaseSinkImpl for PaintableSink {
|
||||||
.into(),
|
.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.info.lock().unwrap().replace(video_info);
|
self.config.lock().unwrap().info = Some(video_info);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -526,6 +544,31 @@ impl BaseSinkImpl for PaintableSink {
|
||||||
_ => BaseSinkImplExt::parent_query(self, query),
|
_ => BaseSinkImplExt::parent_query(self, query),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn event(&self, event: gst::Event) -> bool {
|
||||||
|
match event.view() {
|
||||||
|
gst::EventView::StreamStart(_) => {
|
||||||
|
let mut config = self.config.lock().unwrap();
|
||||||
|
config.global_orientation = frame::Orientation::Rotate0;
|
||||||
|
config.stream_orientation = None;
|
||||||
|
}
|
||||||
|
gst::EventView::Tag(ev) => {
|
||||||
|
let mut config = self.config.lock().unwrap();
|
||||||
|
let tags = ev.tag();
|
||||||
|
let scope = tags.scope();
|
||||||
|
let orientation = frame::Orientation::from_tags(tags);
|
||||||
|
|
||||||
|
if scope == gst::TagScope::Global {
|
||||||
|
config.global_orientation = orientation.unwrap_or(frame::Orientation::Rotate0);
|
||||||
|
} else {
|
||||||
|
config.stream_orientation = orientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parent_event(event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoSinkImpl for PaintableSink {
|
impl VideoSinkImpl for PaintableSink {
|
||||||
|
@ -542,11 +585,14 @@ impl VideoSinkImpl for PaintableSink {
|
||||||
return Ok(gst::FlowSuccess::Ok);
|
return Ok(gst::FlowSuccess::Ok);
|
||||||
};
|
};
|
||||||
|
|
||||||
let info = self.info.lock().unwrap();
|
let config = self.config.lock().unwrap();
|
||||||
let info = info.as_ref().ok_or_else(|| {
|
let info = config.info.as_ref().ok_or_else(|| {
|
||||||
gst::error!(CAT, imp: self, "Received no caps yet");
|
gst::error!(CAT, imp: self, "Received no caps yet");
|
||||||
gst::FlowError::NotNegotiated
|
gst::FlowError::NotNegotiated
|
||||||
})?;
|
})?;
|
||||||
|
let orientation = config
|
||||||
|
.stream_orientation
|
||||||
|
.unwrap_or(config.global_orientation);
|
||||||
|
|
||||||
let wrapped_context = {
|
let wrapped_context = {
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))]
|
#[cfg(not(any(target_os = "macos", target_os = "windows", feature = "gst-gl")))]
|
||||||
|
@ -566,7 +612,8 @@ impl VideoSinkImpl for PaintableSink {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let frame = Frame::new(buffer, info, wrapped_context.as_ref()).map_err(|err| {
|
let frame =
|
||||||
|
Frame::new(buffer, info, orientation, wrapped_context.as_ref()).map_err(|err| {
|
||||||
gst::error!(CAT, imp: self, "Failed to map video frame");
|
gst::error!(CAT, imp: self, "Failed to map video frame");
|
||||||
err
|
err
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
use gtk::glib::prelude::*;
|
use gtk::glib::prelude::*;
|
||||||
|
|
||||||
mod frame;
|
pub(super) mod frame;
|
||||||
pub(super) mod imp;
|
pub(super) mod imp;
|
||||||
pub(super) mod paintable;
|
pub(super) mod paintable;
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ use gtk::prelude::*;
|
||||||
use gtk::subclass::prelude::*;
|
use gtk::subclass::prelude::*;
|
||||||
use gtk::{gdk, glib, graphene, gsk};
|
use gtk::{gdk, glib, graphene, gsk};
|
||||||
|
|
||||||
use crate::sink::frame::{Frame, Texture};
|
use crate::sink::frame::{self, Frame, Texture};
|
||||||
|
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -38,6 +38,7 @@ pub struct Paintable {
|
||||||
scaling_filter: Cell<gsk::ScalingFilter>,
|
scaling_filter: Cell<gsk::ScalingFilter>,
|
||||||
use_scaling_filter: Cell<bool>,
|
use_scaling_filter: Cell<bool>,
|
||||||
force_aspect_ratio: Cell<bool>,
|
force_aspect_ratio: Cell<bool>,
|
||||||
|
orientation: Cell<frame::Orientation>,
|
||||||
#[cfg(not(feature = "gtk_v4_10"))]
|
#[cfg(not(feature = "gtk_v4_10"))]
|
||||||
premult_shader: gsk::GLShader,
|
premult_shader: gsk::GLShader,
|
||||||
}
|
}
|
||||||
|
@ -53,6 +54,7 @@ impl Default for Paintable {
|
||||||
scaling_filter: Cell::new(gsk::ScalingFilter::Linear),
|
scaling_filter: Cell::new(gsk::ScalingFilter::Linear),
|
||||||
use_scaling_filter: Cell::new(false),
|
use_scaling_filter: Cell::new(false),
|
||||||
force_aspect_ratio: Cell::new(false),
|
force_aspect_ratio: Cell::new(false),
|
||||||
|
orientation: Cell::new(frame::Orientation::Auto),
|
||||||
#[cfg(not(feature = "gtk_v4_10"))]
|
#[cfg(not(feature = "gtk_v4_10"))]
|
||||||
premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!(
|
premult_shader: gsk::GLShader::from_bytes(&glib::Bytes::from_static(include_bytes!(
|
||||||
"premult.glsl"
|
"premult.glsl"
|
||||||
|
@ -101,6 +103,10 @@ impl ObjectImpl for Paintable {
|
||||||
.blurb("When enabled, scaling will respect original aspect ratio")
|
.blurb("When enabled, scaling will respect original aspect ratio")
|
||||||
.default_value(false)
|
.default_value(false)
|
||||||
.build(),
|
.build(),
|
||||||
|
glib::ParamSpecEnum::builder::<frame::Orientation>("orientation")
|
||||||
|
.nick("Orientation")
|
||||||
|
.blurb("Orientation of the video frames")
|
||||||
|
.build(),
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -125,6 +131,7 @@ impl ObjectImpl for Paintable {
|
||||||
#[cfg(feature = "gtk_v4_10")]
|
#[cfg(feature = "gtk_v4_10")]
|
||||||
"use-scaling-filter" => self.use_scaling_filter.get().to_value(),
|
"use-scaling-filter" => self.use_scaling_filter.get().to_value(),
|
||||||
"force-aspect-ratio" => self.force_aspect_ratio.get().to_value(),
|
"force-aspect-ratio" => self.force_aspect_ratio.get().to_value(),
|
||||||
|
"orientation" => self.orientation.get().to_value(),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -148,6 +155,7 @@ impl ObjectImpl for Paintable {
|
||||||
#[cfg(feature = "gtk_v4_10")]
|
#[cfg(feature = "gtk_v4_10")]
|
||||||
"use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()),
|
"use-scaling-filter" => self.use_scaling_filter.set(value.get().unwrap()),
|
||||||
"force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()),
|
"force-aspect-ratio" => self.force_aspect_ratio.set(value.get().unwrap()),
|
||||||
|
"orientation" => self.orientation.set(value.get().unwrap()),
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +164,14 @@ impl ObjectImpl for Paintable {
|
||||||
impl PaintableImpl for Paintable {
|
impl PaintableImpl for Paintable {
|
||||||
fn intrinsic_height(&self) -> i32 {
|
fn intrinsic_height(&self) -> i32 {
|
||||||
if let Some(paintable) = self.paintables.borrow().first() {
|
if let Some(paintable) = self.paintables.borrow().first() {
|
||||||
|
if self
|
||||||
|
.effective_orientation(paintable.orientation)
|
||||||
|
.is_flip_width_height()
|
||||||
|
{
|
||||||
|
f32::round(paintable.width) as i32
|
||||||
|
} else {
|
||||||
f32::round(paintable.height) as i32
|
f32::round(paintable.height) as i32
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -164,7 +179,14 @@ impl PaintableImpl for Paintable {
|
||||||
|
|
||||||
fn intrinsic_width(&self) -> i32 {
|
fn intrinsic_width(&self) -> i32 {
|
||||||
if let Some(paintable) = self.paintables.borrow().first() {
|
if let Some(paintable) = self.paintables.borrow().first() {
|
||||||
|
if self
|
||||||
|
.effective_orientation(paintable.orientation)
|
||||||
|
.is_flip_width_height()
|
||||||
|
{
|
||||||
|
f32::round(paintable.height) as i32
|
||||||
|
} else {
|
||||||
f32::round(paintable.width) as i32
|
f32::round(paintable.width) as i32
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
|
@ -172,7 +194,14 @@ impl PaintableImpl for Paintable {
|
||||||
|
|
||||||
fn intrinsic_aspect_ratio(&self) -> f64 {
|
fn intrinsic_aspect_ratio(&self) -> f64 {
|
||||||
if let Some(paintable) = self.paintables.borrow().first() {
|
if let Some(paintable) = self.paintables.borrow().first() {
|
||||||
|
if self
|
||||||
|
.effective_orientation(paintable.orientation)
|
||||||
|
.is_flip_width_height()
|
||||||
|
{
|
||||||
|
paintable.height as f64 / paintable.width as f64
|
||||||
|
} else {
|
||||||
paintable.width as f64 / paintable.height as f64
|
paintable.width as f64 / paintable.height as f64
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
}
|
}
|
||||||
|
@ -180,7 +209,6 @@ impl PaintableImpl for Paintable {
|
||||||
|
|
||||||
fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
|
fn snapshot(&self, snapshot: &gdk::Snapshot, width: f64, height: f64) {
|
||||||
let snapshot = snapshot.downcast_ref::<gtk::Snapshot>().unwrap();
|
let snapshot = snapshot.downcast_ref::<gtk::Snapshot>().unwrap();
|
||||||
|
|
||||||
let background_color = self.background_color.get();
|
let background_color = self.background_color.get();
|
||||||
let force_aspect_ratio = self.force_aspect_ratio.get();
|
let force_aspect_ratio = self.force_aspect_ratio.get();
|
||||||
let paintables = self.paintables.borrow();
|
let paintables = self.paintables.borrow();
|
||||||
|
@ -201,8 +229,72 @@ impl PaintableImpl for Paintable {
|
||||||
//
|
//
|
||||||
// Based on its size relative to the snapshot width/height, all other paintables are
|
// Based on its size relative to the snapshot width/height, all other paintables are
|
||||||
// scaled accordingly.
|
// scaled accordingly.
|
||||||
let (frame_width, frame_height) = (first_paintable.width, first_paintable.height);
|
//
|
||||||
|
// We also only consider the orientation of the first paintable for now and rotate all
|
||||||
|
// overlays consistently with that to follow the behaviour of glvideoflip.
|
||||||
|
let effective_orientation = self.effective_orientation(first_paintable.orientation);
|
||||||
|
|
||||||
|
// First do the rotation around the center of the whole snapshot area
|
||||||
|
if effective_orientation != frame::Orientation::Rotate0 {
|
||||||
|
snapshot.translate(&graphene::Point::new(
|
||||||
|
width as f32 / 2.0,
|
||||||
|
height as f32 / 2.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
match effective_orientation {
|
||||||
|
frame::Orientation::Rotate0 => {}
|
||||||
|
frame::Orientation::Rotate90 => {
|
||||||
|
snapshot.rotate(90.0);
|
||||||
|
}
|
||||||
|
frame::Orientation::Rotate180 => {
|
||||||
|
snapshot.rotate(180.0);
|
||||||
|
}
|
||||||
|
frame::Orientation::Rotate270 => {
|
||||||
|
snapshot.rotate(270.0);
|
||||||
|
}
|
||||||
|
frame::Orientation::FlipRotate0 => {
|
||||||
|
snapshot.rotate_3d(180.0, >k::graphene::Vec3::y_axis());
|
||||||
|
}
|
||||||
|
frame::Orientation::FlipRotate90 => {
|
||||||
|
snapshot.rotate(90.0);
|
||||||
|
snapshot.rotate_3d(180.0, >k::graphene::Vec3::y_axis());
|
||||||
|
}
|
||||||
|
frame::Orientation::FlipRotate180 => {
|
||||||
|
snapshot.rotate(180.0);
|
||||||
|
snapshot.rotate_3d(180.0, >k::graphene::Vec3::y_axis());
|
||||||
|
}
|
||||||
|
frame::Orientation::FlipRotate270 => {
|
||||||
|
snapshot.rotate(270.0);
|
||||||
|
snapshot.rotate_3d(180.0, >k::graphene::Vec3::y_axis());
|
||||||
|
}
|
||||||
|
frame::Orientation::Auto => unreachable!(),
|
||||||
|
}
|
||||||
|
if effective_orientation != frame::Orientation::Rotate0 {
|
||||||
|
if effective_orientation.is_flip_width_height() {
|
||||||
|
snapshot.translate(&graphene::Point::new(
|
||||||
|
-height as f32 / 2.0,
|
||||||
|
-width as f32 / 2.0,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
snapshot.translate(&graphene::Point::new(
|
||||||
|
-width as f32 / 2.0,
|
||||||
|
-height as f32 / 2.0,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The rotation is applied now and we're back at the origin at this point
|
||||||
|
|
||||||
|
// Width / height of the overall frame that we're drawing. This has to be flipped
|
||||||
|
// if a 90/270 degree rotation is applied.
|
||||||
|
let (frame_width, frame_height) = if effective_orientation.is_flip_width_height() {
|
||||||
|
(first_paintable.height, first_paintable.width)
|
||||||
|
} else {
|
||||||
|
(first_paintable.width, first_paintable.height)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Amount of scaling that has to be applied to the main frame and all overlays to fill the
|
||||||
|
// available area
|
||||||
let mut scale_x = width / frame_width as f64;
|
let mut scale_x = width / frame_width as f64;
|
||||||
let mut scale_y = height / frame_height as f64;
|
let mut scale_y = height / frame_height as f64;
|
||||||
|
|
||||||
|
@ -227,14 +319,26 @@ impl PaintableImpl for Paintable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) {
|
if !background_color.is_clear() && (trans_x > f64::EPSILON || trans_y > f64::EPSILON) {
|
||||||
|
// Clamping for the bounds below has to be flipped over for 90/270 degree rotations.
|
||||||
|
let (width, height) = if effective_orientation.is_flip_width_height() {
|
||||||
|
(height, width)
|
||||||
|
} else {
|
||||||
|
(width, height)
|
||||||
|
};
|
||||||
|
|
||||||
snapshot.append_color(
|
snapshot.append_color(
|
||||||
&background_color,
|
&background_color,
|
||||||
&graphene::Rect::new(0f32, 0f32, width as f32, height as f32),
|
&graphene::Rect::new(0f32, 0f32, width as f32, height as f32),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if effective_orientation.is_flip_width_height() {
|
||||||
|
std::mem::swap(&mut trans_x, &mut trans_y);
|
||||||
|
}
|
||||||
snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32));
|
snapshot.translate(&graphene::Point::new(trans_x as f32, trans_y as f32));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// At this point we're at the origin of the area into which the actual video frame is drawn
|
||||||
|
|
||||||
// Make immutable
|
// Make immutable
|
||||||
let scale_x = scale_x;
|
let scale_x = scale_x;
|
||||||
let scale_y = scale_y;
|
let scale_y = scale_y;
|
||||||
|
@ -249,11 +353,19 @@ impl PaintableImpl for Paintable {
|
||||||
height: paintable_height,
|
height: paintable_height,
|
||||||
global_alpha,
|
global_alpha,
|
||||||
has_alpha,
|
has_alpha,
|
||||||
|
orientation: _orientation,
|
||||||
},
|
},
|
||||||
) in paintables.iter().enumerate()
|
) in paintables.iter().enumerate()
|
||||||
{
|
{
|
||||||
snapshot.push_opacity(*global_alpha as f64);
|
snapshot.push_opacity(*global_alpha as f64);
|
||||||
|
|
||||||
|
// Clamping for the bounds below has to be flipped over for 90/270 degree rotations.
|
||||||
|
let (width, height) = if effective_orientation.is_flip_width_height() {
|
||||||
|
(height, width)
|
||||||
|
} else {
|
||||||
|
(width, height)
|
||||||
|
};
|
||||||
|
|
||||||
let bounds = if !force_aspect_ratio && idx == 0 {
|
let bounds = if !force_aspect_ratio && idx == 0 {
|
||||||
// While this should end up with width again, be explicit in this case to avoid
|
// While this should end up with width again, be explicit in this case to avoid
|
||||||
// rounding errors and fill the whole area with the video frame.
|
// rounding errors and fill the whole area with the video frame.
|
||||||
|
@ -261,11 +373,32 @@ impl PaintableImpl for Paintable {
|
||||||
} else {
|
} else {
|
||||||
// Scale texture position and size with the same scale factor as the main video
|
// Scale texture position and size with the same scale factor as the main video
|
||||||
// frame, and make sure to not render outside (0, 0, width, height).
|
// frame, and make sure to not render outside (0, 0, width, height).
|
||||||
let x = f32::clamp(*x * scale_x as f32, 0.0, width as f32);
|
let (rect_x, rect_y) = if effective_orientation.is_flip_width_height() {
|
||||||
let y = f32::clamp(*y * scale_y as f32, 0.0, height as f32);
|
(
|
||||||
let texture_width = f32::min(*paintable_width * scale_x as f32, width as f32);
|
f32::clamp(*y * scale_y as f32, 0.0, width as f32),
|
||||||
let texture_height = f32::min(*paintable_height * scale_y as f32, height as f32);
|
f32::clamp(*x * scale_x as f32, 0.0, height as f32),
|
||||||
graphene::Rect::new(x, y, texture_width, texture_height)
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
f32::clamp(*x * scale_x as f32, 0.0, width as f32),
|
||||||
|
f32::clamp(*y * scale_y as f32, 0.0, height as f32),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let (texture_width, texture_height) =
|
||||||
|
if effective_orientation.is_flip_width_height() {
|
||||||
|
(
|
||||||
|
f32::min(*paintable_width * scale_y as f32, width as f32),
|
||||||
|
f32::min(*paintable_height * scale_x as f32, height as f32),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
f32::min(*paintable_width * scale_x as f32, width as f32),
|
||||||
|
f32::min(*paintable_height * scale_y as f32, height as f32),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
graphene::Rect::new(rect_x, rect_y, texture_width, texture_height)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only premultiply GL textures that expect to be in premultiplied RGBA format.
|
// Only premultiply GL textures that expect to be in premultiplied RGBA format.
|
||||||
|
@ -364,6 +497,19 @@ impl PaintableImpl for Paintable {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paintable {
|
impl Paintable {
|
||||||
|
fn effective_orientation(
|
||||||
|
&self,
|
||||||
|
paintable_orientation: frame::Orientation,
|
||||||
|
) -> frame::Orientation {
|
||||||
|
let orientation = self.orientation.get();
|
||||||
|
if orientation != frame::Orientation::Auto {
|
||||||
|
return orientation;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_ne!(paintable_orientation, frame::Orientation::Auto);
|
||||||
|
paintable_orientation
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) {
|
pub(super) fn handle_frame_changed(&self, sink: &crate::PaintableSink, frame: Frame) {
|
||||||
let context = self.gl_context.borrow();
|
let context = self.gl_context.borrow();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue