wayclip-daemon 0.2.0

Recording and management of Wayclip daemon
use gstreamer::{BufferCopyFlags, BufferFlags, Caps, ClockTime};
use gstreamer_app::{AppSink, AppSrc};
use wayclip_core::models::error::WayclipError;

use crate::common::video::ring::data::EncodedFrame;

const DEFAULT_APPSRC_FORMAT: gstreamer::Format = gstreamer::Format::Time;
const DEFAULT_APPSRC_IS_LIVE: bool = false;
pub const DEFAULT_APPSRC_DO_TIMESTAMP: bool = false;

const DEFAULT_APPSINK_MAX_BUFFERS: u32 = 100;
const DEFAULT_APPSINK_DROP: bool = false;
const DEFAULT_APPSINK_SYNC: bool = false;

pub struct GStreamerApp;

impl GStreamerApp {
    pub fn build_app_sink() -> AppSink {
        gstreamer_app::AppSink::builder()
            .property("drop", DEFAULT_APPSINK_DROP)
            .property("max-buffers", DEFAULT_APPSINK_MAX_BUFFERS)
            .property("sync", DEFAULT_APPSINK_SYNC)
            .build()
    }

    pub fn build_app_src(caps: &Caps) -> AppSrc {
        gstreamer_app::AppSrc::builder()
            .caps(caps)
            .format(DEFAULT_APPSRC_FORMAT)
            .is_live(DEFAULT_APPSRC_IS_LIVE)
            .do_timestamp(DEFAULT_APPSRC_DO_TIMESTAMP)
            .build()
    }

    fn sync_timestamp(ts: ClockTime, base: ClockTime, offset_ms: i64) -> ClockTime {
        let normalised = ts.saturating_sub(base);
        let offset_dur = ClockTime::from_mseconds(offset_ms.unsigned_abs());
        if offset_ms >= 0 {
            normalised.saturating_add(offset_dur)
        } else {
            normalised.saturating_sub(offset_dur)
        }
    }

    pub fn push_frames(
        appsrc: AppSrc,
        frame_info: EncodedFrameInfo,
    ) -> std::thread::JoinHandle<Result<(), WayclipError>> {
        std::thread::spawn(move || {
            for frame in frame_info.frames {
                // Copy instead using `MEMORY`
                let mut payload = frame
                    .payload
                    .copy_region(BufferCopyFlags::MEMORY | BufferCopyFlags::FLAGS, ..)
                    .map_err(|e| {
                        WayclipError::Remux(format!("Failed to copy buffer: {e:?}").into())
                    })?;

                let pts = GStreamerApp::sync_timestamp(
                    frame.pts,
                    frame_info.base_pts,
                    frame_info.offset_ms,
                );

                let dts = frame.dts.map(|dts| {
                    GStreamerApp::sync_timestamp(dts, frame_info.base_pts, frame_info.offset_ms)
                });

                if let Some(buffer_ref) = payload.get_mut() {
                    buffer_ref.set_pts(pts);

                    if let Some(dts) = dts {
                        buffer_ref.set_dts(dts);
                    }

                    if let Some(duration) = frame.duration {
                        buffer_ref.set_duration(duration);
                    }

                    if !frame.is_keyframe {
                        buffer_ref.set_flags(BufferFlags::DELTA_UNIT);
                    }
                }

                appsrc.push_buffer(payload).map_err(|e| {
                    WayclipError::Remux(format!("Failed to push buffer: {:?}", e).into())
                })?;
            }

            appsrc
                .end_of_stream()
                .map_err(|e| WayclipError::Remux(format!("Failed to send EOS: {:?}", e).into()))?;

            Ok(())
        })
    }
}

pub struct EncodedFrameInfo {
    pub frames: Vec<EncodedFrame>,
    pub base_pts: ClockTime,
    pub offset_ms: i64,
}