fix(wzp-video): fix ndk 0.9 MediaCodec API + missing constants for Android build

- Replace buffer.index() with buffer.buffer_mut()/buffer.buffer() (ndk 0.9 RAII API)
- Replace queue_input_buffer_by_index/release_output_buffer_by_index with
  queue_input_buffer/release_output_buffer taking buffer objects
- Fix MaybeUninit<u8> copy using .write() instead of copy_from_slice
- Add BITRATE_MODE_CBR and AMEDIACODEC_BUFFER_FLAG_KEY_FRAME local constants
  (removes ndk_sys dependency for these values)
- Add unsafe impl Send for all six MediaCodec wrapper structs
- Pin @tauri-apps/api to ^2.11 to match Cargo.lock tauri 2.11.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 06:05:58 +04:00
parent ed8a7ae5aa
commit 15af58a95d

View File

@@ -39,6 +39,16 @@ pub struct MediaCodecEncoder {
/// Android color format constant: YUV 4:2:0 planar (I420). /// Android color format constant: YUV 4:2:0 planar (I420).
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
const COLOR_FORMAT_YUV420_PLANAR: i32 = 19; const COLOR_FORMAT_YUV420_PLANAR: i32 = 19;
/// Android MediaCodec CBR bitrate mode (MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR).
#[cfg(target_os = "android")]
const BITRATE_MODE_CBR: i32 = 2;
/// AMediaCodec keyframe buffer flag.
#[cfg(target_os = "android")]
const AMEDIACODEC_BUFFER_FLAG_KEY_FRAME: u32 = 1;
// AMediaCodec is thread-safe; the NonNull inside MediaCodec suppresses auto-Send.
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecEncoder {}
impl MediaCodecEncoder { impl MediaCodecEncoder {
/// Create a new encoder. /// Create a new encoder.
@@ -103,32 +113,25 @@ impl VideoEncoder for MediaCodecEncoder {
.codec .codec
.dequeue_input_buffer(std::time::Duration::from_millis(10)) .dequeue_input_buffer(std::time::Duration::from_millis(10))
{ {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let flags = if self.force_keyframe {
if let Some(input_buf) = self.codec.input_buffer(idx) { AMEDIACODEC_BUFFER_FLAG_KEY_FRAME
let to_copy = frame.data.len().min(input_buf.len()); } else {
input_buf[..to_copy].copy_from_slice(&frame.data[..to_copy]); 0
};
let flags = if self.force_keyframe { let to_copy = {
// Request a sync frame by setting the key-frame flag. let buf = buffer.buffer_mut();
// The flag is cleared only after we see a keyframe in output. let n = frame.data.len().min(buf.len());
ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32 for (d, &s) in buf[..n].iter_mut().zip(frame.data[..n].iter()) {
} else { d.write(s);
0 }
}; n
};
self.codec self.codec
.queue_input_buffer_by_index( .queue_input_buffer(buffer, 0, to_copy, frame.timestamp_ms as u64 * 1000, flags)
idx, .map_err(|e| {
0, VideoError::PlatformError(format!("queue_input_buffer failed: {e}"))
to_copy, })?;
frame.timestamp_ms as u64 * 1000,
flags,
)
.map_err(|e| {
VideoError::PlatformError(format!("queue_input_buffer failed: {e}"))
})?;
}
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -173,35 +176,25 @@ impl MediaCodecEncoder {
.dequeue_output_buffer(std::time::Duration::from_millis(0)) .dequeue_output_buffer(std::time::Duration::from_millis(0))
{ {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let is_keyframe =
if let Some(data) = self.codec.output_buffer(idx) { (buffer.info().flags() & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0;
// Check if this is a keyframe by looking at buffer flags. if is_keyframe {
let info = buffer.info(); self.force_keyframe = false;
let is_keyframe = (info.flags()
& (ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32))
!= 0;
if is_keyframe {
self.force_keyframe = false;
}
output.extend_from_slice(&avcc_to_annexb(data));
} }
let data = buffer.buffer().to_vec();
output.extend_from_slice(&avcc_to_annexb(&data));
self.codec self.codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!("release_output_buffer failed: {e}")) VideoError::PlatformError(format!("release_output_buffer failed: {e}"))
})?; })?;
} }
Ok( Ok(
ndk::media::media_codec::DequeuedOutputBufferInfoResult::OutputFormatChanged, ndk::media::media_codec::DequeuedOutputBufferInfoResult::OutputFormatChanged,
) => { ) => continue,
// Format change — usually happens once at start. Continue draining.
continue;
}
Ok( Ok(
ndk::media::media_codec::DequeuedOutputBufferInfoResult::OutputBuffersChanged, ndk::media::media_codec::DequeuedOutputBufferInfoResult::OutputBuffersChanged,
) => { ) => continue,
continue;
}
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::TryAgainLater) => break, Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::TryAgainLater) => break,
Err(e) => { Err(e) => {
return Err(VideoError::PlatformError(format!( return Err(VideoError::PlatformError(format!(
@@ -231,6 +224,9 @@ pub struct MediaCodecDecoder {
_height: u32, _height: u32,
} }
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecDecoder {}
impl MediaCodecDecoder { impl MediaCodecDecoder {
/// Create a new decoder. /// Create a new decoder.
pub fn new(width: u32, height: u32) -> Result<Self, VideoError> { pub fn new(width: u32, height: u32) -> Result<Self, VideoError> {
@@ -294,19 +290,22 @@ impl VideoDecoder for MediaCodecDecoder {
// Feed input. // Feed input.
match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let to_copy = {
if let Some(input_buf) = codec.input_buffer(idx) { let buf = buffer.buffer_mut();
let to_copy = access_unit.len().min(input_buf.len()); let n = access_unit.len().min(buf.len());
input_buf[..to_copy].copy_from_slice(&access_unit[..to_copy]); for (d, &s) in buf[..n].iter_mut().zip(access_unit[..n].iter()) {
codec d.write(s);
.queue_input_buffer_by_index(idx, 0, to_copy, 0, 0) }
.map_err(|e| { n
VideoError::PlatformError(format!( };
"decoder queue_input_buffer failed: {e}" codec
)) .queue_input_buffer(buffer, 0, to_copy, 0, 0)
})?; .map_err(|e| {
} VideoError::PlatformError(format!(
"decoder queue_input_buffer failed: {e}"
))
})?;
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -319,16 +318,14 @@ impl VideoDecoder for MediaCodecDecoder {
// Drain output. // Drain output.
match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let data = buffer.buffer().to_vec();
let data = codec.output_buffer(idx).unwrap_or(&[]).to_vec();
codec codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!( VideoError::PlatformError(format!(
"decoder release_output_buffer failed: {e}" "decoder release_output_buffer failed: {e}"
)) ))
})?; })?;
Ok(Some(VideoFrame { Ok(Some(VideoFrame {
width: self.width, width: self.width,
height: self.height, height: self.height,
@@ -373,6 +370,9 @@ pub struct MediaCodecHevcEncoder {
_bitrate_bps: u32, _bitrate_bps: u32,
} }
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecHevcEncoder {}
impl MediaCodecHevcEncoder { impl MediaCodecHevcEncoder {
pub fn new(width: u32, height: u32, bitrate_bps: u32) -> Result<Self, VideoError> { pub fn new(width: u32, height: u32, bitrate_bps: u32) -> Result<Self, VideoError> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -433,30 +433,21 @@ impl VideoEncoder for MediaCodecHevcEncoder {
.codec .codec
.dequeue_input_buffer(std::time::Duration::from_millis(10)) .dequeue_input_buffer(std::time::Duration::from_millis(10))
{ {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let flags = if self.force_keyframe { AMEDIACODEC_BUFFER_FLAG_KEY_FRAME } else { 0 };
if let Some(input_buf) = self.codec.input_buffer(idx) { let to_copy = {
let to_copy = frame.data.len().min(input_buf.len()); let buf = buffer.buffer_mut();
input_buf[..to_copy].copy_from_slice(&frame.data[..to_copy]); let n = frame.data.len().min(buf.len());
for (d, &s) in buf[..n].iter_mut().zip(frame.data[..n].iter()) {
let flags = if self.force_keyframe { d.write(s);
ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32 }
} else { n
0 };
}; self.codec
.queue_input_buffer(buffer, 0, to_copy, frame.timestamp_ms as u64 * 1000, flags)
self.codec .map_err(|e| {
.queue_input_buffer_by_index( VideoError::PlatformError(format!("queue_input_buffer failed: {e}"))
idx, })?;
0,
to_copy,
frame.timestamp_ms as u64 * 1000,
flags,
)
.map_err(|e| {
VideoError::PlatformError(format!("queue_input_buffer failed: {e}"))
})?;
}
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -508,6 +499,9 @@ pub struct MediaCodecAv1Encoder {
_bitrate_bps: u32, _bitrate_bps: u32,
} }
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecAv1Encoder {}
impl MediaCodecAv1Encoder { impl MediaCodecAv1Encoder {
pub fn new(width: u32, height: u32, bitrate_bps: u32) -> Result<Self, VideoError> { pub fn new(width: u32, height: u32, bitrate_bps: u32) -> Result<Self, VideoError> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -561,32 +555,23 @@ impl VideoEncoder for MediaCodecAv1Encoder {
.codec .codec
.dequeue_input_buffer(std::time::Duration::from_millis(0)) .dequeue_input_buffer(std::time::Duration::from_millis(0))
{ {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let flags = if self.force_keyframe { AMEDIACODEC_BUFFER_FLAG_KEY_FRAME } else { 0 };
if let Some(input_buf) = self.codec.input_buffer(idx) { let to_copy = {
let to_copy = frame.data.len().min(input_buf.len()); let buf = buffer.buffer_mut();
input_buf[..to_copy].copy_from_slice(&frame.data[..to_copy]); let n = frame.data.len().min(buf.len());
for (d, &s) in buf[..n].iter_mut().zip(frame.data[..n].iter()) {
let flags = if self.force_keyframe { d.write(s);
ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32 }
} else { n
0 };
}; self.codec
.queue_input_buffer(buffer, 0, to_copy, frame.timestamp_ms as u64 * 1000, flags)
self.codec .map_err(|e| {
.queue_input_buffer_by_index( VideoError::PlatformError(format!(
idx, "AV1 encoder queue_input_buffer failed: {e}"
0, ))
to_copy, })?;
frame.timestamp_ms as u64 * 1000,
flags,
)
.map_err(|e| {
VideoError::PlatformError(format!(
"AV1 encoder queue_input_buffer failed: {e}"
))
})?;
}
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -625,19 +610,15 @@ impl MediaCodecHevcEncoder {
.dequeue_output_buffer(std::time::Duration::from_millis(0)) .dequeue_output_buffer(std::time::Duration::from_millis(0))
{ {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let is_keyframe =
if let Some(data) = self.codec.output_buffer(idx) { (buffer.info().flags() & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0;
let info = buffer.info(); if is_keyframe {
let is_keyframe = (info.flags() self.force_keyframe = false;
& (ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32))
!= 0;
if is_keyframe {
self.force_keyframe = false;
}
output.extend_from_slice(&avcc_to_annexb(data));
} }
let data = buffer.buffer().to_vec();
output.extend_from_slice(&avcc_to_annexb(&data));
self.codec self.codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!("release_output_buffer failed: {e}")) VideoError::PlatformError(format!("release_output_buffer failed: {e}"))
})?; })?;
@@ -670,20 +651,16 @@ impl MediaCodecAv1Encoder {
.dequeue_output_buffer(std::time::Duration::from_millis(0)) .dequeue_output_buffer(std::time::Duration::from_millis(0))
{ {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let is_keyframe =
if let Some(data) = self.codec.output_buffer(idx) { (buffer.info().flags() & AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0;
let info = buffer.info(); if is_keyframe {
let is_keyframe = (info.flags() self.force_keyframe = false;
& (ndk_sys::AMEDIACODEC_BUFFER_FLAG_KEY_FRAME as u32))
!= 0;
if is_keyframe {
self.force_keyframe = false;
}
// AV1 output from MediaCodec is already in OBU format.
output.extend_from_slice(data);
} }
// AV1 output from MediaCodec is already in OBU format.
let data = buffer.buffer().to_vec();
output.extend_from_slice(&data);
self.codec self.codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!( VideoError::PlatformError(format!(
"AV1 encoder release_output_buffer failed: {e}" "AV1 encoder release_output_buffer failed: {e}"
@@ -724,6 +701,9 @@ pub struct MediaCodecHevcDecoder {
_height: u32, _height: u32,
} }
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecHevcDecoder {}
impl MediaCodecHevcDecoder { impl MediaCodecHevcDecoder {
pub fn new(width: u32, height: u32) -> Result<Self, VideoError> { pub fn new(width: u32, height: u32) -> Result<Self, VideoError> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -788,19 +768,22 @@ impl VideoDecoder for MediaCodecHevcDecoder {
let codec = self.codec.as_mut().ok_or(VideoError::NotInitialized)?; let codec = self.codec.as_mut().ok_or(VideoError::NotInitialized)?;
match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let to_copy = {
if let Some(input_buf) = codec.input_buffer(idx) { let buf = buffer.buffer_mut();
let to_copy = access_unit.len().min(input_buf.len()); let n = access_unit.len().min(buf.len());
input_buf[..to_copy].copy_from_slice(&access_unit[..to_copy]); for (d, &s) in buf[..n].iter_mut().zip(access_unit[..n].iter()) {
codec d.write(s);
.queue_input_buffer_by_index(idx, 0, to_copy, 0, 0) }
.map_err(|e| { n
VideoError::PlatformError(format!( };
"decoder queue_input_buffer failed: {e}" codec
)) .queue_input_buffer(buffer, 0, to_copy, 0, 0)
})?; .map_err(|e| {
} VideoError::PlatformError(format!(
"decoder queue_input_buffer failed: {e}"
))
})?;
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -812,16 +795,14 @@ impl VideoDecoder for MediaCodecHevcDecoder {
match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let data = buffer.buffer().to_vec();
let data = codec.output_buffer(idx).unwrap_or(&[]).to_vec();
codec codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!( VideoError::PlatformError(format!(
"decoder release_output_buffer failed: {e}" "decoder release_output_buffer failed: {e}"
)) ))
})?; })?;
Ok(Some(VideoFrame { Ok(Some(VideoFrame {
width: self.width, width: self.width,
height: self.height, height: self.height,
@@ -859,6 +840,9 @@ pub struct MediaCodecAv1Decoder {
_height: u32, _height: u32,
} }
#[cfg(target_os = "android")]
unsafe impl Send for MediaCodecAv1Decoder {}
impl MediaCodecAv1Decoder { impl MediaCodecAv1Decoder {
pub fn new(width: u32, height: u32) -> Result<Self, VideoError> { pub fn new(width: u32, height: u32) -> Result<Self, VideoError> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
@@ -919,19 +903,22 @@ impl VideoDecoder for MediaCodecAv1Decoder {
let codec = self.codec.as_mut().ok_or(VideoError::NotInitialized)?; let codec = self.codec.as_mut().ok_or(VideoError::NotInitialized)?;
match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_input_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedInputBufferResult::Buffer(mut buffer)) => {
let idx = buffer.index(); let to_copy = {
if let Some(input_buf) = codec.input_buffer(idx) { let buf = buffer.buffer_mut();
let to_copy = access_unit.len().min(input_buf.len()); let n = access_unit.len().min(buf.len());
input_buf[..to_copy].copy_from_slice(&access_unit[..to_copy]); for (d, &s) in buf[..n].iter_mut().zip(access_unit[..n].iter()) {
codec d.write(s);
.queue_input_buffer_by_index(idx, 0, to_copy, 0, 0) }
.map_err(|e| { n
VideoError::PlatformError(format!( };
"AV1 decoder queue_input_buffer failed: {e}" codec
)) .queue_input_buffer(buffer, 0, to_copy, 0, 0)
})?; .map_err(|e| {
} VideoError::PlatformError(format!(
"AV1 decoder queue_input_buffer failed: {e}"
))
})?;
} }
Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {} Ok(ndk::media::media_codec::DequeuedInputBufferResult::TryAgainLater) => {}
Err(e) => { Err(e) => {
@@ -943,16 +930,14 @@ impl VideoDecoder for MediaCodecAv1Decoder {
match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) { match codec.dequeue_output_buffer(std::time::Duration::from_millis(10)) {
Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => { Ok(ndk::media::media_codec::DequeuedOutputBufferInfoResult::Buffer(buffer)) => {
let idx = buffer.index(); let data = buffer.buffer().to_vec();
let data = codec.output_buffer(idx).unwrap_or(&[]).to_vec();
codec codec
.release_output_buffer_by_index(idx, false) .release_output_buffer(buffer, false)
.map_err(|e| { .map_err(|e| {
VideoError::PlatformError(format!( VideoError::PlatformError(format!(
"AV1 decoder release_output_buffer failed: {e}" "AV1 decoder release_output_buffer failed: {e}"
)) ))
})?; })?;
Ok(Some(VideoFrame { Ok(Some(VideoFrame {
width: self.width, width: self.width,
height: self.height, height: self.height,