mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-06-14 03:32:00 +08:00
138 lines
4.8 KiB
Rust
138 lines
4.8 KiB
Rust
//! Android FFmpeg/MediaCodec MJPEG decoder glue.
|
|
|
|
use hwcodec::ffmpeg::AVPixelFormat;
|
|
use hwcodec::ffmpeg_ram::decode::{DecodeContext, Decoder};
|
|
use tracing::{info, warn};
|
|
|
|
use crate::error::{AppError, Result};
|
|
use crate::video::codec::convert::Nv12Converter;
|
|
use crate::video::format::{PixelFormat, Resolution};
|
|
|
|
pub struct AndroidMediaCodecMjpegDecoder {
|
|
decoder: Decoder,
|
|
resolution: Resolution,
|
|
nv12_converter: Option<Nv12Converter>,
|
|
last_output_format: Option<PixelFormat>,
|
|
pending_frames: u32,
|
|
}
|
|
|
|
impl AndroidMediaCodecMjpegDecoder {
|
|
pub fn new(resolution: Resolution) -> Result<Self> {
|
|
let ctx = DecodeContext {
|
|
name: "mjpeg_mediacodec".to_string(),
|
|
width: resolution.width as i32,
|
|
height: resolution.height as i32,
|
|
sw_pixfmt: AVPixelFormat::AV_PIX_FMT_NV12,
|
|
thread_count: 1,
|
|
};
|
|
let decoder = Decoder::new(ctx).map_err(|_| {
|
|
AppError::VideoError("Failed to create FFmpeg mjpeg_mediacodec decoder".to_string())
|
|
})?;
|
|
Ok(Self {
|
|
decoder,
|
|
resolution,
|
|
nv12_converter: None,
|
|
last_output_format: None,
|
|
pending_frames: 0,
|
|
})
|
|
}
|
|
|
|
pub fn decode_to_nv12(&mut self, mjpeg: &[u8]) -> Result<Vec<u8>> {
|
|
let frames = match self.decoder.decode(mjpeg) {
|
|
Ok(frames) => frames,
|
|
Err(err) if err == -11 => {
|
|
self.pending_frames += 1;
|
|
if self.pending_frames <= 3 {
|
|
return Err(AppError::VideoError(
|
|
"mjpeg_mediacodec decode needs more input".to_string(),
|
|
));
|
|
}
|
|
return Err(AppError::VideoError(
|
|
"mjpeg_mediacodec decoder did not output after 3 frames".to_string(),
|
|
));
|
|
}
|
|
Err(err) => {
|
|
return Err(AppError::VideoError(format!(
|
|
"mjpeg_mediacodec decode failed: {err}"
|
|
)));
|
|
}
|
|
};
|
|
if frames.is_empty() {
|
|
self.pending_frames += 1;
|
|
if self.pending_frames <= 3 {
|
|
return Err(AppError::VideoError(
|
|
"mjpeg_mediacodec decode needs more input".to_string(),
|
|
));
|
|
}
|
|
return Err(AppError::VideoError(
|
|
"mjpeg_mediacodec decoder did not output after 3 frames".to_string(),
|
|
));
|
|
}
|
|
self.pending_frames = 0;
|
|
if frames.len() > 1 {
|
|
warn!(
|
|
"mjpeg_mediacodec decode returned {} frames, using last",
|
|
frames.len()
|
|
);
|
|
}
|
|
|
|
let frame = frames.pop().ok_or_else(|| {
|
|
AppError::VideoError("mjpeg_mediacodec decode returned empty".to_string())
|
|
})?;
|
|
|
|
if frame.width as u32 != self.resolution.width
|
|
|| frame.height as u32 != self.resolution.height
|
|
{
|
|
warn!(
|
|
"mjpeg_mediacodec output size {}x{} differs from expected {}x{}",
|
|
frame.width, frame.height, self.resolution.width, self.resolution.height
|
|
);
|
|
}
|
|
|
|
let output_format = pixel_format_from_av(frame.pixfmt).ok_or_else(|| {
|
|
AppError::VideoError(format!(
|
|
"mjpeg_mediacodec output pixfmt {:?} is not supported",
|
|
frame.pixfmt
|
|
))
|
|
})?;
|
|
|
|
if self.last_output_format != Some(output_format) {
|
|
info!("mjpeg_mediacodec output format: {}", output_format);
|
|
self.last_output_format = Some(output_format);
|
|
}
|
|
|
|
match output_format {
|
|
PixelFormat::Nv12 => Ok(frame.data),
|
|
PixelFormat::Nv21 => {
|
|
let converter = self
|
|
.nv12_converter
|
|
.get_or_insert_with(|| Nv12Converter::nv21_to_nv12(self.resolution));
|
|
Ok(converter.convert(&frame.data)?.to_vec())
|
|
}
|
|
PixelFormat::Yuv420 => {
|
|
let converter = self
|
|
.nv12_converter
|
|
.get_or_insert_with(|| Nv12Converter::yuv420_to_nv12(self.resolution));
|
|
Ok(converter.convert(&frame.data)?.to_vec())
|
|
}
|
|
other => Err(AppError::VideoError(format!(
|
|
"mjpeg_mediacodec output {} cannot be converted to NV12",
|
|
other
|
|
))),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn pixel_format_from_av(format: AVPixelFormat) -> Option<PixelFormat> {
|
|
match format {
|
|
AVPixelFormat::AV_PIX_FMT_NV12 => Some(PixelFormat::Nv12),
|
|
AVPixelFormat::AV_PIX_FMT_NV21 => Some(PixelFormat::Nv21),
|
|
AVPixelFormat::AV_PIX_FMT_YUV420P | AVPixelFormat::AV_PIX_FMT_YUVJ420P => {
|
|
Some(PixelFormat::Yuv420)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for AndroidMediaCodecMjpegDecoder {}
|