2019-05-25 12:16:39 +00:00
|
|
|
// Copyright (C) 2019 Sebastian Dröge <sebastian@centricular.com>
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
|
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
|
|
// option. This file may not be copied, modified, or distributed
|
|
|
|
// except according to those terms.
|
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
use atomic_refcell::AtomicRefCell;
|
2019-05-25 12:16:39 +00:00
|
|
|
use glib::subclass;
|
|
|
|
use glib::subclass::prelude::*;
|
|
|
|
use gst::subclass::prelude::*;
|
|
|
|
use gst_video::prelude::*;
|
|
|
|
use gst_video::subclass::prelude::*;
|
2019-06-14 06:12:01 +00:00
|
|
|
use rav1e::color;
|
|
|
|
use rav1e::config;
|
|
|
|
use rav1e::data;
|
2019-08-30 06:46:37 +00:00
|
|
|
use std::sync::Mutex;
|
2019-05-25 12:16:39 +00:00
|
|
|
|
|
|
|
const DEFAULT_SPEED_PRESET: u32 = 5;
|
|
|
|
const DEFAULT_LOW_LATENCY: bool = false;
|
|
|
|
const DEFAULT_MIN_KEY_FRAME_INTERVAL: u64 = 12;
|
|
|
|
const DEFAULT_MAX_KEY_FRAME_INTERVAL: u64 = 240;
|
|
|
|
const DEFAULT_BITRATE: i32 = 0;
|
|
|
|
const DEFAULT_QUANTIZER: usize = 100;
|
2019-08-27 09:39:50 +00:00
|
|
|
const DEFAULT_TILE_COLS: usize = 0;
|
|
|
|
const DEFAULT_TILE_ROWS: usize = 0;
|
|
|
|
const DEFAULT_TILES: usize = 0;
|
2019-05-25 12:16:39 +00:00
|
|
|
const DEFAULT_THREADS: usize = 0;
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
struct Settings {
|
|
|
|
speed_preset: u32,
|
|
|
|
low_latency: bool,
|
|
|
|
min_key_frame_interval: u64,
|
|
|
|
max_key_frame_interval: u64,
|
|
|
|
bitrate: i32,
|
|
|
|
quantizer: usize,
|
2019-08-27 09:39:50 +00:00
|
|
|
tile_cols: usize,
|
|
|
|
tile_rows: usize,
|
|
|
|
tiles: usize,
|
2019-05-25 12:16:39 +00:00
|
|
|
threads: usize,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Settings {
|
|
|
|
fn default() -> Self {
|
|
|
|
Settings {
|
|
|
|
speed_preset: DEFAULT_SPEED_PRESET,
|
|
|
|
low_latency: DEFAULT_LOW_LATENCY,
|
|
|
|
min_key_frame_interval: DEFAULT_MIN_KEY_FRAME_INTERVAL,
|
|
|
|
max_key_frame_interval: DEFAULT_MAX_KEY_FRAME_INTERVAL,
|
|
|
|
bitrate: DEFAULT_BITRATE,
|
|
|
|
quantizer: DEFAULT_QUANTIZER,
|
2019-08-27 09:39:50 +00:00
|
|
|
tile_cols: DEFAULT_TILE_COLS,
|
|
|
|
tile_rows: DEFAULT_TILE_ROWS,
|
|
|
|
tiles: DEFAULT_TILES,
|
2019-05-25 12:16:39 +00:00
|
|
|
threads: DEFAULT_THREADS,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-27 09:39:50 +00:00
|
|
|
static PROPERTIES: [subclass::Property; 10] = [
|
2019-05-25 12:16:39 +00:00
|
|
|
subclass::Property("speed-preset", |name| {
|
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
|
|
|
"Speed Preset",
|
|
|
|
"Speed preset (10 fastest, 0 slowest)",
|
|
|
|
0,
|
|
|
|
10,
|
|
|
|
DEFAULT_SPEED_PRESET,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("low-latency", |name| {
|
|
|
|
glib::ParamSpec::boolean(
|
|
|
|
name,
|
|
|
|
"Low Latency",
|
|
|
|
"Low Latency",
|
|
|
|
DEFAULT_LOW_LATENCY,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("min-key-frame-interval", |name| {
|
|
|
|
glib::ParamSpec::uint64(
|
|
|
|
name,
|
|
|
|
"Min Key Frame Interval",
|
|
|
|
"Min Key Frame Interval",
|
|
|
|
0,
|
|
|
|
std::u64::MAX,
|
|
|
|
DEFAULT_MIN_KEY_FRAME_INTERVAL,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("max-key-frame-interval", |name| {
|
|
|
|
glib::ParamSpec::uint64(
|
|
|
|
name,
|
|
|
|
"Max Key Frame Interval",
|
|
|
|
"Max Key Frame Interval",
|
|
|
|
0,
|
|
|
|
std::u64::MAX,
|
|
|
|
DEFAULT_MAX_KEY_FRAME_INTERVAL,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("bitrate", |name| {
|
|
|
|
glib::ParamSpec::int(
|
|
|
|
name,
|
|
|
|
"Bitrate",
|
|
|
|
"Bitrate",
|
|
|
|
0,
|
|
|
|
std::i32::MAX,
|
|
|
|
DEFAULT_BITRATE,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("quantizer", |name| {
|
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
|
|
|
"Quantizer",
|
|
|
|
"Quantizer",
|
|
|
|
0,
|
|
|
|
std::u32::MAX,
|
|
|
|
DEFAULT_QUANTIZER as u32,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tile-cols", |name| {
|
2019-05-25 12:16:39 +00:00
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
2019-08-27 09:39:50 +00:00
|
|
|
"Tile Cols",
|
|
|
|
"Tile Cols",
|
2019-05-25 12:16:39 +00:00
|
|
|
0,
|
|
|
|
std::u32::MAX,
|
2019-08-27 09:39:50 +00:00
|
|
|
DEFAULT_TILE_COLS as u32,
|
2019-05-25 12:16:39 +00:00
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tile-rows", |name| {
|
2019-05-25 12:16:39 +00:00
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
2019-08-27 09:39:50 +00:00
|
|
|
"Tile Rows",
|
|
|
|
"Tile Rows",
|
2019-05-25 12:16:39 +00:00
|
|
|
0,
|
|
|
|
std::u32::MAX,
|
2019-08-27 09:39:50 +00:00
|
|
|
DEFAULT_TILE_ROWS as u32,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("tiles", |name| {
|
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
|
|
|
"Tiles",
|
|
|
|
"Tiles",
|
|
|
|
0,
|
|
|
|
std::u32::MAX,
|
|
|
|
DEFAULT_TILES as u32,
|
2019-05-25 12:16:39 +00:00
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
subclass::Property("threads", |name| {
|
|
|
|
glib::ParamSpec::uint(
|
|
|
|
name,
|
|
|
|
"Threads",
|
|
|
|
"Threads",
|
|
|
|
0,
|
|
|
|
std::u32::MAX,
|
|
|
|
DEFAULT_THREADS as u32,
|
|
|
|
glib::ParamFlags::READWRITE,
|
|
|
|
)
|
|
|
|
}),
|
|
|
|
];
|
|
|
|
|
|
|
|
enum Context {
|
|
|
|
Eight(rav1e::Context<u8>),
|
|
|
|
Sixteen(rav1e::Context<u16>),
|
|
|
|
}
|
|
|
|
|
2019-05-26 14:26:30 +00:00
|
|
|
impl Context {
|
2019-06-14 06:12:01 +00:00
|
|
|
fn receive_packet(&mut self) -> Result<(data::FrameType, u64, Vec<u8>), data::EncoderStatus> {
|
2019-05-26 14:26:30 +00:00
|
|
|
match self {
|
|
|
|
Context::Eight(ref mut context) => context
|
|
|
|
.receive_packet()
|
2019-06-12 22:52:12 +00:00
|
|
|
.map(|packet| (packet.frame_type, packet.input_frameno, packet.data)),
|
2019-05-26 14:26:30 +00:00
|
|
|
Context::Sixteen(ref mut context) => context
|
|
|
|
.receive_packet()
|
2019-06-12 22:52:12 +00:00
|
|
|
.map(|packet| (packet.frame_type, packet.input_frameno, packet.data)),
|
2019-05-26 14:26:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn send_frame(
|
|
|
|
&mut self,
|
|
|
|
in_frame: Option<&gst_video::VideoFrameRef<&gst::BufferRef>>,
|
2019-08-30 06:46:37 +00:00
|
|
|
force_keyframe: bool,
|
2019-06-14 06:12:01 +00:00
|
|
|
) -> Result<(), data::EncoderStatus> {
|
2019-05-26 14:26:30 +00:00
|
|
|
match self {
|
|
|
|
Context::Eight(ref mut context) => {
|
2019-08-30 06:46:37 +00:00
|
|
|
if let Some(in_frame) = in_frame {
|
2019-05-26 14:26:30 +00:00
|
|
|
let mut enc_frame = context.new_frame();
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[0].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(0).unwrap(),
|
|
|
|
in_frame.plane_stride()[0] as usize,
|
|
|
|
1,
|
|
|
|
);
|
|
|
|
|
|
|
|
if in_frame.n_planes() > 1 {
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[1].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(1).unwrap(),
|
|
|
|
in_frame.plane_stride()[1] as usize,
|
|
|
|
1,
|
|
|
|
);
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[2].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(2).unwrap(),
|
|
|
|
in_frame.plane_stride()[2] as usize,
|
|
|
|
1,
|
|
|
|
);
|
|
|
|
}
|
2019-08-30 06:46:37 +00:00
|
|
|
|
|
|
|
context.send_frame((
|
|
|
|
enc_frame,
|
|
|
|
Some(rav1e::data::FrameParameters {
|
|
|
|
frame_type_override: if force_keyframe {
|
|
|
|
rav1e::prelude::FrameTypeOverride::Key
|
|
|
|
} else {
|
|
|
|
rav1e::prelude::FrameTypeOverride::No
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
context.send_frame(None)
|
|
|
|
}
|
2019-05-26 14:26:30 +00:00
|
|
|
}
|
|
|
|
Context::Sixteen(ref mut context) => {
|
2019-08-30 06:46:37 +00:00
|
|
|
if let Some(in_frame) = in_frame {
|
2019-05-26 14:26:30 +00:00
|
|
|
let mut enc_frame = context.new_frame();
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[0].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(0).unwrap(),
|
|
|
|
in_frame.plane_stride()[0] as usize,
|
|
|
|
2,
|
|
|
|
);
|
|
|
|
|
|
|
|
if in_frame.n_planes() > 1 {
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[1].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(1).unwrap(),
|
|
|
|
in_frame.plane_stride()[1] as usize,
|
|
|
|
2,
|
|
|
|
);
|
2019-08-30 06:46:37 +00:00
|
|
|
enc_frame.planes[2].copy_from_raw_u8(
|
2019-05-26 14:26:30 +00:00
|
|
|
in_frame.plane_data(2).unwrap(),
|
|
|
|
in_frame.plane_stride()[2] as usize,
|
|
|
|
2,
|
|
|
|
);
|
|
|
|
}
|
2019-08-30 06:46:37 +00:00
|
|
|
|
|
|
|
context.send_frame((
|
|
|
|
enc_frame,
|
|
|
|
Some(rav1e::data::FrameParameters {
|
|
|
|
frame_type_override: if force_keyframe {
|
|
|
|
rav1e::prelude::FrameTypeOverride::Key
|
|
|
|
} else {
|
|
|
|
rav1e::prelude::FrameTypeOverride::No
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
))
|
|
|
|
} else {
|
|
|
|
context.send_frame(None)
|
|
|
|
}
|
2019-05-26 14:26:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn flush(&mut self) {
|
|
|
|
match self {
|
|
|
|
Context::Eight(ref mut context) => context.flush(),
|
|
|
|
Context::Sixteen(ref mut context) => context.flush(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-25 12:16:39 +00:00
|
|
|
struct State {
|
|
|
|
context: Context,
|
|
|
|
video_info: gst_video::VideoInfo,
|
|
|
|
}
|
|
|
|
|
2020-11-15 13:50:12 +00:00
|
|
|
pub struct Rav1Enc {
|
2019-09-14 15:38:19 +00:00
|
|
|
state: AtomicRefCell<Option<State>>,
|
2019-05-25 12:16:39 +00:00
|
|
|
settings: Mutex<Settings>,
|
|
|
|
}
|
|
|
|
|
2019-10-31 22:34:21 +00:00
|
|
|
lazy_static! {
|
|
|
|
static ref CAT: gst::DebugCategory = gst::DebugCategory::new(
|
|
|
|
"rav1enc",
|
|
|
|
gst::DebugColorFlags::empty(),
|
|
|
|
Some("rav1e AV1 encoder"),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-05-25 12:16:39 +00:00
|
|
|
impl ObjectSubclass for Rav1Enc {
|
|
|
|
const NAME: &'static str = "Rav1Enc";
|
2020-11-15 13:50:12 +00:00
|
|
|
type Type = super::Rav1Enc;
|
2019-05-25 12:16:39 +00:00
|
|
|
type ParentType = gst_video::VideoEncoder;
|
|
|
|
type Instance = gst::subclass::ElementInstanceStruct<Self>;
|
|
|
|
type Class = subclass::simple::ClassStruct<Self>;
|
|
|
|
|
|
|
|
glib_object_subclass!();
|
|
|
|
|
|
|
|
fn new() -> Self {
|
|
|
|
Self {
|
2019-09-14 15:38:19 +00:00
|
|
|
state: AtomicRefCell::new(None),
|
2019-05-25 12:16:39 +00:00
|
|
|
settings: Mutex::new(Default::default()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 13:50:12 +00:00
|
|
|
fn class_init(klass: &mut Self::Class) {
|
2019-05-25 12:16:39 +00:00
|
|
|
klass.set_metadata(
|
|
|
|
"rav1e AV1 encoder",
|
|
|
|
"Encoder/Video",
|
|
|
|
"rav1e AV1 encoder",
|
|
|
|
"Sebastian Dröge <sebastian@centricular.com>",
|
|
|
|
);
|
|
|
|
|
|
|
|
let sink_caps = gst::Caps::new_simple(
|
|
|
|
"video/x-raw",
|
|
|
|
&[
|
|
|
|
(
|
|
|
|
"format",
|
|
|
|
&gst::List::new(&[
|
2019-10-04 08:47:50 +00:00
|
|
|
&gst_video::VideoFormat::I420.to_str(),
|
|
|
|
&gst_video::VideoFormat::Y42b.to_str(),
|
|
|
|
&gst_video::VideoFormat::Y444.to_str(),
|
|
|
|
&gst_video::VideoFormat::I42010le.to_str(),
|
|
|
|
&gst_video::VideoFormat::I42210le.to_str(),
|
|
|
|
&gst_video::VideoFormat::Y44410le.to_str(),
|
|
|
|
&gst_video::VideoFormat::I42012le.to_str(),
|
|
|
|
&gst_video::VideoFormat::I42212le.to_str(),
|
|
|
|
&gst_video::VideoFormat::Y44412le.to_str(),
|
|
|
|
// &gst_video::VideoFormat::Gray8.to_str(),
|
2019-05-25 12:16:39 +00:00
|
|
|
]),
|
|
|
|
),
|
|
|
|
("width", &gst::IntRange::<i32>::new(1, std::i32::MAX)),
|
|
|
|
("height", &gst::IntRange::<i32>::new(1, std::i32::MAX)),
|
|
|
|
(
|
|
|
|
"framerate",
|
|
|
|
&gst::FractionRange::new(
|
|
|
|
gst::Fraction::new(0, 1),
|
|
|
|
gst::Fraction::new(std::i32::MAX, 1),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
let sink_pad_template = gst::PadTemplate::new(
|
|
|
|
"sink",
|
|
|
|
gst::PadDirection::Sink,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&sink_caps,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
klass.add_pad_template(sink_pad_template);
|
|
|
|
|
|
|
|
let src_caps = gst::Caps::new_simple("video/x-av1", &[]);
|
|
|
|
let src_pad_template = gst::PadTemplate::new(
|
|
|
|
"src",
|
|
|
|
gst::PadDirection::Src,
|
|
|
|
gst::PadPresence::Always,
|
|
|
|
&src_caps,
|
|
|
|
)
|
|
|
|
.unwrap();
|
|
|
|
klass.add_pad_template(src_pad_template);
|
|
|
|
|
|
|
|
klass.install_properties(&PROPERTIES);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectImpl for Rav1Enc {
|
2020-11-15 13:50:12 +00:00
|
|
|
fn set_property(&self, _obj: &Self::Type, id: usize, value: &glib::Value) {
|
2019-05-25 12:16:39 +00:00
|
|
|
let prop = &PROPERTIES[id];
|
|
|
|
|
|
|
|
match *prop {
|
|
|
|
subclass::Property("speed-preset", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.speed_preset = value.get_some().expect("type checked upstream");
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("low-latency", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.low_latency = value.get_some().expect("type checked upstream");
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("min-key-frame-interval", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.min_key_frame_interval = value.get_some().expect("type checked upstream");
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("max-key-frame-interval", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.max_key_frame_interval = value.get_some().expect("type checked upstream");
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("bitrate", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.bitrate = value.get_some().expect("type checked upstream");
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("quantizer", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.quantizer =
|
|
|
|
value.get_some::<u32>().expect("type checked upstream") as usize;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tile-cols", ..) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-27 09:39:50 +00:00
|
|
|
settings.tile_cols =
|
2019-08-12 22:45:36 +00:00
|
|
|
value.get_some::<u32>().expect("type checked upstream") as usize;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tile-rows", ..) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-27 09:39:50 +00:00
|
|
|
settings.tile_rows =
|
2019-08-12 22:45:36 +00:00
|
|
|
value.get_some::<u32>().expect("type checked upstream") as usize;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tiles", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
|
|
|
settings.tiles = value.get_some::<u32>().expect("type checked upstream") as usize;
|
|
|
|
}
|
2019-05-25 12:16:39 +00:00
|
|
|
subclass::Property("threads", ..) => {
|
|
|
|
let mut settings = self.settings.lock().unwrap();
|
2019-08-12 22:45:36 +00:00
|
|
|
settings.threads = value.get_some::<u32>().expect("type checked upstream") as usize;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 13:50:12 +00:00
|
|
|
fn get_property(&self, _obj: &Self::Type, id: usize) -> Result<glib::Value, ()> {
|
2019-05-25 12:16:39 +00:00
|
|
|
let prop = &PROPERTIES[id];
|
|
|
|
|
|
|
|
match *prop {
|
|
|
|
subclass::Property("speed-preset", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.speed_preset.to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("low-latency", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.low_latency.to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("min-key-frame-interval", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.min_key_frame_interval.to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("max-key-frame-interval", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.max_key_frame_interval.to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("bitrate", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok(settings.bitrate.to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("quantizer", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok((settings.quantizer as u32).to_value())
|
|
|
|
}
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tile-cols", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok((settings.tile_cols as u32).to_value())
|
|
|
|
}
|
|
|
|
subclass::Property("tile-rows", ..) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
let settings = self.settings.lock().unwrap();
|
2019-08-27 09:39:50 +00:00
|
|
|
Ok((settings.tile_rows as u32).to_value())
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-08-27 09:39:50 +00:00
|
|
|
subclass::Property("tiles", ..) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
let settings = self.settings.lock().unwrap();
|
2019-08-27 09:39:50 +00:00
|
|
|
Ok((settings.tiles as u32).to_value())
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
subclass::Property("threads", ..) => {
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
Ok((settings.threads as u32).to_value())
|
|
|
|
}
|
|
|
|
_ => unimplemented!(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ElementImpl for Rav1Enc {}
|
|
|
|
|
|
|
|
impl VideoEncoderImpl for Rav1Enc {
|
2020-11-15 13:50:12 +00:00
|
|
|
fn stop(&self, _element: &Self::Type) -> Result<(), gst::ErrorMessage> {
|
2019-09-14 15:38:19 +00:00
|
|
|
*self.state.borrow_mut() = None;
|
|
|
|
|
|
|
|
Ok(())
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
|
2020-03-19 10:55:29 +00:00
|
|
|
// For the colorimetry mapping below
|
|
|
|
#[allow(clippy::wildcard_in_or_patterns)]
|
2019-05-25 12:16:39 +00:00
|
|
|
fn set_format(
|
|
|
|
&self,
|
2020-11-15 13:50:12 +00:00
|
|
|
element: &Self::Type,
|
2019-12-23 11:31:24 +00:00
|
|
|
state: &gst_video::VideoCodecState<'static, gst_video::video_codec_state::Readable>,
|
2019-05-25 12:16:39 +00:00
|
|
|
) -> Result<(), gst::LoggableError> {
|
|
|
|
self.finish(element)
|
2019-10-31 22:34:21 +00:00
|
|
|
.map_err(|_| gst_loggable_error!(CAT, "Failed to drain"))?;
|
2019-05-25 12:16:39 +00:00
|
|
|
|
|
|
|
let video_info = state.get_info();
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_debug!(CAT, obj: element, "Setting format {:?}", video_info);
|
2019-05-25 12:16:39 +00:00
|
|
|
|
|
|
|
let settings = self.settings.lock().unwrap();
|
|
|
|
|
2019-05-26 15:13:48 +00:00
|
|
|
// TODO: More properties, HDR information
|
2019-06-14 06:12:01 +00:00
|
|
|
let cfg = config::Config {
|
|
|
|
enc: config::EncoderConfig {
|
2019-05-25 12:16:39 +00:00
|
|
|
width: video_info.width() as usize,
|
|
|
|
height: video_info.height() as usize,
|
|
|
|
bit_depth: video_info.format_info().depth()[0] as usize,
|
|
|
|
chroma_sampling: match video_info.format() {
|
|
|
|
gst_video::VideoFormat::I420
|
|
|
|
| gst_video::VideoFormat::I42010le
|
2019-06-14 06:12:01 +00:00
|
|
|
| gst_video::VideoFormat::I42012le => color::ChromaSampling::Cs420,
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_video::VideoFormat::Y42b
|
|
|
|
| gst_video::VideoFormat::I42210le
|
2019-06-14 06:12:01 +00:00
|
|
|
| gst_video::VideoFormat::I42212le => color::ChromaSampling::Cs422,
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_video::VideoFormat::Y444
|
|
|
|
| gst_video::VideoFormat::Y44410le
|
2019-06-14 06:12:01 +00:00
|
|
|
| gst_video::VideoFormat::Y44412le => color::ChromaSampling::Cs444,
|
|
|
|
// gst_video::VideoFormat::Gray8 => color::ChromaSampling::Cs400,
|
2019-05-25 12:16:39 +00:00
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
2019-05-26 15:13:48 +00:00
|
|
|
chroma_sample_position: match video_info.chroma_site() {
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoChromaSite::H_COSITED => color::ChromaSamplePosition::Vertical,
|
|
|
|
gst_video::VideoChromaSite::COSITED => color::ChromaSamplePosition::Colocated,
|
|
|
|
_ => color::ChromaSamplePosition::Unknown,
|
2019-05-26 15:13:48 +00:00
|
|
|
},
|
|
|
|
pixel_range: match video_info.colorimetry().range() {
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoColorRange::Range0255 => color::PixelRange::Full,
|
2019-08-02 09:11:13 +00:00
|
|
|
_ => color::PixelRange::Limited,
|
2019-05-26 15:13:48 +00:00
|
|
|
},
|
|
|
|
color_description: {
|
|
|
|
let matrix = match video_info.colorimetry().matrix() {
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoColorMatrix::Rgb => color::MatrixCoefficients::Identity,
|
2019-08-19 15:57:37 +00:00
|
|
|
gst_video::VideoColorMatrix::Fcc => color::MatrixCoefficients::FCC,
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoColorMatrix::Bt709 => color::MatrixCoefficients::BT709,
|
2019-08-19 15:57:37 +00:00
|
|
|
gst_video::VideoColorMatrix::Bt601 => color::MatrixCoefficients::BT601,
|
|
|
|
gst_video::VideoColorMatrix::Smpte240m => {
|
|
|
|
color::MatrixCoefficients::SMPTE240
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
2019-08-19 15:57:37 +00:00
|
|
|
gst_video::VideoColorMatrix::Bt2020 => color::MatrixCoefficients::BT2020NCL,
|
2019-06-14 06:12:01 +00:00
|
|
|
_ => color::MatrixCoefficients::Unspecified,
|
2019-05-26 15:13:48 +00:00
|
|
|
};
|
|
|
|
let transfer = match video_info.colorimetry().transfer() {
|
|
|
|
gst_video::VideoTransferFunction::Gamma10 => {
|
2019-06-14 06:12:01 +00:00
|
|
|
color::TransferCharacteristics::Linear
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Bt709 => {
|
2019-08-19 15:57:37 +00:00
|
|
|
color::TransferCharacteristics::BT709
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Smpte240m => {
|
2019-08-19 15:57:37 +00:00
|
|
|
color::TransferCharacteristics::SMPTE240
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Srgb => {
|
2019-06-14 06:12:01 +00:00
|
|
|
color::TransferCharacteristics::SRGB
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Log100 => {
|
2019-08-19 15:57:37 +00:00
|
|
|
color::TransferCharacteristics::Log100
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Log316 => {
|
2019-08-19 15:57:37 +00:00
|
|
|
color::TransferCharacteristics::Log100Sqrt10
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Bt202012 => {
|
2019-08-19 15:57:37 +00:00
|
|
|
color::TransferCharacteristics::BT2020_12Bit
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
gst_video::VideoTransferFunction::Gamma18
|
|
|
|
| gst_video::VideoTransferFunction::Gamma20
|
|
|
|
| gst_video::VideoTransferFunction::Gamma22
|
|
|
|
| gst_video::VideoTransferFunction::Gamma28
|
|
|
|
| gst_video::VideoTransferFunction::Adobergb
|
2019-06-14 06:12:01 +00:00
|
|
|
| _ => color::TransferCharacteristics::Unspecified,
|
2019-05-26 15:13:48 +00:00
|
|
|
};
|
|
|
|
let primaries = match video_info.colorimetry().primaries() {
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoColorPrimaries::Bt709 => color::ColorPrimaries::BT709,
|
|
|
|
gst_video::VideoColorPrimaries::Bt470m => color::ColorPrimaries::BT470M,
|
|
|
|
gst_video::VideoColorPrimaries::Bt470bg => color::ColorPrimaries::BT470BG,
|
2019-08-19 15:57:37 +00:00
|
|
|
gst_video::VideoColorPrimaries::Smpte170m => color::ColorPrimaries::BT601,
|
|
|
|
gst_video::VideoColorPrimaries::Smpte240m => {
|
|
|
|
color::ColorPrimaries::SMPTE240
|
|
|
|
}
|
|
|
|
gst_video::VideoColorPrimaries::Film => color::ColorPrimaries::GenericFilm,
|
2019-06-14 06:12:01 +00:00
|
|
|
gst_video::VideoColorPrimaries::Bt2020 => color::ColorPrimaries::BT2020,
|
2019-05-26 15:13:48 +00:00
|
|
|
gst_video::VideoColorPrimaries::Adobergb | _ => {
|
2019-06-14 06:12:01 +00:00
|
|
|
color::ColorPrimaries::Unspecified
|
2019-05-26 15:13:48 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-06-14 06:12:01 +00:00
|
|
|
Some(color::ColorDescription {
|
2019-05-26 15:13:48 +00:00
|
|
|
color_primaries: primaries,
|
|
|
|
transfer_characteristics: transfer,
|
|
|
|
matrix_coefficients: matrix,
|
|
|
|
})
|
|
|
|
},
|
2019-06-14 06:12:01 +00:00
|
|
|
speed_settings: config::SpeedSettings::from_preset(settings.speed_preset as usize),
|
2019-05-25 12:16:39 +00:00
|
|
|
time_base: if video_info.fps() != gst::Fraction::new(0, 1) {
|
2019-06-14 06:12:01 +00:00
|
|
|
data::Rational {
|
2019-05-25 12:16:39 +00:00
|
|
|
num: *video_info.fps().numer() as u64,
|
|
|
|
den: *video_info.fps().denom() as u64,
|
|
|
|
}
|
|
|
|
} else {
|
2019-06-14 06:12:01 +00:00
|
|
|
data::Rational { num: 30, den: 1 }
|
2019-05-25 12:16:39 +00:00
|
|
|
},
|
|
|
|
low_latency: settings.low_latency,
|
|
|
|
min_key_frame_interval: settings.min_key_frame_interval,
|
|
|
|
max_key_frame_interval: settings.max_key_frame_interval,
|
|
|
|
bitrate: settings.bitrate,
|
|
|
|
quantizer: settings.quantizer,
|
2019-08-27 09:39:50 +00:00
|
|
|
tile_cols: settings.tile_cols,
|
|
|
|
tile_rows: settings.tile_rows,
|
|
|
|
tiles: settings.tiles,
|
2019-05-25 12:16:39 +00:00
|
|
|
..Default::default()
|
|
|
|
},
|
|
|
|
threads: settings.threads,
|
|
|
|
};
|
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
*self.state.borrow_mut() = Some(State {
|
2019-05-25 12:16:39 +00:00
|
|
|
context: if video_info.format_info().depth()[0] > 8 {
|
2019-08-27 09:39:50 +00:00
|
|
|
Context::Sixteen(cfg.new_context().map_err(|err| {
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_loggable_error!(CAT, "Failed to create context: {:?}", err)
|
2019-08-27 09:39:50 +00:00
|
|
|
})?)
|
2019-05-25 12:16:39 +00:00
|
|
|
} else {
|
2019-08-27 09:39:50 +00:00
|
|
|
Context::Eight(cfg.new_context().map_err(|err| {
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_loggable_error!(CAT, "Failed to create context: {:?}", err)
|
2019-08-27 09:39:50 +00:00
|
|
|
})?)
|
2019-05-25 12:16:39 +00:00
|
|
|
},
|
2019-11-24 22:00:27 +00:00
|
|
|
video_info,
|
2019-05-25 12:16:39 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
let output_state = element
|
|
|
|
.set_output_state(gst::Caps::new_simple("video/x-av1", &[]), Some(state))
|
2019-10-31 22:34:21 +00:00
|
|
|
.map_err(|_| gst_loggable_error!(CAT, "Failed to set output state"))?;
|
2019-05-25 12:16:39 +00:00
|
|
|
element
|
|
|
|
.negotiate(output_state)
|
2019-10-31 22:34:21 +00:00
|
|
|
.map_err(|_| gst_loggable_error!(CAT, "Failed to negotiate"))?;
|
2019-05-25 12:16:39 +00:00
|
|
|
|
|
|
|
self.parent_set_format(element, state)
|
|
|
|
}
|
|
|
|
|
2020-11-15 13:50:12 +00:00
|
|
|
fn flush(&self, element: &Self::Type) -> bool {
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_debug!(CAT, obj: element, "Flushing");
|
2019-05-25 12:16:39 +00:00
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
let mut state_guard = self.state.borrow_mut();
|
2019-05-25 12:16:39 +00:00
|
|
|
if let Some(ref mut state) = *state_guard {
|
2019-05-26 14:26:30 +00:00
|
|
|
state.context.flush();
|
2019-11-24 22:00:27 +00:00
|
|
|
while let Ok(_) | Err(data::EncoderStatus::Encoded) = state.context.receive_packet() {
|
|
|
|
gst_debug!(CAT, obj: element, "Dropping packet on flush",);
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
true
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
|
2020-11-15 13:50:12 +00:00
|
|
|
fn finish(&self, element: &Self::Type) -> Result<gst::FlowSuccess, gst::FlowError> {
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_debug!(CAT, obj: element, "Finishing");
|
2019-05-25 12:16:39 +00:00
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
let mut state_guard = self.state.borrow_mut();
|
2019-05-25 12:16:39 +00:00
|
|
|
if let Some(ref mut state) = *state_guard {
|
2019-08-30 06:46:37 +00:00
|
|
|
if let Err(data::EncoderStatus::Failure) = state.context.send_frame(None, false) {
|
2019-05-26 14:26:30 +00:00
|
|
|
return Err(gst::FlowError::Error);
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-05-26 14:26:30 +00:00
|
|
|
state.context.flush();
|
2019-09-14 15:38:19 +00:00
|
|
|
self.output_frames(element, state)?;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-09-14 15:38:19 +00:00
|
|
|
|
|
|
|
Ok(gst::FlowSuccess::Ok)
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_frame(
|
|
|
|
&self,
|
2020-11-15 13:50:12 +00:00
|
|
|
element: &Self::Type,
|
2019-05-25 12:16:39 +00:00
|
|
|
frame: gst_video::VideoCodecFrame,
|
|
|
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
2019-09-14 15:38:19 +00:00
|
|
|
let mut state_guard = self.state.borrow_mut();
|
2019-05-25 12:16:39 +00:00
|
|
|
let state = state_guard.as_mut().ok_or(gst::FlowError::NotNegotiated)?;
|
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
self.output_frames(element, state)?;
|
|
|
|
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2019-05-25 12:16:39 +00:00
|
|
|
obj: element,
|
|
|
|
"Sending frame {}",
|
|
|
|
frame.get_system_frame_number()
|
|
|
|
);
|
|
|
|
|
2019-05-26 14:26:30 +00:00
|
|
|
let input_buffer = frame
|
|
|
|
.get_input_buffer()
|
|
|
|
.expect("frame without input buffer");
|
2019-05-25 12:16:39 +00:00
|
|
|
|
2019-05-26 14:26:30 +00:00
|
|
|
let in_frame =
|
|
|
|
gst_video::VideoFrameRef::from_buffer_ref_readable(&*input_buffer, &state.video_info)
|
2019-12-18 05:50:10 +00:00
|
|
|
.map_err(|_| {
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to map output buffer readable"]
|
|
|
|
);
|
|
|
|
gst::FlowError::Error
|
|
|
|
})?;
|
|
|
|
|
2019-08-30 06:46:37 +00:00
|
|
|
match state.context.send_frame(
|
|
|
|
Some(&in_frame),
|
|
|
|
frame
|
|
|
|
.get_flags()
|
|
|
|
.contains(gst_video::VideoCodecFrameFlags::FORCE_KEYFRAME),
|
|
|
|
) {
|
2019-05-25 12:16:39 +00:00
|
|
|
Ok(_) => {
|
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2019-05-25 12:16:39 +00:00
|
|
|
obj: element,
|
|
|
|
"Sent frame {}",
|
|
|
|
frame.get_system_frame_number()
|
|
|
|
);
|
|
|
|
}
|
2019-06-14 06:12:01 +00:00
|
|
|
Err(data::EncoderStatus::Failure) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_element_error!(element, gst::CoreError::Failed, ["Failed to send frame"]);
|
|
|
|
return Err(gst::FlowError::Error);
|
|
|
|
}
|
|
|
|
Err(_) => (),
|
|
|
|
}
|
|
|
|
|
2019-09-14 15:38:19 +00:00
|
|
|
self.output_frames(element, state)
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Rav1Enc {
|
|
|
|
fn output_frames(
|
|
|
|
&self,
|
2020-11-15 13:50:12 +00:00
|
|
|
element: &super::Rav1Enc,
|
2019-09-14 15:38:19 +00:00
|
|
|
state: &mut State,
|
2019-05-25 12:16:39 +00:00
|
|
|
) -> Result<gst::FlowSuccess, gst::FlowError> {
|
|
|
|
loop {
|
2019-05-26 14:26:30 +00:00
|
|
|
match state.context.receive_packet() {
|
|
|
|
Ok((packet_type, packet_number, packet_data)) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2019-05-25 12:16:39 +00:00
|
|
|
obj: element,
|
|
|
|
"Received packet {} of size {}, frame type {:?}",
|
2019-05-26 14:26:30 +00:00
|
|
|
packet_number,
|
|
|
|
packet_data.len(),
|
|
|
|
packet_type
|
2019-05-25 12:16:39 +00:00
|
|
|
);
|
|
|
|
|
2019-12-18 16:55:17 +00:00
|
|
|
let mut frame = element.get_oldest_frame().expect("frame not found");
|
2019-06-14 06:12:01 +00:00
|
|
|
if packet_type == data::FrameType::KEY {
|
2019-05-25 12:16:39 +00:00
|
|
|
frame.set_flags(gst_video::VideoCodecFrameFlags::SYNC_POINT);
|
|
|
|
}
|
|
|
|
let output_buffer = gst::Buffer::from_mut_slice(packet_data);
|
|
|
|
frame.set_output_buffer(output_buffer);
|
2019-08-14 19:12:26 +00:00
|
|
|
element.finish_frame(Some(frame))?;
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-06-14 06:12:01 +00:00
|
|
|
Err(data::EncoderStatus::Encoded) => {
|
2019-10-31 22:34:21 +00:00
|
|
|
gst_debug!(CAT, obj: element, "Encoded but not output frame yet",);
|
2019-05-25 12:16:39 +00:00
|
|
|
}
|
2019-06-14 06:12:01 +00:00
|
|
|
Err(data::EncoderStatus::Failure) => {
|
2019-05-25 12:16:39 +00:00
|
|
|
gst_element_error!(
|
|
|
|
element,
|
|
|
|
gst::CoreError::Failed,
|
|
|
|
["Failed to receive frame"]
|
|
|
|
);
|
|
|
|
return Err(gst::FlowError::Error);
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
gst_debug!(
|
2019-10-31 22:34:21 +00:00
|
|
|
CAT,
|
2019-05-25 12:16:39 +00:00
|
|
|
obj: element,
|
|
|
|
"Soft error when receiving frame: {:?}",
|
|
|
|
err
|
|
|
|
);
|
|
|
|
return Ok(gst::FlowSuccess::Ok);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|