use ndk::{
hardware_buffer::{HardwareBuffer, HardwareBufferRef},
media::image_reader::Image,
};
use crate::{DecodedSurface, FrameDimensions, PixelFormat, VideoError};
use super::session::android_error;
pub struct AndroidVideoFrame {
image: Image,
hardware_buffer: HardwareBufferRef,
dimensions: FrameDimensions,
crop_origin: [usize; 2],
timestamp_ns: i64,
native_format: u32,
}
unsafe impl Send for AndroidVideoFrame {}
#[derive(Debug, Clone, Copy)]
pub struct AndroidImagePlane<'a> {
data: &'a [u8],
row_stride: usize,
pixel_stride: usize,
}
impl<'a> AndroidImagePlane<'a> {
pub const fn data(&self) -> &'a [u8] {
self.data
}
pub const fn row_stride(&self) -> usize {
self.row_stride
}
pub const fn pixel_stride(&self) -> usize {
self.pixel_stride
}
}
impl std::fmt::Debug for AndroidVideoFrame {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("AndroidVideoFrame")
.field("dimensions", &self.dimensions)
.field("timestamp_ns", &self.timestamp_ns)
.field("native_format", &self.native_format)
.finish()
}
}
impl AndroidVideoFrame {
pub(crate) fn new(image: Image) -> Result<Self, VideoError> {
let crop = image
.crop_rect()
.map_err(|error| android_error("AImage_getCropRect", error))?;
let fallback_width = image
.width()
.map_err(|error| android_error("AImage_getWidth", error))?;
let fallback_height = image
.height()
.map_err(|error| android_error("AImage_getHeight", error))?;
let crop_width = crop.right.saturating_sub(crop.left);
let crop_height = crop.bottom.saturating_sub(crop.top);
let dimensions = FrameDimensions {
width: u32::try_from(if crop_width > 0 {
crop_width
} else {
fallback_width
})
.map_err(|_| invalid_dimensions())?,
height: u32::try_from(if crop_height > 0 {
crop_height
} else {
fallback_height
})
.map_err(|_| invalid_dimensions())?,
};
let timestamp_ns = image
.timestamp()
.map_err(|error| android_error("AImage_getTimestamp", error))?;
let hardware_buffer = image
.hardware_buffer()
.map_err(|error| android_error("AImage_getHardwareBuffer", error))?
.acquire();
let native_format = i32::from(hardware_buffer.describe().format) as u32;
Ok(Self {
image,
hardware_buffer,
dimensions,
crop_origin: [
usize::try_from(crop.left.max(0)).map_err(|_| invalid_dimensions())?,
usize::try_from(crop.top.max(0)).map_err(|_| invalid_dimensions())?,
],
timestamp_ns,
native_format,
})
}
pub fn hardware_buffer(&self) -> &HardwareBuffer {
&self.hardware_buffer
}
pub fn image(&self) -> &Image {
&self.image
}
pub const fn timestamp_ns(&self) -> i64 {
self.timestamp_ns
}
pub const fn native_format(&self) -> u32 {
self.native_format
}
pub const fn crop_origin(&self) -> [usize; 2] {
self.crop_origin
}
pub fn with_mapped_planes<R>(
&self,
callback: impl FnOnce(&[AndroidImagePlane<'_>]) -> R,
) -> Result<R, VideoError> {
let count = self
.image
.number_of_planes()
.map_err(|error| android_error("AImage_getNumberOfPlanes", error))?;
let mut planes = Vec::with_capacity(usize::try_from(count.max(0)).unwrap_or(0));
for index in 0..count {
let data = self
.image
.plane_data(index)
.map_err(|error| android_error("AImage_getPlaneData", error))?;
let row_stride = self
.image
.plane_row_stride(index)
.map_err(|error| android_error("AImage_getPlaneRowStride", error))?;
let pixel_stride = self
.image
.plane_pixel_stride(index)
.map_err(|error| android_error("AImage_getPlanePixelStride", error))?;
let row_stride = usize::try_from(row_stride).map_err(|_| invalid_plane_layout())?;
let pixel_stride = usize::try_from(pixel_stride).map_err(|_| invalid_plane_layout())?;
if row_stride == 0 || pixel_stride == 0 {
return Err(invalid_plane_layout());
}
planes.push(AndroidImagePlane {
data,
row_stride,
pixel_stride,
});
}
Ok(callback(&planes))
}
}
impl DecodedSurface for AndroidVideoFrame {
fn dimensions(&self) -> FrameDimensions {
self.dimensions
}
fn pixel_format(&self) -> PixelFormat {
PixelFormat::Native(self.native_format)
}
}
fn invalid_dimensions() -> VideoError {
VideoError::Backend {
backend: "mediacodec",
operation: "read output dimensions",
message: "AImage reported negative dimensions".to_owned(),
}
}
fn invalid_plane_layout() -> VideoError {
VideoError::Backend {
backend: "mediacodec",
operation: "map AImage planes",
message: "AImage reported an invalid plane stride".to_owned(),
}
}