waycap_rs/encoders/
rgba_image_encoder.rs

1use crate::{
2    encoders::video::{PipewireSPA, ProcessingThread},
3    types::video_frame::RawVideoFrame,
4    VideoEncoder,
5};
6use crossbeam::channel::{Receiver, Sender};
7
8use crate::types::error::Result;
9use pipewire as pw;
10
11/// "Encoder" which outputs image::RgbaImage
12///
13/// This is entirely CPU side, and won't ever be as fast as [`NvencEncoder`] or [`VaapiEncoder`].
14/// Don't use this to record video!
15/// It will likely benefit from compile time optimizations a lot, due to the BGRA to RGBA image conversion.
16pub struct RgbaImageEncoder {
17    image_sender: Sender<image::RgbaImage>,
18    image_receiver: Receiver<image::RgbaImage>,
19}
20
21impl Default for RgbaImageEncoder {
22    fn default() -> Self {
23        let (image_sender, image_receiver) = crossbeam::channel::bounded(10);
24        Self {
25            image_sender,
26            image_receiver,
27        }
28    }
29}
30
31impl ProcessingThread for RgbaImageEncoder {
32    fn process(&mut self, frame: RawVideoFrame) -> Result<()> {
33        let mut raw = frame.data.clone();
34        bgra_to_rgba_inplace(&mut raw);
35        let image =
36            image::RgbaImage::from_raw(frame.dimensions.width, frame.dimensions.height, raw)
37                .unwrap();
38        match self.image_sender.try_send(image) {
39            Ok(_) => {}
40            Err(crossbeam::channel::TrySendError::Full(_)) => {
41                log::error!("Could not send encoded video frame. Receiver is full");
42            }
43            Err(crossbeam::channel::TrySendError::Disconnected(_)) => {
44                log::error!("Could not send encoded video frame. Receiver disconnected");
45            }
46        }
47        Ok(())
48    }
49}
50
51impl VideoEncoder for RgbaImageEncoder {
52    type Output = image::RgbaImage;
53
54    fn reset(&mut self) -> crate::types::error::Result<()> {
55        Ok(())
56    }
57
58    fn output(&mut self) -> Option<crossbeam::channel::Receiver<Self::Output>> {
59        Some(self.image_receiver.clone())
60    }
61
62    fn drop_processor(&mut self) {}
63
64    fn drain(&mut self) -> crate::types::error::Result<()> {
65        Ok(())
66    }
67
68    fn get_encoder(&self) -> &Option<ffmpeg_next::codec::encoder::Video> {
69        &None
70    }
71}
72
73impl PipewireSPA for RgbaImageEncoder {
74    fn get_spa_definition() -> Result<pipewire::spa::pod::Object> {
75        Ok(pw::spa::pod::object!(
76            pw::spa::utils::SpaTypes::ObjectParamFormat,
77            pw::spa::param::ParamType::EnumFormat,
78            pw::spa::pod::property!(
79                pw::spa::param::format::FormatProperties::MediaType,
80                Id,
81                pw::spa::param::format::MediaType::Video
82            ),
83            pw::spa::pod::property!(
84                pw::spa::param::format::FormatProperties::MediaSubtype,
85                Id,
86                pw::spa::param::format::MediaSubtype::Raw
87            ),
88            pw::spa::pod::property!(
89                pw::spa::param::format::FormatProperties::VideoFormat,
90                Id,
91                pw::spa::param::video::VideoFormat::BGRA
92            ),
93            pw::spa::pod::property!(
94                pw::spa::param::format::FormatProperties::VideoSize,
95                Choice,
96                Range,
97                Rectangle,
98                pw::spa::utils::Rectangle {
99                    width: 2560,
100                    height: 1440
101                }, // Default
102                pw::spa::utils::Rectangle {
103                    width: 1,
104                    height: 1
105                }, // Min
106                pw::spa::utils::Rectangle {
107                    width: 4096,
108                    height: 4096
109                } // Max
110            ),
111            pw::spa::pod::property!(
112                pw::spa::param::format::FormatProperties::VideoFramerate,
113                Choice,
114                Range,
115                Fraction,
116                pw::spa::utils::Fraction { num: 240, denom: 1 }, // Default
117                pw::spa::utils::Fraction { num: 0, denom: 1 },   // Min
118                pw::spa::utils::Fraction { num: 244, denom: 1 }  // Max
119            ),
120        ))
121    }
122}
123
124/// BGRA to RGBA pixel buffer conversion
125///
126/// Will likely benefit from compile time optimizations a lot, especially with SIMD instruction sets enabled.
127/// `RUSTFLAGS="-C target-cpu=x86-64-v3"` is a relatively safe bet, as according to steam hardware survey ~95% of people have it.
128pub fn bgra_to_rgba_inplace(buf: &mut [u8]) {
129    // adapted from: Source: https://users.rust-lang.org/t/the-fastest-way-to-copy-a-buffer-bgra-to-rgba/126651/11
130    let (chunked, _) = buf.as_chunks_mut::<4>();
131
132    for p in chunked {
133        let bgra = u32::from_be_bytes(*p);
134        let argb = bgra.swap_bytes();
135        let rgba = argb.rotate_left(8);
136        *p = rgba.to_be_bytes();
137    }
138}