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