1use styx_core::prelude::*;
2
3use crate::decoder::raw::yuv_to_rgb;
4#[cfg(feature = "image")]
5use crate::decoder::{ImageDecode, process_to_dynamic};
6use crate::{Codec, CodecDescriptor, CodecError, CodecKind};
7use yuvutils_rs::{YuvPackedImage, YuvRange, YuvStandardMatrix};
8
9#[inline(always)]
10fn map_colorspace(color: ColorSpace) -> (YuvRange, YuvStandardMatrix) {
11 match color {
12 ColorSpace::Bt709 => (YuvRange::Limited, YuvStandardMatrix::Bt709),
13 ColorSpace::Bt2020 => (YuvRange::Limited, YuvStandardMatrix::Bt2020),
14 ColorSpace::Srgb => (YuvRange::Full, YuvStandardMatrix::Bt601),
17 ColorSpace::Unknown => (YuvRange::Limited, YuvStandardMatrix::Bt709),
18 }
19}
20
21pub struct YuyvToRgbDecoder {
23 descriptor: CodecDescriptor,
24 pool: BufferPool,
25}
26
27impl YuyvToRgbDecoder {
28 pub fn new(max_width: u32, max_height: u32) -> Self {
29 let bytes = max_width as usize * max_height as usize * 3;
30 Self::with_pool(BufferPool::with_limits(2, bytes, 4))
31 }
32
33 pub fn with_pool(pool: BufferPool) -> Self {
34 Self {
35 descriptor: CodecDescriptor {
36 kind: CodecKind::Decoder,
37 input: FourCc::new(*b"YUYV"),
38 output: FourCc::new(*b"RG24"),
39 name: "yuv2rgb",
40 impl_name: "yuyv-cpu",
41 },
42 pool,
43 }
44 }
45
46 pub fn decode_into(&self, input: &FrameLease, dst: &mut [u8]) -> Result<FrameMeta, CodecError> {
50 let meta = input.meta();
51 if meta.format.code != self.descriptor.input {
52 return Err(CodecError::FormatMismatch {
53 expected: self.descriptor.input,
54 actual: meta.format.code,
55 });
56 }
57 let width = meta.format.resolution.width.get() as usize;
58 let height = meta.format.resolution.height.get() as usize;
59 let color = meta.format.color;
60 let plane = input
61 .planes()
62 .into_iter()
63 .next()
64 .ok_or_else(|| CodecError::Codec("yuyv frame missing plane".into()))?;
65 let stride = plane.stride().max(width * 2);
66 let required = stride
67 .checked_mul(height)
68 .ok_or_else(|| CodecError::Codec("yuyv stride overflow".into()))?;
69 if plane.data().len() < required {
70 return Err(CodecError::Codec("yuyv plane buffer too short".into()));
71 }
72
73 let row_bytes = width
74 .checked_mul(3)
75 .ok_or_else(|| CodecError::Codec("yuyv output overflow".into()))?;
76 let out_len = row_bytes
77 .checked_mul(height)
78 .ok_or_else(|| CodecError::Codec("yuyv output overflow".into()))?;
79 if dst.len() < out_len {
80 return Err(CodecError::Codec("yuyv dst buffer too short".into()));
81 }
82 let dst = &mut dst[..out_len];
83
84 let src = &plane.data()[..required];
85 let packed = YuvPackedImage {
86 yuy: src,
87 yuy_stride: stride as u32,
88 width: width as u32,
89 height: height as u32,
90 };
91 let (range, matrix) = map_colorspace(color);
92 if yuvutils_rs::yuyv422_to_rgb(&packed, dst, row_bytes as u32, range, matrix).is_err() {
93 for y in 0..height {
94 let src_line = &src[y * stride..][..width * 2];
95 let dst_line = &mut dst[y * row_bytes..(y + 1) * row_bytes];
96 for (dst_px, src_px) in dst_line
97 .chunks_exact_mut(6)
98 .zip(src_line[..width * 2].chunks_exact(4))
99 {
100 let y0 = unsafe { *src_px.get_unchecked(0) as i32 };
101 let u = unsafe { *src_px.get_unchecked(1) as i32 };
102 let y1 = unsafe { *src_px.get_unchecked(2) as i32 };
103 let v = unsafe { *src_px.get_unchecked(3) as i32 };
104 let (r0, g0, b0) = yuv_to_rgb(y0, u, v, color);
105 let (r1, g1, b1) = yuv_to_rgb(y1, u, v, color);
106 unsafe {
107 *dst_px.get_unchecked_mut(0) = r0;
108 *dst_px.get_unchecked_mut(1) = g0;
109 *dst_px.get_unchecked_mut(2) = b0;
110 *dst_px.get_unchecked_mut(3) = r1;
111 *dst_px.get_unchecked_mut(4) = g1;
112 *dst_px.get_unchecked_mut(5) = b1;
113 }
114 }
115 }
116 }
117
118 Ok(FrameMeta::new(
119 MediaFormat::new(
120 self.descriptor.output,
121 meta.format.resolution,
122 meta.format.color,
123 ),
124 meta.timestamp,
125 ))
126 }
127}
128
129impl Codec for YuyvToRgbDecoder {
130 fn descriptor(&self) -> &CodecDescriptor {
131 &self.descriptor
132 }
133
134 fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
135 let layout = plane_layout_from_dims(
136 input.meta().format.resolution.width,
137 input.meta().format.resolution.height,
138 3,
139 );
140 let mut buf = self.pool.lease();
141 unsafe { buf.resize_uninit(layout.len) };
142 let meta = self.decode_into(&input, buf.as_mut_slice())?;
143 Ok(unsafe { FrameLease::single_plane_uninit(meta, buf, layout.len, layout.stride) })
144 }
145}
146
147#[cfg(feature = "image")]
148impl ImageDecode for YuyvToRgbDecoder {
149 fn decode_image(&self, frame: FrameLease) -> Result<image::DynamicImage, CodecError> {
150 process_to_dynamic(self, frame)
151 }
152}
153
154pub struct YuyvToLumaDecoder {
156 descriptor: CodecDescriptor,
157 pool: BufferPool,
158}
159
160impl YuyvToLumaDecoder {
161 pub fn new(max_width: u32, max_height: u32) -> Self {
162 let bytes = max_width as usize * max_height as usize;
163 Self::with_pool(BufferPool::with_limits(2, bytes, 4))
164 }
165
166 pub fn with_pool(pool: BufferPool) -> Self {
167 Self {
168 descriptor: CodecDescriptor {
169 kind: CodecKind::Decoder,
170 input: FourCc::new(*b"YUYV"),
171 output: FourCc::new(*b"GREY"),
172 name: "yuv2luma",
173 impl_name: "yuyv-luma",
174 },
175 pool,
176 }
177 }
178
179 pub fn decode_into(&self, input: &FrameLease, dst: &mut [u8]) -> Result<FrameMeta, CodecError> {
183 let meta = input.meta();
184 if meta.format.code != self.descriptor.input {
185 return Err(CodecError::FormatMismatch {
186 expected: self.descriptor.input,
187 actual: meta.format.code,
188 });
189 }
190 let width = meta.format.resolution.width.get() as usize;
191 let height = meta.format.resolution.height.get() as usize;
192 let plane = input
193 .planes()
194 .into_iter()
195 .next()
196 .ok_or_else(|| CodecError::Codec("yuyv frame missing plane".into()))?;
197 let stride = plane.stride().max(width * 2);
198 let required = stride
199 .checked_mul(height)
200 .ok_or_else(|| CodecError::Codec("yuyv stride overflow".into()))?;
201 if plane.data().len() < required {
202 return Err(CodecError::Codec("yuyv plane buffer too short".into()));
203 }
204
205 let out_len = width
206 .checked_mul(height)
207 .ok_or_else(|| CodecError::Codec("yuyv luma output overflow".into()))?;
208 if dst.len() < out_len {
209 return Err(CodecError::Codec("yuyv luma dst buffer too short".into()));
210 }
211 let dst = &mut dst[..out_len];
212 let src = &plane.data()[..required];
213
214 #[cfg(target_arch = "aarch64")]
215 {
216 use std::arch::aarch64::{vld2q_u8, vst1q_u8};
217 for y in 0..height {
218 let src_line = &src[y * stride..][..width * 2];
219 let dst_line = &mut dst[y * width..(y + 1) * width];
220 unsafe {
221 let src_ptr = src_line.as_ptr();
222 let dst_ptr = dst_line.as_mut_ptr();
223 let blocks = width / 16;
224 for i in 0..blocks {
225 let src_block = src_ptr.add(i * 32) as *const u8;
226 let yuv = vld2q_u8(src_block);
227 vst1q_u8(dst_ptr.add(i * 16), yuv.0);
228 }
229 let start = blocks * 16;
230 for x in start..width {
231 *dst_ptr.add(x) = *src_ptr.add(x * 2);
232 }
233 }
234 }
235 }
236 #[cfg(not(target_arch = "aarch64"))]
237 {
238 for y in 0..height {
239 let src_line = &src[y * stride..][..width * 2];
240 let dst_line = &mut dst[y * width..(y + 1) * width];
241 unsafe {
242 let src_ptr = src_line.as_ptr();
243 let dst_ptr = dst_line.as_mut_ptr();
244 for x in 0..width {
245 *dst_ptr.add(x) = *src_ptr.add(x * 2);
246 }
247 }
248 }
249 }
250
251 Ok(FrameMeta::new(
252 MediaFormat::new(
253 self.descriptor.output,
254 meta.format.resolution,
255 meta.format.color,
256 ),
257 meta.timestamp,
258 ))
259 }
260}
261
262impl Codec for YuyvToLumaDecoder {
263 fn descriptor(&self) -> &CodecDescriptor {
264 &self.descriptor
265 }
266
267 fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
268 let layout = plane_layout_from_dims(
269 input.meta().format.resolution.width,
270 input.meta().format.resolution.height,
271 1,
272 );
273 let mut buf = self.pool.lease();
274 unsafe { buf.resize_uninit(layout.len) };
275 let meta = self.decode_into(&input, buf.as_mut_slice())?;
276 Ok(unsafe { FrameLease::single_plane_uninit(meta, buf, layout.len, layout.stride) })
277 }
278}
279
280#[cfg(feature = "image")]
281impl ImageDecode for YuyvToLumaDecoder {
282 fn decode_image(&self, frame: FrameLease) -> Result<image::DynamicImage, CodecError> {
283 process_to_dynamic(self, frame)
284 }
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 fn make_yuyv_frame(bytes: &[u8], res: Resolution, stride: usize) -> FrameLease {
292 let mut buf = BufferPool::with_limits(1, bytes.len(), 1).lease();
293 buf.resize(bytes.len());
294 buf.as_mut_slice().copy_from_slice(bytes);
295 FrameLease::single_plane(
296 FrameMeta::new(MediaFormat::new(FourCc::new(*b"YUYV"), res, ColorSpace::Srgb), 1),
297 buf,
298 bytes.len(),
299 stride,
300 )
301 }
302
303 #[test]
304 fn yuyv_decode_into_matches_process_rgb() {
305 let res = Resolution::new(2, 1).unwrap();
306 let bytes = [10u8, 128, 20, 128];
307 let make = || make_yuyv_frame(&bytes, res, 4);
308 let dec = YuyvToRgbDecoder::new(2, 1);
309
310 let processed = dec.process(make()).unwrap();
311 let plane = processed.planes().into_iter().next().unwrap();
312
313 let mut out = vec![0u8; 2 * 3];
314 let frame = make();
315 dec.decode_into(&frame, &mut out).unwrap();
316 assert_eq!(plane.data(), out.as_slice());
317 }
318
319 #[test]
320 fn yuyv_decode_into_matches_process_luma() {
321 let res = Resolution::new(2, 1).unwrap();
322 let bytes = [10u8, 128, 20, 128];
323 let make = || make_yuyv_frame(&bytes, res, 4);
324 let dec = YuyvToLumaDecoder::new(2, 1);
325
326 let processed = dec.process(make()).unwrap();
327 let plane = processed.planes().into_iter().next().unwrap();
328
329 let mut out = vec![0u8; 2];
330 let frame = make();
331 dec.decode_into(&frame, &mut out).unwrap();
332 assert_eq!(plane.data(), out.as_slice());
333 }
334}