#[deprecated = "MediaFormat should be referenced directly from the media_format module"]
pub use super::media_format::MediaFormat;
use crate::media_error::{MediaError, Result};
use crate::native_window::NativeWindow;
use crate::utils::abort_on_panic;
use std::{
ffi::{c_char, c_void, CStr, CString},
fmt,
mem::MaybeUninit,
pin::Pin,
ptr::{self, NonNull},
slice,
time::Duration,
};
#[derive(Debug, PartialEq, Eq)]
pub enum MediaCodecDirection {
Decoder,
Encoder,
}
#[derive(Debug)]
pub struct MediaCodec {
inner: NonNull<ffi::AMediaCodec>,
async_notify_callback: Option<Pin<Box<AsyncNotifyCallback>>>,
}
pub struct AsyncNotifyCallback {
pub on_input_available: Option<InputAvailableCallback>,
pub on_output_available: Option<OutputAvailableCallback>,
pub on_format_changed: Option<FormatChangedCallback>,
pub on_error: Option<ErrorCallback>,
}
impl fmt::Debug for AsyncNotifyCallback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AsyncNotifyCallback")
.field(
"on_input_available",
match &self.on_input_available {
Some(_) => &"Some(_)",
None => &"None",
},
)
.field(
"on_output_available",
match &self.on_output_available {
Some(_) => &"Some(_)",
None => &"None",
},
)
.field(
"on_format_changed",
match &self.on_format_changed {
Some(_) => &"Some(_)",
None => &"None",
},
)
.field(
"on_error",
match &self.on_error {
Some(_) => &"Some(_)",
None => &"None",
},
)
.finish()
}
}
pub type InputAvailableCallback = Box<dyn FnMut(usize) + Send>;
pub type OutputAvailableCallback = Box<dyn FnMut(usize, &BufferInfo) + Send>;
pub type FormatChangedCallback = Box<dyn FnMut(&MediaFormat) + Send>;
pub type ErrorCallback = Box<dyn FnMut(MediaError, ActionCode, &CStr) + Send>;
impl MediaCodec {
fn as_ptr(&self) -> *mut ffi::AMediaCodec {
self.inner.as_ptr()
}
pub fn from_codec_name(name: &str) -> Option<Self> {
let c_string = CString::new(name).unwrap();
Some(Self {
inner: NonNull::new(unsafe { ffi::AMediaCodec_createCodecByName(c_string.as_ptr()) })?,
async_notify_callback: None,
})
}
pub fn from_decoder_type(mime_type: &str) -> Option<Self> {
let c_string = CString::new(mime_type).unwrap();
Some(Self {
inner: NonNull::new(unsafe {
ffi::AMediaCodec_createDecoderByType(c_string.as_ptr())
})?,
async_notify_callback: None,
})
}
pub fn from_encoder_type(mime_type: &str) -> Option<Self> {
let c_string = CString::new(mime_type).unwrap();
Some(Self {
inner: NonNull::new(unsafe {
ffi::AMediaCodec_createEncoderByType(c_string.as_ptr())
})?,
async_notify_callback: None,
})
}
#[cfg(feature = "api-level-28")]
pub fn set_async_notify_callback(
&mut self,
callback: Option<AsyncNotifyCallback>,
) -> Result<()> {
unsafe extern "C" fn ffi_on_input_available(
_codec: *mut ffi::AMediaCodec,
user_data: *mut c_void,
index: i32,
) {
abort_on_panic(|| {
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
if let Some(f) = callback.on_input_available.as_mut() {
f(index as usize);
}
})
}
unsafe extern "C" fn ffi_on_output_available(
_codec: *mut ffi::AMediaCodec,
user_data: *mut c_void,
index: i32,
buffer_info: *mut ffi::AMediaCodecBufferInfo,
) {
abort_on_panic(|| {
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
if let Some(f) = callback.on_output_available.as_mut() {
let buffer_info = BufferInfo {
inner: *buffer_info,
};
f(index as usize, &buffer_info);
}
})
}
unsafe extern "C" fn ffi_on_format_changed(
_codec: *mut ffi::AMediaCodec,
user_data: *mut c_void,
format: *mut ffi::AMediaFormat,
) {
abort_on_panic(|| {
let format = MediaFormat::from_ptr(NonNull::new_unchecked(format));
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
if let Some(f) = callback.on_format_changed.as_mut() {
f(&format);
}
})
}
unsafe extern "C" fn ffi_on_error(
_codec: *mut ffi::AMediaCodec,
user_data: *mut c_void,
error: ffi::media_status_t,
action_code: i32,
detail: *const c_char,
) {
abort_on_panic(|| {
let callback = &mut *(user_data as *mut AsyncNotifyCallback);
if let Some(f) = callback.on_error.as_mut() {
f(
MediaError::from_status(error).unwrap_err(),
ActionCode(action_code),
CStr::from_ptr(detail),
);
}
})
}
let (callback, ffi_callback, user_data) = if let Some(callback) = callback {
let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback {
onAsyncInputAvailable: Some(ffi_on_input_available),
onAsyncOutputAvailable: Some(ffi_on_output_available),
onAsyncFormatChanged: Some(ffi_on_format_changed),
onAsyncError: Some(ffi_on_error),
};
let mut boxed = Box::pin(callback);
let ptr: *mut AsyncNotifyCallback = &mut *boxed;
(Some(boxed), ffi_callback, ptr as *mut c_void)
} else {
let ffi_callback = ffi::AMediaCodecOnAsyncNotifyCallback {
onAsyncInputAvailable: None,
onAsyncOutputAvailable: None,
onAsyncFormatChanged: None,
onAsyncError: None,
};
(None, ffi_callback, ptr::null_mut())
};
let status = unsafe {
ffi::AMediaCodec_setAsyncNotifyCallback(self.as_ptr(), ffi_callback, user_data)
};
let result = MediaError::from_status(status);
if result.is_ok() {
self.async_notify_callback = callback;
} else {
self.async_notify_callback = None;
}
result
}
pub fn configure(
&self,
format: &MediaFormat,
surface: Option<&NativeWindow>,
direction: MediaCodecDirection,
) -> Result<()> {
let status = unsafe {
ffi::AMediaCodec_configure(
self.as_ptr(),
format.as_ptr(),
surface.map_or(ptr::null_mut(), |s| s.ptr().as_ptr()),
ptr::null_mut(),
if direction == MediaCodecDirection::Encoder {
ffi::AMEDIACODEC_CONFIGURE_FLAG_ENCODE as u32
} else {
0
},
)
};
MediaError::from_status(status)
}
#[cfg(feature = "api-level-26")]
pub fn create_input_surface(&self) -> Result<NativeWindow> {
use crate::media_error::construct_never_null;
unsafe {
let ptr = construct_never_null(|res| {
ffi::AMediaCodec_createInputSurface(self.as_ptr(), res)
})?;
Ok(NativeWindow::from_ptr(ptr))
}
}
#[cfg(feature = "api-level-26")]
pub fn create_persistent_input_surface() -> Result<NativeWindow> {
use crate::media_error::construct_never_null;
unsafe {
let ptr =
construct_never_null(|res| ffi::AMediaCodec_createPersistentInputSurface(res))?;
Ok(NativeWindow::from_ptr(ptr))
}
}
pub fn dequeue_input_buffer(&self, timeout: Duration) -> Result<DequeuedInputBufferResult<'_>> {
let result = unsafe {
ffi::AMediaCodec_dequeueInputBuffer(
self.as_ptr(),
timeout
.as_micros()
.try_into()
.expect("Supplied timeout is too large"),
)
};
if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize {
Ok(DequeuedInputBufferResult::TryAgainLater)
} else {
let index = MediaError::from_status_if_negative(result)? as usize;
Ok(DequeuedInputBufferResult::Buffer(InputBuffer {
codec: self,
index,
}))
}
}
pub fn dequeue_output_buffer(
&self,
timeout: Duration,
) -> Result<DequeuedOutputBufferInfoResult<'_>> {
let mut info = MaybeUninit::uninit();
let result = unsafe {
ffi::AMediaCodec_dequeueOutputBuffer(
self.as_ptr(),
info.as_mut_ptr(),
timeout
.as_micros()
.try_into()
.expect("Supplied timeout is too large"),
)
};
if result == ffi::AMEDIACODEC_INFO_TRY_AGAIN_LATER as isize {
Ok(DequeuedOutputBufferInfoResult::TryAgainLater)
} else if result == ffi::AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED as isize {
Ok(DequeuedOutputBufferInfoResult::OutputFormatChanged)
} else if result == ffi::AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED as isize {
Ok(DequeuedOutputBufferInfoResult::OutputBuffersChanged)
} else {
let index = MediaError::from_status_if_negative(result)? as usize;
Ok(DequeuedOutputBufferInfoResult::Buffer(OutputBuffer {
codec: self,
index,
info: BufferInfo {
inner: unsafe { info.assume_init() },
},
}))
}
}
pub fn flush(&self) -> Result<()> {
let status = unsafe { ffi::AMediaCodec_flush(self.as_ptr()) };
MediaError::from_status(status)
}
pub fn input_buffer(&self, index: usize) -> Option<&mut [MaybeUninit<u8>]> {
unsafe {
let mut out_size = 0;
let buffer_ptr = ffi::AMediaCodec_getInputBuffer(self.as_ptr(), index, &mut out_size);
if buffer_ptr.is_null() {
return None;
}
Some(slice::from_raw_parts_mut(buffer_ptr.cast(), out_size))
}
}
pub fn output_buffer(&self, index: usize) -> Option<&[u8]> {
unsafe {
let mut out_size = 0;
let buffer_ptr = ffi::AMediaCodec_getOutputBuffer(self.as_ptr(), index, &mut out_size);
if buffer_ptr.is_null() {
return None;
}
Some(slice::from_raw_parts(buffer_ptr, out_size))
}
}
#[cfg(feature = "api-level-28")]
pub fn input_format(&self) -> MediaFormat {
let inner = NonNull::new(unsafe { ffi::AMediaCodec_getInputFormat(self.as_ptr()) })
.expect("AMediaCodec_getInputFormat returned NULL");
unsafe { MediaFormat::from_ptr(inner) }
}
pub fn output_format(&self) -> MediaFormat {
let inner = NonNull::new(unsafe { ffi::AMediaCodec_getOutputFormat(self.as_ptr()) })
.expect("AMediaCodec_getOutputFormat returned NULL");
unsafe { MediaFormat::from_ptr(inner) }
}
#[cfg(feature = "api-level-28")]
pub fn name(&self) -> Result<String> {
use crate::media_error::construct;
unsafe {
let name_ptr = construct(|name| ffi::AMediaCodec_getName(self.as_ptr(), name))?;
let name = CStr::from_ptr(name_ptr).to_str().unwrap().to_owned();
ffi::AMediaCodec_releaseName(self.as_ptr(), name_ptr);
Ok(name)
}
}
pub fn queue_input_buffer(
&self,
buffer: InputBuffer<'_>,
offset: usize,
size: usize,
time: u64,
flags: u32,
) -> Result<()> {
debug_assert!(ptr::eq(self, buffer.codec));
self.queue_input_buffer_by_index(buffer.index, offset, size, time, flags)
}
pub fn queue_input_buffer_by_index(
&self,
buffer_index: usize,
offset: usize,
size: usize,
time: u64,
flags: u32,
) -> Result<()> {
let status = unsafe {
ffi::AMediaCodec_queueInputBuffer(
self.as_ptr(),
buffer_index,
offset as ffi::off_t,
size,
time,
flags,
)
};
MediaError::from_status(status)
}
pub fn release_output_buffer(&self, buffer: OutputBuffer<'_>, render: bool) -> Result<()> {
debug_assert!(ptr::eq(self, buffer.codec));
self.release_output_buffer_by_index(buffer.index, render)
}
pub fn release_output_buffer_by_index(&self, buffer_index: usize, render: bool) -> Result<()> {
let status =
unsafe { ffi::AMediaCodec_releaseOutputBuffer(self.as_ptr(), buffer_index, render) };
MediaError::from_status(status)
}
pub fn release_output_buffer_at_time(
&self,
buffer: OutputBuffer<'_>,
timestamp_ns: i64,
) -> Result<()> {
debug_assert!(ptr::eq(self, buffer.codec));
self.release_output_buffer_at_time_by_index(buffer.index, timestamp_ns)
}
pub fn release_output_buffer_at_time_by_index(
&self,
buffer_index: usize,
timestamp_ns: i64,
) -> Result<()> {
let status = unsafe {
ffi::AMediaCodec_releaseOutputBufferAtTime(self.as_ptr(), buffer_index, timestamp_ns)
};
MediaError::from_status(status)
}
#[cfg(feature = "api-level-26")]
pub fn set_input_surface(&self, surface: &NativeWindow) -> Result<()> {
let status =
unsafe { ffi::AMediaCodec_setInputSurface(self.as_ptr(), surface.ptr().as_ptr()) };
MediaError::from_status(status)
}
pub fn set_output_surface(&self, surface: &NativeWindow) -> Result<()> {
let status =
unsafe { ffi::AMediaCodec_setOutputSurface(self.as_ptr(), surface.ptr().as_ptr()) };
MediaError::from_status(status)
}
#[cfg(feature = "api-level-26")]
pub fn set_parameters(&self, params: MediaFormat) -> Result<()> {
let status = unsafe { ffi::AMediaCodec_setParameters(self.as_ptr(), params.as_ptr()) };
MediaError::from_status(status)
}
#[cfg(feature = "api-level-26")]
pub fn set_signal_end_of_input_stream(&self) -> Result<()> {
let status = unsafe { ffi::AMediaCodec_signalEndOfInputStream(self.as_ptr()) };
MediaError::from_status(status)
}
pub fn start(&self) -> Result<()> {
let status = unsafe { ffi::AMediaCodec_start(self.as_ptr()) };
MediaError::from_status(status)
}
pub fn stop(&self) -> Result<()> {
let status = unsafe { ffi::AMediaCodec_stop(self.as_ptr()) };
MediaError::from_status(status)
}
}
impl Drop for MediaCodec {
fn drop(&mut self) {
let status = unsafe { ffi::AMediaCodec_delete(self.as_ptr()) };
MediaError::from_status(status).unwrap();
}
}
#[derive(Debug)]
pub struct InputBuffer<'a> {
codec: &'a MediaCodec,
index: usize,
}
impl InputBuffer<'_> {
pub fn buffer_mut(&mut self) -> &mut [MaybeUninit<u8>] {
self.codec.input_buffer(self.index).unwrap_or_else(|| {
panic!(
"AMediaCodec_getInputBuffer returned NULL for index {}",
self.index
)
})
}
}
#[derive(Debug)]
pub enum DequeuedInputBufferResult<'a> {
Buffer(InputBuffer<'a>),
TryAgainLater,
}
#[derive(Debug)]
pub struct OutputBuffer<'a> {
codec: &'a MediaCodec,
index: usize,
info: BufferInfo,
}
impl OutputBuffer<'_> {
pub fn buffer(&self) -> &[u8] {
self.codec.output_buffer(self.index).unwrap_or_else(|| {
panic!(
"AMediaCodec_getOutputBuffer returned NULL for index {}",
self.index
)
})
}
#[cfg(feature = "api-level-28")]
pub fn format(&self) -> MediaFormat {
let inner = NonNull::new(unsafe {
ffi::AMediaCodec_getBufferFormat(self.codec.as_ptr(), self.index)
})
.expect("AMediaCodec_getBufferFormat returned NULL");
unsafe { MediaFormat::from_ptr(inner) }
}
pub fn info(&self) -> &BufferInfo {
&self.info
}
}
#[derive(Debug)]
pub enum DequeuedOutputBufferInfoResult<'a> {
Buffer(OutputBuffer<'a>),
TryAgainLater,
OutputFormatChanged,
OutputBuffersChanged,
}
#[derive(Copy, Clone, Debug)]
pub struct BufferInfo {
inner: ffi::AMediaCodecBufferInfo,
}
impl BufferInfo {
pub fn offset(&self) -> i32 {
self.inner.offset
}
pub fn size(&self) -> i32 {
self.inner.size
}
pub fn presentation_time_us(&self) -> i64 {
self.inner.presentationTimeUs
}
pub fn flags(&self) -> u32 {
self.inner.flags
}
}
#[derive(Copy, Clone, Debug)]
pub struct ActionCode(pub i32);
impl ActionCode {
pub fn is_recoverable(self) -> bool {
unsafe { ffi::AMediaCodecActionCode_isRecoverable(self.0) }
}
pub fn is_transient(self) -> bool {
unsafe { ffi::AMediaCodecActionCode_isTransient(self.0) }
}
}