├── .gitignore ├── src ├── crypto.rs ├── samples.rs ├── error.rs ├── lib.rs ├── extractor.rs ├── native_window.rs ├── muxer.rs ├── format.rs └── codec.rs ├── run_android.bat ├── TODO.md ├── examples ├── demuxing.rs └── decoding.rs ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /src/crypto.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | pub struct AMediaCrypto { 3 | _data: [u8; 0], 4 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 5 | } 6 | -------------------------------------------------------------------------------- /run_android.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | title Android Runner 4 | echo Compiling and Running Project... 5 | 6 | cargo apk run 7 | 8 | if %errorlevel% neq 0 exit /b %errorlevel% 9 | 10 | @REM rem Clear the screen once compilation is done 11 | cls 12 | 13 | timeout 2 > nul 14 | 15 | echo Opening Logcat... 16 | 17 | rem Get the PID 18 | FOR /F %%i IN ('adb shell pidof rust.mediacodec') DO set pid=%%i 19 | 20 | echo PID: %pid% 21 | 22 | adb logcat --pid=%pid% 23 | 24 | @REM adb 25 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # Documenting things to do 2 | 3 | ## TODOs 4 | - [x] Write proper examples 5 | - [ ] Implement returning actual buffers for raw video samples returned by the codec. So far, the decoder can only return hardware buffer samples. 6 | - [x] Add a script to automate running adb logcat with the correct PID 7 | - [x] Write Documentation 8 | - [x] Implement MediaMuxer bindings (Since there's already MediaExtractor, it's only fitting that I implement MediaMuxer too) 9 | - [ ] Implement ImageReader. This one will be interesting, because there are plenty of use cases for why I might want to really implement it. However, I don't need it at the moment so I'll see where this goes 10 | - [x] Implement Debug for the appropriate types -------------------------------------------------------------------------------- /examples/demuxing.rs: -------------------------------------------------------------------------------- 1 | use log::debug; 2 | use mediacodec::{Frame, MediaExtractor, SampleFormat, VideoFrame}; 3 | 4 | #[no_mangle] 5 | extern "C" fn process() { 6 | let mut extractor = MediaExtractor::from_url("/path/to/a/resource").unwrap(); 7 | 8 | debug!("Track count: {}", extractor.track_count()); 9 | 10 | for i in 0..extractor.track_count() { 11 | let format = extractor.track_format(i).unwrap(); 12 | debug!("{}", format.to_string()); 13 | let mime_type = format.get_string("mime").unwrap(); 14 | extractor.select_track(i); 15 | } 16 | 17 | while extractor.has_next() { 18 | // 1. Get the track index 19 | let index = extractor.track_index(); 20 | 21 | if index < 0 { 22 | break; 23 | } 24 | 25 | // Get a codec buffer and read data into it 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mediacodec" 3 | version = "0.1.2" 4 | edition = "2021" 5 | readme = "README.md" 6 | authors = [ 7 | "Adebayo Jagunmolu " 8 | ] 9 | description = "Rust bindings to MediaCodec, with an easy-to-use API" 10 | repository = "https://github.com/bayo-code/rust_mediacodec" 11 | license = "MIT" 12 | keywords = ["ffi", "bindings", "multimedia", "codec", "android"] 13 | categories = ["api-bindings", "encoding", "multimedia", "multimedia::audio", "multimedia::video"] 14 | 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [lib] 19 | name = "mediacodec" 20 | crate-type = ["cdylib"] 21 | 22 | [[example]] 23 | name = "demuxing" 24 | crate-type = ["cdylib"] 25 | 26 | [[example]] 27 | name = "decoding" 28 | crate-type = ["cdylib"] 29 | 30 | [target.'cfg(target_os = "android")'.dependencies] 31 | log = "0.4.14" 32 | android_log = "0.1.3" 33 | jni = "0.19.0" 34 | javavm = "0.1.2" 35 | # samplerate = "0.2.4" 36 | # image = "0.23.14" 37 | # palette = "0.6.0" 38 | # qrcode-generator = "4.1.2" 39 | # resize = "0.7.2" 40 | 41 | [features] 42 | api24 = [] 43 | api26 = ["api24"] 44 | api28 = ["api26"] 45 | api29 = ["api28"] 46 | 47 | # For cargo apk to test the library 48 | # [package.metadata.android.sdk] 49 | # min_sdk_version = 21 50 | # target_sdk_version = 31 51 | 52 | # [package.metadata.android] 53 | # build_targets = [ "armv7-linux-androideabi" ] 54 | 55 | # [[package.metadata.android.uses_permission]] 56 | # name = "android.permission.READ_EXTERNAL_STORAGE" 57 | 58 | # [[package.metadata.android.uses_permission]] 59 | # name = "android.permission.INTERNET" 60 | 61 | # [target.'cfg(target_os = "android")'.dependencies] 62 | # ndk-glue = { version = "*", features = ["logger"] } 63 | 64 | [package.metadata.docs.rs] 65 | features = ["api28"] 66 | targets = [ 67 | "aarch64-linux-android", 68 | "armv7-linux-androideabi", 69 | "i686-linux-android", 70 | "x86_64-linux-android", 71 | ] 72 | -------------------------------------------------------------------------------- /src/samples.rs: -------------------------------------------------------------------------------- 1 | /// Represents a codec frame (either audio or video) 2 | #[derive(Debug)] 3 | pub enum Frame<'a> { 4 | Audio(AudioFrame<'a>), 5 | Video(VideoFrame<'a>), 6 | } 7 | 8 | pub const ENCODING_PCM_16BIT: usize = 2; 9 | pub const ENCODING_PCM_FLOAT: usize = 4; 10 | 11 | /// Represents an audio sample format, and contains the samples buffer 12 | #[derive(Debug)] 13 | pub enum SampleFormat<'a> { 14 | S16(&'a [i16]), 15 | F32(&'a [f32]), 16 | } 17 | 18 | impl SampleFormat<'_> { 19 | /// Returns the number of samples contained by this format 20 | fn samples(&self, channels: u32) -> usize { 21 | match self { 22 | SampleFormat::S16(value) => value.len() / channels as usize, 23 | SampleFormat::F32(value) => value.len() / channels as usize, 24 | } 25 | } 26 | 27 | /// Returns the size of one sample represented by this format 28 | fn sample_size(&self) -> usize { 29 | match self { 30 | SampleFormat::S16(_) => std::mem::size_of::(), 31 | SampleFormat::F32(_) => std::mem::size_of::(), 32 | } 33 | } 34 | 35 | /// Returns the size of one frame represented by this format. It needs the number of channels stored in this buffer to determine the value 36 | fn frame_size(&self, channels: u32) -> usize { 37 | self.sample_size() * channels as usize 38 | } 39 | } 40 | 41 | /// Represents an audio frame, with sample format and channels 42 | #[derive(Debug)] 43 | pub struct AudioFrame<'a> { 44 | format: SampleFormat<'a>, 45 | channels: u32, 46 | } 47 | 48 | impl<'a> AudioFrame<'a> { 49 | /// Create the audio frame 50 | pub fn new(format: SampleFormat<'a>, channels: u32) -> Self { 51 | Self { format, channels } 52 | } 53 | 54 | /// Returns the number of channels for this frame 55 | pub fn channels(&self) -> u32 { 56 | self.channels 57 | } 58 | 59 | /// Returns the sample format for this frame 60 | pub fn format(&self) -> &SampleFormat { 61 | &self.format 62 | } 63 | 64 | /// Returns the number of samples contained in this frame 65 | pub fn nb_samples(&self) -> usize { 66 | self.format.samples(self.channels) 67 | } 68 | } 69 | 70 | #[derive(Debug)] 71 | pub enum VideoFrame<'a> { 72 | /// Represens a hardware video frame (stored in a NativeWindow, so it cannot be accessed) 73 | /// 74 | /// Can't do much with this, it's just a marker. The underlying `CodecOutputBuffer` will take care of it 75 | Hardware, 76 | /// Represents a raw video frame, with a specific pixel format and a buffer to read the data 77 | RawFrame(RawVideoFrame<'a>), 78 | } 79 | 80 | /// A raw video frame with pixel format and a byte buffer to read the data 81 | #[derive(Debug)] 82 | pub struct RawVideoFrame<'a> { 83 | buffer: &'a [u8], 84 | } 85 | -------------------------------------------------------------------------------- /examples/decoding.rs: -------------------------------------------------------------------------------- 1 | use log::debug; 2 | use mediacodec::{Frame, MediaCodec, MediaExtractor, SampleFormat, VideoFrame}; 3 | 4 | #[no_mangle] 5 | extern "C" fn process() { 6 | let mut extractor = MediaExtractor::from_url("/path/to/a/resource").unwrap(); 7 | 8 | debug!("Track count: {}", extractor.track_count()); 9 | 10 | let mut decoders = vec![]; 11 | 12 | for i in 0..extractor.track_count() { 13 | let format = extractor.track_format(i).unwrap(); 14 | debug!("{}", format.to_string()); 15 | let mime_type = format.get_string("mime").unwrap(); 16 | let mut codec = MediaCodec::create_decoder(&mime_type).unwrap(); 17 | 18 | codec.init(&format, None, 0).unwrap(); 19 | 20 | codec.start().unwrap(); 21 | decoders.push(codec); 22 | extractor.select_track(i); 23 | } 24 | 25 | while extractor.has_next() { 26 | // 1. Get the track index 27 | let index = extractor.track_index(); 28 | 29 | if index < 0 { 30 | break; 31 | } 32 | 33 | let codec = &mut decoders[index as usize]; 34 | 35 | // Fetch the codec's input buffer 36 | while let Ok(mut buffer) = codec.dequeue_input() { 37 | if !extractor.read_next(&mut buffer) { 38 | debug!( 39 | "MediaExtractor.read_next() returned false! has_next(): {}", 40 | extractor.has_next() 41 | ); 42 | break; // VERY IMPORTANT, there's nothing else to DO, so break!!! 43 | } 44 | 45 | // When the buffer gets dropped (here), the buffer will be queued back to MediaCodec 46 | // And we don't have to do anything else 47 | } 48 | 49 | // Check for output 50 | let output_fmt = codec.output_format().unwrap(); 51 | while let Ok(mut buffer) = codec.dequeue_output() { 52 | if let Some(ref frame) = buffer.frame() { 53 | match frame { 54 | Frame::Audio(value) => match value.format() { 55 | SampleFormat::S16(_) => { 56 | // Do something with the audio frame 57 | } 58 | SampleFormat::F32(_) => { 59 | // Do something with the audio frame 60 | } 61 | }, 62 | Frame::Video(value) => match value { 63 | VideoFrame::Hardware => { 64 | // Nothing TODO. The frame will be rendered 65 | } 66 | VideoFrame::RawFrame(_) => { 67 | // Read out the raw buffers or something 68 | } 69 | }, 70 | } 71 | } 72 | 73 | // Set the buffer to render when dropped. Only applicable to video codecs that have a hardware buffer (i.e, attached to a native window) 74 | buffer.set_render(true); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust MediaCodec 2 | This library provides Rust bindings to the Android MediaCodec APIs. It also adds some pretty nifty utilities to make working with buffers on Android easier. 3 | 4 | ## Features Currently Implemented 5 | - [x] MediaCodec 6 | - [x] MediaExtractor 7 | - [x] MediaMuxer 8 | - [x] MediaFormat 9 | - [x] Safe codec buffers abstraction 10 | - [x] Some extra utilities to make working with the library easier 11 | 12 | Some Decoding example: 13 | 14 | ```rust 15 | use log::debug; 16 | use mediacodec::{Frame, MediaCodec, MediaExtractor, SampleFormat, VideoFrame}; 17 | 18 | #[no_mangle] 19 | extern "C" fn process() { 20 | let mut extractor = MediaExtractor::from_url("/path/to/a/resource").unwrap(); 21 | 22 | debug!("Track count: {}", extractor.track_count()); 23 | 24 | let mut decoders = vec![]; 25 | 26 | for i in 0..extractor.track_count() { 27 | let format = extractor.track_format(i).unwrap(); 28 | debug!("{}", format.to_string()); 29 | let mime_type = format.get_string("mime").unwrap(); 30 | let mut codec = MediaCodec::create_decoder(&mime_type).unwrap(); 31 | 32 | codec.init(&format, None, 0).unwrap(); 33 | 34 | codec.start().unwrap(); 35 | decoders.push(codec); 36 | extractor.select_track(i); 37 | } 38 | 39 | while extractor.has_next() { 40 | // 1. Get the track index 41 | let index = extractor.track_index(); 42 | 43 | if index < 0 { 44 | break; 45 | } 46 | 47 | let codec = &mut decoders[index as usize]; 48 | 49 | // Fetch the codec's input buffer 50 | while let Ok(mut buffer) = codec.dequeue_input() { 51 | if !extractor.read_next(&mut buffer) { 52 | debug!( 53 | "MediaExtractor.read_next() returned false! has_next(): {}", 54 | extractor.has_next() 55 | ); 56 | break; // VERY IMPORTANT, there's nothing else to DO, so break!!! 57 | } 58 | 59 | // When the buffer gets dropped (here), the buffer will be queued back to MediaCodec 60 | // And we don't have to do anything else 61 | } 62 | 63 | // Check for output 64 | let output_fmt = codec.output_format().unwrap(); 65 | while let Ok(mut buffer) = codec.dequeue_output() { 66 | if let Some(ref frame) = buffer.frame() { 67 | match frame { 68 | Frame::Audio(value) => match value.format() { 69 | SampleFormat::S16(_) => { 70 | // Do something with the audio frame 71 | } 72 | SampleFormat::F32(_) => { 73 | // Do something with the audio frame 74 | } 75 | }, 76 | Frame::Video(value) => match value { 77 | VideoFrame::Hardware => { 78 | // Nothing TODO. The frame will be rendered 79 | } 80 | VideoFrame::RawFrame(_) => { 81 | // Read out the raw buffers or something 82 | } 83 | }, 84 | } 85 | } 86 | 87 | // Set the buffer to render when dropped. Only applicable to video codecs that have a hardware buffer (i.e, attached to a native window) 88 | buffer.set_render(true); 89 | } 90 | } 91 | } 92 | ``` 93 | 94 | You can find some more examples in the **examples** directory. -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | #[repr(C)] 2 | #[derive(PartialEq, Debug, Clone, Copy)] 3 | pub enum MediaStatus { 4 | Ok = 0, 5 | ErrorInsufficientResource = 1100, 6 | ErrorReclaimed = 1101, 7 | ErrorUnknown = -10000, 8 | ErrorMalformed = crate::MediaStatus::ErrorUnknown as isize - 1, 9 | ErrorUnsupported = crate::MediaStatus::ErrorUnknown as isize - 2, 10 | ErrorInvalidObject = crate::MediaStatus::ErrorUnknown as isize - 3, 11 | ErrorInvalidParameter = crate::MediaStatus::ErrorUnknown as isize - 4, 12 | ErrorInvalidOperation = crate::MediaStatus::ErrorUnknown as isize - 5, 13 | ErrorEndOfStream = crate::MediaStatus::ErrorUnknown as isize - 6, 14 | ErrorIO = crate::MediaStatus::ErrorUnknown as isize - 7, 15 | ErrorWouldBlock = crate::MediaStatus::ErrorUnknown as isize - 8, 16 | DRMErrorBase = -20000, 17 | DRMNotProvisioned = crate::MediaStatus::DRMErrorBase as isize - 1, 18 | DRMResourceBusy = crate::MediaStatus::DRMErrorBase as isize - 2, 19 | DRMDeviceRevoked = crate::MediaStatus::DRMErrorBase as isize - 3, 20 | DRMShortBuffer = crate::MediaStatus::DRMErrorBase as isize - 4, 21 | DRMSessionNotOpened = crate::MediaStatus::DRMErrorBase as isize - 5, 22 | DRMTamperDetected = crate::MediaStatus::DRMErrorBase as isize - 6, 23 | DRMVerifyFailed = crate::MediaStatus::DRMErrorBase as isize - 7, 24 | DRMNeedKey = crate::MediaStatus::DRMErrorBase as isize - 8, 25 | DRMLicenseExpired = crate::MediaStatus::DRMErrorBase as isize - 9, 26 | ImgReaderErrorBase = -30000, 27 | ImgReaderNoBufferAvailable = crate::MediaStatus::ImgReaderErrorBase as isize - 1, 28 | ImgReaderMaxImagesAcquired = crate::MediaStatus::ImgReaderErrorBase as isize - 2, 29 | ImgReaderCannotLockImage = crate::MediaStatus::ImgReaderErrorBase as isize - 3, 30 | ImgReaderCannotUnlockImage = crate::MediaStatus::ImgReaderErrorBase as isize - 4, 31 | ImgReaderImageNotLocked = crate::MediaStatus::ImgReaderErrorBase as isize - 5, 32 | } 33 | 34 | impl MediaStatus { 35 | fn values() -> Vec { 36 | use crate::MediaStatus::*; 37 | vec![ 38 | Ok, 39 | ErrorInsufficientResource, 40 | ErrorReclaimed, 41 | ErrorUnknown, 42 | ErrorMalformed, 43 | ErrorUnsupported, 44 | ErrorInvalidObject, 45 | ErrorInvalidParameter, 46 | ErrorInvalidOperation, 47 | ErrorEndOfStream, 48 | ErrorIO, 49 | ErrorWouldBlock, 50 | DRMErrorBase, 51 | DRMNotProvisioned, 52 | DRMResourceBusy, 53 | DRMDeviceRevoked, 54 | DRMShortBuffer, 55 | DRMSessionNotOpened, 56 | DRMTamperDetected, 57 | DRMVerifyFailed, 58 | DRMNeedKey, 59 | DRMLicenseExpired, 60 | ImgReaderErrorBase, 61 | ImgReaderNoBufferAvailable, 62 | ImgReaderMaxImagesAcquired, 63 | ImgReaderCannotLockImage, 64 | ImgReaderCannotUnlockImage, 65 | ImgReaderImageNotLocked, 66 | ] 67 | } 68 | // Makes a result based on `value`. Ok is returned with the value if the value is not one of MediaStatus error codes 69 | pub fn make_result(value: isize) -> Result { 70 | let status: Result = value.try_into(); 71 | if let Ok(status) = status { 72 | if status.is_ok() { 73 | return Ok(status as isize); 74 | } else { 75 | return Err(status); 76 | } 77 | } 78 | 79 | // If we could not convert to a MediaStatus, we got some other value that's most likely not an error, so we return Ok 80 | Ok(value) 81 | } 82 | 83 | pub fn result(&self) -> Result { 84 | if self.is_ok() { 85 | return Ok(*self as isize); 86 | } 87 | 88 | Err(*self) 89 | } 90 | 91 | fn is_ok(&self) -> bool { 92 | let mut valuez = Self::values(); 93 | // Remove the Ok. Now, the rest are errors 94 | valuez.remove(0); 95 | 96 | // If we get none, there were no errors 97 | return valuez.iter().find(|&&x| *self == x).is_none(); 98 | } 99 | 100 | fn is_err(&self) -> bool { 101 | return !self.is_ok(); 102 | } 103 | } 104 | 105 | impl TryFrom for MediaStatus { 106 | type Error = &'static str; 107 | 108 | fn try_from(value: isize) -> Result { 109 | for item in Self::values() { 110 | if item as isize == value { 111 | return Ok(item); 112 | } 113 | } 114 | 115 | return Err("Not Found"); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides bindings to the MediaCodec APIs in the Android NDK. 2 | //! 3 | //! Examples: 4 | //! ### Decoding 5 | //! ```edition2021 6 | //! 7 | //! # #[no_mangle] 8 | //! # extern "C" fn process() { 9 | //! use mediacodec::{Frame, MediaCodec, MediaExtractor, SampleFormat, VideoFrame}; 10 | //! let mut extractor = MediaExtractor::from_url("/path/to/a/resource").unwrap(); 11 | 12 | //! debug!("Track count: {}", extractor.track_count()); 13 | 14 | //! let mut decoders = vec![]; 15 | 16 | //! for i in 0..extractor.track_count() { 17 | //! let format = extractor.track_format(i).unwrap(); 18 | //! debug!("{}", format.to_string()); 19 | //! let mime_type = format.get_string("mime").unwrap(); 20 | //! let mut codec = MediaCodec::create_decoder(&mime_type).unwrap(); 21 | 22 | //! codec.init(&format, None, 0).unwrap(); 23 | 24 | //! codec.start().unwrap(); 25 | //! decoders.push(codec); 26 | //! extractor.select_track(i); 27 | //! } 28 | 29 | //! while extractor.has_next() { 30 | //! // 1. Get the track index 31 | //! let index = extractor.track_index(); 32 | //! 33 | //! if index < 0 { 34 | //! break; 35 | //! } 36 | //! 37 | //! let codec = &mut decoders[index as usize]; 38 | //! 39 | //! // Fetch the codec's input buffer 40 | //! while let Ok(mut buffer) = codec.dequeue_input() { 41 | //! if !extractor.read_next(&mut buffer) { 42 | //! debug!( 43 | //! "MediaExtractor.read_next() returned false! has_next(): {}", 44 | //! extractor.has_next() 45 | //! ); 46 | //! break; // VERY IMPORTANT, there's nothing else to DO, so break!!! 47 | //! } 48 | //! 49 | //! // When the buffer gets dropped (here), the buffer will be queued back to MediaCodec 50 | //! // And we don't have to do anything else 51 | //! } 52 | //! 53 | //! // Check for output 54 | //! let output_fmt = codec.output_format().unwrap(); 55 | //! while let Ok(mut buffer) = codec.dequeue_output() { 56 | //! if let Some(ref frame) = buffer.frame() { 57 | //! match frame { 58 | //! Frame::Audio(value) => match value.format() { 59 | //! SampleFormat::S16(_) => { 60 | //! // Do something with the audio frame 61 | //! } 62 | //! SampleFormat::F32(_) => { 63 | //! // Do something with the audio frame 64 | //! } 65 | //! }, 66 | //! Frame::Video(value) => match value { 67 | //! VideoFrame::Hardware => { 68 | //! // Nothing TODO. The frame will be rendered 69 | //! } 70 | //! VideoFrame::RawFrame(_) => { 71 | //! // Read out the raw buffers or something 72 | //! } 73 | //! }, 74 | //! } 75 | //! } 76 | //! 77 | //! // Set the buffer to render when dropped. Only applicable to video codecs that have a hardware buffer (i.e, attached to a native window) 78 | //! buffer.set_render(true); 79 | //! } 80 | //! } 81 | //! # } 82 | //! ``` 83 | //! 84 | //! ### Demuxing 85 | //! ```edition2021 86 | //! use log::debug; 87 | //! use mediacodec::{Frame, MediaExtractor, SampleFormat, VideoFrame}; 88 | //! 89 | //! # #[no_mangle] 90 | //! # extern "C" fn process() { 91 | //! let mut extractor = MediaExtractor::from_url("/path/to/a/resource").unwrap(); 92 | //! 93 | //! debug!("Track count: {}", extractor.track_count()); 94 | //! 95 | //! for i in 0..extractor.track_count() { 96 | //! let format = extractor.track_format(i).unwrap(); 97 | //! debug!("{}", format.to_string()); 98 | //! let mime_type = format.get_string("mime").unwrap(); 99 | //! extractor.select_track(i); 100 | //! } 101 | //! 102 | //! while extractor.has_next() { 103 | //! // 1. Get the track index 104 | //! let index = extractor.track_index(); 105 | //! 106 | //! if index < 0 { 107 | //! break; 108 | //! } 109 | //! 110 | //! // Get a codec buffer and read data into it 111 | //! } 112 | //! # } 113 | 114 | //! ``` 115 | // #![cfg(os = "android")] 116 | 117 | mod codec; 118 | mod crypto; 119 | mod error; 120 | mod extractor; 121 | mod format; 122 | mod muxer; 123 | mod native_window; 124 | mod samples; 125 | 126 | pub use codec::*; 127 | pub use crypto::*; 128 | pub use error::*; 129 | pub use extractor::*; 130 | pub use format::*; 131 | pub use muxer::*; 132 | pub use native_window::*; 133 | pub use samples::*; 134 | -------------------------------------------------------------------------------- /src/extractor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{CStr, CString}, 3 | marker::PhantomData, 4 | os::raw::c_char, 5 | }; 6 | 7 | use log::{debug, info}; 8 | 9 | use crate::{AMediaFormat, CodecInputBuffer, MediaFormat, MediaStatus}; 10 | 11 | #[repr(C)] 12 | #[derive(Debug, Clone, Copy)] 13 | struct AMediaExtractor { 14 | _data: [u8; 0], 15 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 16 | } 17 | 18 | #[link(name = "mediandk")] 19 | extern "C" { 20 | /// Since: API 21 21 | fn AMediaExtractor_new() -> *mut AMediaExtractor; 22 | 23 | /// Since: API 21 24 | fn AMediaExtractor_delete(extractor: *mut AMediaExtractor) -> isize; 25 | 26 | /// Since: API 21 27 | fn AMediaExtractor_setDataSourceFd( 28 | extractor: *mut AMediaExtractor, 29 | fd: i32, 30 | offset: u64, 31 | length: u64, 32 | ) -> isize; 33 | 34 | /// Since: API 21 35 | fn AMediaExtractor_setDataSource( 36 | extractor: *mut AMediaExtractor, 37 | location: *const c_char, 38 | ) -> isize; 39 | 40 | /// Since: API 21 41 | fn AMediaExtractor_getTrackCount(extractor: *mut AMediaExtractor) -> usize; 42 | 43 | /// Since: API 21 44 | fn AMediaExtractor_getTrackFormat( 45 | extractor: *mut AMediaExtractor, 46 | index: usize, 47 | ) -> *mut AMediaFormat; 48 | 49 | /// Since: API 21 50 | fn AMediaExtractor_selectTrack(extractor: *mut AMediaExtractor, index: usize) -> isize; 51 | 52 | /// Since: API 21 53 | fn AMediaExtractor_unselectTrack(extractor: *mut AMediaExtractor, index: usize) -> isize; 54 | 55 | /// Since: API 21 56 | fn AMediaExtractor_readSampleData( 57 | extractor: *mut AMediaExtractor, 58 | buffer: *mut u8, 59 | capacity: usize, 60 | ) -> isize; 61 | 62 | /// Since: API 21 63 | fn AMediaExtractor_getSampleFlags(extractor: *mut AMediaExtractor) -> u32; 64 | 65 | /// Since: API 21 66 | fn AMediaExtractor_getSampleTrackIndex(extractor: *mut AMediaExtractor) -> i32; 67 | 68 | /// Since: API 21 69 | fn AMediaExtractor_getSampleTime(extractor: *mut AMediaExtractor) -> i64; 70 | 71 | /// Since: API 21 72 | fn AMediaExtractor_advance(extractor: *mut AMediaExtractor) -> bool; 73 | } 74 | 75 | /// MediaExtractor is a demuxer that opens a file or resource and demuxes the data to hand over to MediaCodec 76 | #[derive(Debug)] 77 | pub struct MediaExtractor { 78 | inner: *mut AMediaExtractor, 79 | has_next: bool, 80 | } 81 | 82 | impl MediaExtractor { 83 | /// Creates a new MediaExtractor 84 | fn new() -> Self { 85 | Self { 86 | inner: unsafe { AMediaExtractor_new() }, 87 | has_next: false, 88 | } 89 | } 90 | 91 | /// Creates a MediaExtractor with data source set to a specific URL 92 | pub fn from_url(path: &str) -> Result { 93 | unsafe { 94 | let mut me = Self::new(); 95 | 96 | let path = CString::new(path).unwrap(); 97 | 98 | let result = AMediaExtractor_setDataSource(me.inner, path.as_ptr()); 99 | MediaStatus::make_result(result)?; 100 | 101 | me.has_next = true; 102 | 103 | Ok(me) 104 | } 105 | } 106 | 107 | /// Returns the number of tracks found by MediaExtractor 108 | pub fn track_count(&self) -> usize { 109 | unsafe { AMediaExtractor_getTrackCount(self.inner) } 110 | } 111 | 112 | /// Returns the track index of the current packet to be retrieved by MediaExtractor 113 | pub fn track_index(&self) -> i32 { 114 | unsafe { AMediaExtractor_getSampleTrackIndex(self.inner) } 115 | } 116 | 117 | /// Returns the MediaFormat containing the parameters for this track index. 118 | /// 119 | /// The format can be used to create and initialize MediaCodec 120 | pub fn track_format(&self, index: usize) -> Option { 121 | unsafe { 122 | if self.track_count() <= index { 123 | debug!("Invalid track index {index}"); 124 | return None; 125 | } 126 | 127 | let fmt = AMediaExtractor_getTrackFormat(self.inner, index); 128 | Some(MediaFormat::from_raw(fmt)) 129 | } 130 | } 131 | 132 | /// Select this track to be demuxed by MediaExtractor 133 | pub fn select_track(&mut self, index: usize) { 134 | unsafe { 135 | AMediaExtractor_selectTrack(self.inner, index); 136 | } 137 | } 138 | 139 | /// Unselect this track to be demuxed by MediaExtractor 140 | pub fn unselect_track(&mut self, index: usize) { 141 | unsafe { 142 | AMediaExtractor_unselectTrack(self.inner, index); 143 | } 144 | } 145 | 146 | /// Returns the sample flags for the current packet to be returned 147 | pub fn sample_flags(&self) -> u32 { 148 | unsafe { AMediaExtractor_getSampleFlags(self.inner) } 149 | } 150 | 151 | /// Returns the time for the current packet to be returned 152 | pub fn sample_time(&self) -> i64 { 153 | unsafe { AMediaExtractor_getSampleTime(self.inner) } 154 | } 155 | 156 | /// Read a packet into `buffer` and advance the extractor 157 | /// Returns true if there's still more data to read 158 | pub fn read_next(&mut self, buffer: &mut CodecInputBuffer) -> bool { 159 | unsafe { 160 | if !self.has_next { 161 | return false; 162 | } 163 | 164 | let count = AMediaExtractor_readSampleData(self.inner, buffer.buffer, buffer.size); 165 | if count > 0 { 166 | buffer.set_write_size(count as usize); 167 | buffer.set_time(self.sample_time() as u64); 168 | } 169 | buffer.set_flags(self.sample_flags()); 170 | 171 | self.has_next = AMediaExtractor_advance(self.inner); 172 | 173 | self.has_next 174 | } 175 | } 176 | 177 | /// Returns whether MediaExtractor still has packets to read 178 | pub fn has_next(&self) -> bool { 179 | self.has_next 180 | } 181 | } 182 | 183 | impl Drop for MediaExtractor { 184 | fn drop(&mut self) { 185 | unsafe { 186 | info!("Deleting the extractor..."); 187 | AMediaExtractor_delete(self.inner); 188 | } 189 | } 190 | } 191 | 192 | unsafe impl Send for MediaExtractor {} 193 | unsafe impl Sync for MediaExtractor {} 194 | -------------------------------------------------------------------------------- /src/native_window.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, ops::BitOr, ptr::null_mut}; 2 | 3 | use jni::{objects::JObject, JNIEnv}; 4 | 5 | /// Represents an image buffer (or a Surface in Java) 6 | #[repr(C)] 7 | #[derive(Debug)] 8 | pub struct ANativeWindow { 9 | _data: [u8; 0], 10 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 11 | } 12 | 13 | /// Window Formats 14 | #[derive(Debug, Clone, Copy)] 15 | pub enum NativeWindowFormat { 16 | /// 32 bits per pixel (8 bits per channel) 17 | /// 18 | /// GLES: GL_RGBA8 19 | Rgba8 = 1, 20 | /// 32 bits per pixel (8 bits per channel, alpha values are ignored) 21 | /// 22 | /// GLES: GL_RGB8 23 | Rgb8 = 2, 24 | /// 32 bits per pixel (8 bits per channel, alpha values are ignored) 25 | /// 26 | /// GLES: GL_RGB565 27 | Rgb565 = 4, 28 | 29 | /// YUV 420 888 format 30 | /// 31 | /// Must have an even width and height. Can be accessed in OpenGL shaders through an external sampler. 32 | /// Does not support mip-maps, cube-maps or multi-layered textures. 33 | Yuv420 = 0x23, 34 | /// Some unknown, undocumented format 35 | Other, 36 | } 37 | 38 | impl NativeWindowFormat { 39 | fn values() -> Vec { 40 | vec![Self::Rgba8, Self::Rgb8, Self::Rgb565, Self::Yuv420] 41 | } 42 | } 43 | 44 | impl From for NativeWindowFormat { 45 | fn from(value: isize) -> Self { 46 | let values = Self::values(); 47 | 48 | for item in values { 49 | if item as isize == value { 50 | return item; 51 | } 52 | } 53 | 54 | Self::Other 55 | } 56 | } 57 | 58 | #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] 59 | pub enum NativeWindowTransform { 60 | Identity = 0x00, 61 | MirrorHorizontal = 0x01, 62 | MirrorVertical = 0x02, 63 | Rotate90 = 0x04, 64 | Rotate180 = NativeWindowTransform::MirrorHorizontal as isize 65 | | NativeWindowTransform::MirrorVertical as isize, 66 | Rotate270 = 67 | NativeWindowTransform::Rotate180 as isize | NativeWindowTransform::Rotate90 as isize, 68 | } 69 | 70 | impl BitOr for NativeWindowTransform { 71 | type Output = isize; 72 | 73 | fn bitor(self, rhs: Self) -> Self::Output { 74 | self as isize | rhs as isize 75 | } 76 | } 77 | 78 | #[repr(C)] 79 | #[derive(Debug)] 80 | pub struct NativeWindowBuffer { 81 | pub width: i32, 82 | pub height: i32, 83 | pub stride: i32, 84 | pub format: i32, 85 | pub bits: *mut c_void, 86 | /// Do not touch! 87 | reserved: [u32; 6], 88 | window: *mut ANativeWindow, 89 | } 90 | 91 | impl NativeWindowBuffer { 92 | fn new(window: *mut ANativeWindow) -> Self { 93 | // Acquire a reference to this window so that it doesn't get dropped when we drop the parent 94 | unsafe { ANativeWindow_acquire(window) }; 95 | Self { 96 | width: 0, 97 | height: 0, 98 | stride: 0, 99 | format: 0, 100 | reserved: [0; 6], 101 | bits: null_mut(), 102 | window, 103 | } 104 | } 105 | } 106 | 107 | impl Drop for NativeWindowBuffer { 108 | fn drop(&mut self) { 109 | if !self.window.is_null() { 110 | unsafe { 111 | ANativeWindow_unlockAndPost(self.window); 112 | } 113 | NativeWindow::from_raw(self.window); 114 | } 115 | } 116 | } 117 | 118 | #[repr(C)] 119 | pub struct ARect { 120 | pub left: i32, 121 | pub top: i32, 122 | pub right: i32, 123 | pub bottom: i32, 124 | } 125 | 126 | // Functions start 127 | 128 | #[link(name = "android")] 129 | extern "C" { 130 | fn ANativeWindow_fromSurface(env: JNIEnv, surface: JObject) -> *mut ANativeWindow; 131 | 132 | #[cfg(feature = "api26")] 133 | /// Since API 26 134 | fn ANativeWindow_toSurface(env: JNIEnv, window: *mut ANativeWindow) -> JObject; 135 | 136 | fn ANativeWindow_acquire(window: *mut ANativeWindow); 137 | 138 | fn ANativeWindow_release(window: *mut ANativeWindow); 139 | 140 | fn ANativeWindow_getWidth(window: *mut ANativeWindow) -> i32; 141 | 142 | fn ANativeWindow_getHeight(window: *mut ANativeWindow) -> i32; 143 | 144 | fn ANativeWindow_getFormat(window: *mut ANativeWindow) -> i32; 145 | 146 | fn ANativeWindow_setBuffersGeometry( 147 | window: *mut ANativeWindow, 148 | width: i32, 149 | height: i32, 150 | format: i32, 151 | ) -> i32; 152 | 153 | fn ANativeWindow_lock( 154 | window: *mut ANativeWindow, 155 | buffer: *mut NativeWindowBuffer, 156 | rect: *mut ARect, 157 | ) -> i32; 158 | 159 | fn ANativeWindow_unlockAndPost(window: *mut ANativeWindow) -> i32; 160 | } 161 | 162 | // Functions end 163 | 164 | #[derive(Debug)] 165 | pub struct NativeWindow { 166 | pub(crate) inner: *mut ANativeWindow, 167 | } 168 | 169 | impl NativeWindow { 170 | pub fn from_raw(inner: *mut ANativeWindow) -> Self { 171 | Self { inner } 172 | } 173 | 174 | pub fn from_surface(surface: JObject) -> Self { 175 | unsafe { 176 | let env = javavm::get_env(); 177 | Self::from_raw(ANativeWindow_fromSurface(env, surface)) 178 | } 179 | } 180 | 181 | #[cfg(feature = "api26")] 182 | pub fn to_surface(&self) -> JObject { 183 | let env = javavm::get_env(); 184 | unsafe { ANativeWindow_toSurface(env, self.inner) } 185 | } 186 | 187 | pub fn width(&self) -> i32 { 188 | unsafe { ANativeWindow_getWidth(self.inner) } 189 | } 190 | 191 | pub fn height(&self) -> i32 { 192 | unsafe { ANativeWindow_getHeight(self.inner) } 193 | } 194 | 195 | pub fn format(&self) -> NativeWindowFormat { 196 | unsafe { NativeWindowFormat::from(ANativeWindow_getFormat(self.inner) as isize) } 197 | } 198 | 199 | pub fn set_geometry(&mut self, width: i32, height: i32, format: NativeWindowFormat) { 200 | unsafe { 201 | ANativeWindow_setBuffersGeometry(self.inner, width, height, format as i32); 202 | } 203 | } 204 | 205 | /// Lock the window's next surface for writing. `bounds` is used as an in/out parameter, upon entering the function, it contains the dirty region, that is, the region the caller intends to redraw. When the function returns, `bounds` is updated with the actual area the caller needs to redraw 206 | /// 207 | /// Returns The `NativeWindowBuffer` on success, and None on error. 208 | /// 209 | /// The window's surface will be unlocked automatically when the buffer is dropped. 210 | pub fn lock(&mut self, bounds: &mut ARect) -> Option { 211 | unsafe { 212 | let mut buffer = NativeWindowBuffer::new(self.inner); 213 | if ANativeWindow_lock(self.inner, &mut buffer, bounds) == 0 { 214 | return Some(buffer); 215 | } 216 | } 217 | None 218 | } 219 | } 220 | 221 | impl Clone for NativeWindow { 222 | fn clone(&self) -> Self { 223 | unsafe { 224 | ANativeWindow_acquire(self.inner); 225 | Self { inner: self.inner } 226 | } 227 | } 228 | } 229 | 230 | impl Drop for NativeWindow { 231 | fn drop(&mut self) { 232 | unsafe { 233 | ANativeWindow_release(self.inner); 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/muxer.rs: -------------------------------------------------------------------------------- 1 | use log::warn; 2 | 3 | use crate::{AMediaFormat, BufferInfo, MediaFormat, MediaStatus}; 4 | 5 | #[repr(C)] 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct AMediaMuxer { 8 | _data: [u8; 0], 9 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 10 | } 11 | 12 | #[repr(C)] 13 | #[derive(Clone, Copy, Debug)] 14 | pub enum OutputFormat { 15 | Mpeg4 = 0, 16 | Webm = 1, 17 | ThreeGpp = 2, 18 | } 19 | 20 | // FFI FUNCTIONS 21 | 22 | #[link(name = "mediandk")] 23 | extern "C" { 24 | /// Since: API 21 25 | pub fn AMediaMuxer_new(fd: i32, format: OutputFormat) -> *mut AMediaMuxer; 26 | 27 | /// Since: API 21 28 | pub fn AMediaMuxer_delete(muxer: *mut AMediaMuxer) -> MediaStatus; 29 | 30 | /// Since: API 21 31 | pub fn AMediaMuxer_setLocation( 32 | muxer: *mut AMediaMuxer, 33 | latitude: f32, 34 | longitude: f32, 35 | ) -> MediaStatus; 36 | 37 | /// Since: API 21 38 | pub fn AMediaMuxer_setOrientationHint(muxer: *mut AMediaMuxer, degrees: i32) -> MediaStatus; 39 | 40 | /// Since: API 21 41 | pub fn AMediaMuxer_addTrack(muxer: *mut AMediaMuxer, format: *const AMediaFormat) -> isize; 42 | 43 | /// Since: API 21 44 | pub fn AMediaMuxer_start(muxer: *mut AMediaMuxer) -> MediaStatus; 45 | 46 | /// Since: API 21 47 | pub fn AMediaMuxer_stop(muxer: *mut AMediaMuxer) -> MediaStatus; 48 | 49 | /// Since: API 21 50 | pub fn AMediaMuxer_writeSampleData( 51 | muxer: *mut AMediaMuxer, 52 | track_index: usize, 53 | data: *const u8, 54 | info: *const BufferInfo, 55 | ) -> MediaStatus; 56 | } 57 | 58 | // FFI FUNCTIONS END 59 | 60 | #[derive(Debug, Eq, PartialEq)] 61 | enum MuxerState { 62 | Uninitialized, 63 | Started, 64 | } 65 | 66 | /// The Type-Safe wrapper for `AMediaMuxer`. 67 | /// 68 | /// Ensures memory safety and frees resources when it's supposed to. 69 | /// 70 | /// Also comes with some extra super powers that ensures user supplies correct information before handing them to `AMediaMuxer` 71 | #[derive(Debug)] 72 | pub struct MediaMuxer { 73 | inner: *mut AMediaMuxer, 74 | latitude: f32, 75 | longitude: f32, 76 | orientation_hint: i32, 77 | track_formats: Vec, 78 | state: MuxerState, 79 | } 80 | 81 | impl MediaMuxer { 82 | /// Creates a new MediaMuxer instance 83 | /// 84 | /// `fd` is the file descriptor to write data to 85 | /// 86 | /// `output_format` is the container format for the output 87 | pub fn new(fd: i32, output_format: OutputFormat) -> Option { 88 | let value = unsafe { AMediaMuxer_new(fd, output_format) }; 89 | 90 | if value.is_null() { 91 | return None; 92 | } 93 | 94 | Some(Self { 95 | inner: value, 96 | latitude: 0f32, 97 | longitude: 0f32, 98 | orientation_hint: 0, 99 | track_formats: vec![], 100 | state: MuxerState::Uninitialized, 101 | }) 102 | } 103 | 104 | /// Set and store the geodata (latitude and longitude) in the output file. 105 | /// This method should be called before calling `start`. The geodata is stored in udata box if the output format is Mpeg4, and is ignored for other output formats. 106 | /// 107 | /// The geodata is stored according to ISO-6709 standard. 108 | /// 109 | /// Both values are specified in degrees. 110 | /// 111 | /// Latitude must be in the range (-90, 90) 112 | /// 113 | /// Longitude must be in the range (-180, 180) 114 | pub fn set_location(&mut self, latitude: f32, longitude: f32) -> &mut Self { 115 | match latitude { 116 | value if value >= -90.0 && value <= 90.0 => self.latitude = latitude, 117 | _ => {} 118 | } 119 | 120 | match longitude { 121 | value if value >= -180.0 && value <= 180.0 => self.longitude = longitude, 122 | _ => {} 123 | } 124 | 125 | self 126 | } 127 | 128 | /// Sets the orientation hint for output video playback. 129 | /// 130 | /// This method should be called before calling start. Calling this method will not rotate the video frame when muxer is generating the file, but add a composition matrix containing the rotation angle in the output video if the output format is Mpeg4, so that a video player can choose the proper orientation for playback. 131 | /// Note that some video players must choose to ignore the composition matrix during playback. 132 | /// 133 | /// The angle is specified in degrees, clockwise. 134 | /// 135 | /// The supported angles are: 0, 90, 180 and 270 degrees. 136 | pub fn set_orientation_hint(&mut self, degrees: i32) -> &mut Self { 137 | match degrees { 138 | 0 | 90 | 180 | 270 => self.orientation_hint = degrees, 139 | hint => warn!("Unsupported orientation hint passed to MediaMuxer: {hint}"), 140 | } 141 | 142 | self 143 | } 144 | 145 | /// Adds a track with the specified format. 146 | /// 147 | /// Returns the index of the new track or a `MediaStatus` in case of failure. 148 | pub fn add_track(&mut self, format: MediaFormat) -> Result { 149 | let result = unsafe { AMediaMuxer_addTrack(self.inner, format.inner) }; 150 | 151 | let result = MediaStatus::make_result(result)?; 152 | 153 | // Keep the format, the user might need it 154 | self.track_formats.push(format); 155 | 156 | Ok(result) 157 | } 158 | 159 | /// Returns the number of tracks added to the muxer 160 | pub fn track_count(&self) -> usize { 161 | self.track_formats.len() 162 | } 163 | 164 | /// Returns the track format for a specific track 165 | pub fn format(&self, index: usize) -> Option<&MediaFormat> { 166 | if index >= self.track_formats.len() { 167 | return None; 168 | } 169 | 170 | Some(&self.track_formats[index]) 171 | } 172 | 173 | /// Start the muxer. Should be called only when tracks 174 | pub fn start(&mut self) -> Result<(), MediaStatus> { 175 | if let MuxerState::Started = self.state { 176 | return Ok(()); 177 | } 178 | 179 | // Make sure they've added at least one track 180 | if self.track_formats.is_empty() { 181 | return Err(MediaStatus::ErrorInvalidOperation); 182 | } 183 | 184 | unsafe { 185 | // Set all the parameters 186 | AMediaMuxer_setLocation(self.inner, self.latitude, self.longitude).result()?; 187 | AMediaMuxer_setOrientationHint(self.inner, self.orientation_hint).result()?; 188 | 189 | // Start the muxer 190 | AMediaMuxer_start(self.inner).result()?; 191 | 192 | self.state = MuxerState::Started; 193 | } 194 | 195 | Ok(()) 196 | } 197 | 198 | /// Stops the muxer. 199 | /// 200 | /// Once the muxer stops, it cannot be restarted, and therefore this function takes ownership 201 | /// of the muxer instance 202 | pub fn stop(mut self) -> Result<(), MediaStatus> { 203 | if let MuxerState::Uninitialized = self.state { 204 | // Don't return unnecessary errors. Let them do rubbish :) 205 | return Ok(()); 206 | } 207 | 208 | // In case our user is a crazy person, and managed to bypass the Rust system 209 | self.state = MuxerState::Uninitialized; 210 | 211 | unsafe { AMediaMuxer_stop(self.inner) }.result().map(|_| ()) 212 | } 213 | 214 | /// Writes an encoded sample into the muxer. 215 | /// 216 | /// The application needs to make sure that the samples are written into the right tracks. 217 | /// 218 | /// Also, it needs to make sure the samples for each track are written in chronological order (e.g. in the order they are provided by the encoder) 219 | pub fn write_sample_data( 220 | &mut self, 221 | track_index: usize, 222 | data: &[u8], 223 | buffer_info: &BufferInfo, 224 | ) -> Result<(), MediaStatus> { 225 | if let MuxerState::Uninitialized = self.state { 226 | return Err(MediaStatus::ErrorInvalidOperation); 227 | } 228 | unsafe { AMediaMuxer_writeSampleData(self.inner, track_index, data.as_ptr(), buffer_info) } 229 | .result()?; 230 | 231 | Ok(()) 232 | } 233 | } 234 | 235 | unsafe impl Send for MediaMuxer {} 236 | 237 | unsafe impl Sync for MediaMuxer {} 238 | 239 | impl Drop for MediaMuxer { 240 | fn drop(&mut self) { 241 | unsafe { 242 | AMediaMuxer_delete(self.inner); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/format.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::{c_void, CStr, CString}, 3 | os::raw::c_char, 4 | ptr::null_mut, 5 | }; 6 | 7 | use log::debug; 8 | 9 | #[repr(C)] 10 | #[derive(Clone, Copy, Debug)] 11 | pub struct AMediaFormat { 12 | _data: [u8; 0], 13 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 14 | } 15 | 16 | #[link(name = "mediandk")] 17 | extern "C" { 18 | /// Available since API level 21. 19 | fn AMediaFormat_new() -> *mut AMediaFormat; 20 | 21 | /// Available since API level 21. 22 | fn AMediaFormat_delete(format: *mut AMediaFormat) -> isize; 23 | 24 | /// Available since API level 21. 25 | fn AMediaFormat_toString(format: *mut AMediaFormat) -> *const c_char; 26 | 27 | /// Available since API level 21. 28 | fn AMediaFormat_getInt32(format: *mut AMediaFormat, name: *const c_char, out: *mut i32) 29 | -> bool; 30 | 31 | /// Available since API level 21. 32 | fn AMediaFormat_getInt64(format: *mut AMediaFormat, name: *const c_char, out: *mut i64) 33 | -> bool; 34 | 35 | /// Available since API level 21. 36 | fn AMediaFormat_getFloat(format: *mut AMediaFormat, name: *const c_char, out: *mut f32) 37 | -> bool; 38 | 39 | /// Available since API level 28. 40 | #[cfg(feature = "api28")] 41 | fn AMediaFormat_getDouble( 42 | format: *mut AMediaFormat, 43 | name: *const c_char, 44 | out: *mut f64, 45 | ) -> bool; 46 | 47 | /// Available since API level 28. 48 | #[cfg(feature = "api28")] 49 | fn AMediaFormat_getRect( 50 | format: *mut AMediaFormat, 51 | name: *const c_char, 52 | left: *mut i32, 53 | top: *mut i32, 54 | right: *mut i32, 55 | bottom: *mut i32, 56 | ) -> bool; 57 | 58 | /// Available since API level 21. 59 | fn AMediaFormat_getSize( 60 | format: *mut AMediaFormat, 61 | name: *const c_char, 62 | out: *mut usize, 63 | ) -> bool; 64 | 65 | /// Available since API level 21. 66 | fn AMediaFormat_getBuffer( 67 | format: *mut AMediaFormat, 68 | name: *const c_char, 69 | out: *mut *mut c_void, 70 | size: *mut usize, 71 | ) -> bool; 72 | 73 | /// Available since API level 21. 74 | fn AMediaFormat_getString( 75 | format: *mut AMediaFormat, 76 | name: *const c_char, 77 | out: *mut *mut c_char, 78 | ) -> bool; 79 | 80 | /// Available since API level 21. 81 | fn AMediaFormat_setInt32(format: *mut AMediaFormat, name: *const c_char, value: i32) -> bool; 82 | 83 | /// Available since API level 21. 84 | fn AMediaFormat_setInt64(format: *mut AMediaFormat, name: *const c_char, value: i64) -> bool; 85 | 86 | /// Available since API level 21. 87 | fn AMediaFormat_setFloat(format: *mut AMediaFormat, name: *const c_char, value: f32) -> bool; 88 | 89 | /// Available since API level 28. 90 | #[cfg(feature = "api28")] 91 | fn AMediaFormat_setDouble(format: *mut AMediaFormat, name: *const c_char, value: f64) -> bool; 92 | 93 | /// Available since API level 28. 94 | #[cfg(feature = "api28")] 95 | fn AMediaFormat_setSize(format: *mut AMediaFormat, name: *const c_char, value: usize) -> bool; 96 | 97 | /// Available since API level 28. 98 | #[cfg(feature = "api28")] 99 | fn AMediaFormat_setRect( 100 | format: *mut AMediaFormat, 101 | name: *const c_char, 102 | left: i32, 103 | top: i32, 104 | right: i32, 105 | bottom: i32, 106 | ) -> bool; 107 | 108 | /// Available since API level 21. 109 | fn AMediaFormat_setString( 110 | format: *mut AMediaFormat, 111 | name: *const c_char, 112 | value: *const c_char, 113 | ) -> bool; 114 | 115 | /// Available since API level 21. 116 | fn AMediaFormat_setBuffer( 117 | format: *mut AMediaFormat, 118 | name: *const c_char, 119 | value: *const c_void, 120 | size: usize, 121 | ) -> bool; 122 | 123 | /// Available since API level 29. 124 | #[cfg(feature = "api29")] 125 | fn AMediaFormat_clear(format: *mut AMediaFormat); 126 | 127 | /// Available since API level 29. 128 | #[cfg(feature = "api29")] 129 | fn AMediaFormat_copy(to: *mut AMediaFormat, from: *mut AMediaFormat) -> isize; 130 | } 131 | 132 | /// This structure stores data in key-value pairs for use in MediaCodec and other places in the NDK 133 | #[derive(Debug)] 134 | pub struct MediaFormat { 135 | pub(crate) inner: *mut AMediaFormat, 136 | } 137 | 138 | impl MediaFormat { 139 | /// Construct a MediaFormat from a raw pointer 140 | pub fn from_raw(inner: *mut AMediaFormat) -> Self { 141 | Self { inner } 142 | } 143 | 144 | /// Create a new MediaFormat 145 | pub fn new() -> Option { 146 | unsafe { 147 | let inner = AMediaFormat_new(); 148 | 149 | if inner.is_null() { 150 | return None; 151 | } 152 | 153 | Some(Self { inner }) 154 | } 155 | } 156 | 157 | /// Set a 32-bit integer value 158 | pub fn set_i32(&mut self, name: &str, value: i32) -> bool { 159 | unsafe { AMediaFormat_setInt32(self.inner, name.as_ptr().cast(), value) } 160 | } 161 | 162 | /// Get a 32-bit integer value 163 | pub fn get_i32(&self, name: &str) -> Option { 164 | let mut value = None; 165 | 166 | unsafe { 167 | let mut v = 0; 168 | let name = CString::new(name).unwrap(); 169 | if AMediaFormat_getInt32(self.inner, name.as_ptr(), &mut v) { 170 | value = Some(v); 171 | } 172 | } 173 | 174 | value 175 | } 176 | 177 | /// Set a 64-bit integer value 178 | pub fn set_i64(&mut self, name: &str, value: i64) -> bool { 179 | let name = CString::new(name).unwrap(); 180 | unsafe { AMediaFormat_setInt64(self.inner, name.as_ptr(), value) } 181 | } 182 | 183 | /// Get a 64-bit integer value 184 | pub fn get_i64(&self, name: &str) -> Option { 185 | let mut value = None; 186 | 187 | unsafe { 188 | let mut v = 0; 189 | let name = CString::new(name).unwrap(); 190 | if AMediaFormat_getInt64(self.inner, name.as_ptr(), &mut v) { 191 | value = Some(v); 192 | } 193 | } 194 | 195 | value 196 | } 197 | 198 | /// Set a 32-bit floating-point value 199 | pub fn set_f32(&mut self, name: &str, value: f32) -> bool { 200 | let name = CString::new(name).unwrap(); 201 | unsafe { AMediaFormat_setFloat(self.inner, name.as_ptr(), value) } 202 | } 203 | 204 | /// Get a 32-bit floating-point value 205 | pub fn get_f32(&self, name: &str) -> Option { 206 | let mut value = None; 207 | 208 | unsafe { 209 | let mut v = 0f32; 210 | let name = CString::new(name).unwrap(); 211 | if AMediaFormat_getFloat(self.inner, name.as_ptr(), &mut v) { 212 | value = Some(v); 213 | } 214 | } 215 | 216 | value 217 | } 218 | 219 | /// Convenience function to check whether the mime type is audio 220 | pub fn is_audio(&self) -> bool { 221 | if let Some(mime) = self.get_string("mime") { 222 | return mime.contains("audio"); 223 | } 224 | 225 | false 226 | } 227 | 228 | /// Convenience function to check whether the mime type is video 229 | pub fn is_video(&self) -> bool { 230 | if let Some(mime) = self.get_string("mime") { 231 | return mime.contains("video"); 232 | } 233 | 234 | false 235 | } 236 | 237 | /// Set a 64-bit floating point value 238 | #[cfg(feature = "api28")] 239 | pub fn set_f64(&mut self, name: &str, value: f64) -> bool { 240 | let name = CString::new(name).unwrap(); 241 | unsafe { AMediaFormat_setDouble(self.inner, name.as_ptr(), value) } 242 | } 243 | 244 | /// Get a 64-bit floating-point value 245 | #[cfg(feature = "api28")] 246 | pub fn get_f64(&self, name: &str) -> Option { 247 | let value = None; 248 | 249 | unsafe { 250 | let mut v = 0f64; 251 | let name = CString::new(name).unwrap(); 252 | if AMediaFormat_getDouble(self.inner, name.as_ptr(), &mut v) { 253 | value = Some(v); 254 | } 255 | } 256 | 257 | value 258 | } 259 | 260 | /// Set a string value 261 | pub fn set_string(&mut self, name: &str, value: &str) -> bool { 262 | let name = CString::new(name).unwrap(); 263 | let value = CString::new(value).unwrap(); 264 | unsafe { AMediaFormat_setString(self.inner, name.as_ptr(), value.as_ptr()) } 265 | } 266 | 267 | /// Get a string value 268 | pub fn get_string(&self, name: &str) -> Option { 269 | let mut value = None; 270 | 271 | unsafe { 272 | let mut data = null_mut(); 273 | let name = CString::new(name).unwrap(); 274 | if AMediaFormat_getString(self.inner, name.as_ptr(), &mut data) { 275 | value = Some(CStr::from_ptr(data).to_string_lossy().to_string()); 276 | } 277 | } 278 | 279 | value 280 | } 281 | 282 | /// Clear the entire buffer 283 | #[cfg(feature = "api29")] 284 | pub fn clear(&mut self) { 285 | unsafe { 286 | AMediaFormat_clear(self.inner); 287 | } 288 | } 289 | } 290 | 291 | impl ToString for MediaFormat { 292 | fn to_string(&self) -> String { 293 | unsafe { 294 | let value = AMediaFormat_toString(self.inner); 295 | if !value.is_null() { 296 | CStr::from_ptr(value).to_string_lossy().to_string() 297 | } else { 298 | String::new() 299 | } 300 | } 301 | } 302 | } 303 | 304 | impl Drop for MediaFormat { 305 | fn drop(&mut self) { 306 | unsafe { 307 | AMediaFormat_delete(self.inner); 308 | } 309 | } 310 | } 311 | 312 | unsafe impl Send for MediaFormat {} 313 | unsafe impl Sync for MediaFormat {} 314 | -------------------------------------------------------------------------------- /src/codec.rs: -------------------------------------------------------------------------------- 1 | use log::{debug, warn}; 2 | 3 | use crate::{ 4 | AMediaCrypto, AMediaFormat, ANativeWindow, AudioFrame, Frame, MediaFormat, MediaStatus, 5 | NativeWindow, SampleFormat, VideoFrame, ENCODING_PCM_16BIT, ENCODING_PCM_FLOAT, 6 | }; 7 | use std::{ 8 | ffi::{c_void, CString}, 9 | marker::PhantomData, 10 | os::raw::c_char, 11 | ptr::{null_mut, slice_from_raw_parts}, 12 | }; 13 | 14 | #[repr(C)] 15 | #[derive(Clone, Copy, Debug, Default)] 16 | pub struct BufferInfo { 17 | offset: i32, 18 | size: i32, 19 | presentation_time_us: i64, 20 | flags: u32, 21 | } 22 | 23 | #[repr(C)] 24 | #[derive(Clone, Copy, Debug)] 25 | pub struct AMediaCodecCryptoInfo { 26 | _data: [u8; 0], 27 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 28 | } 29 | 30 | #[repr(C)] 31 | #[derive(Clone, Copy, Debug)] 32 | struct AMediaCodec { 33 | _data: [u8; 0], 34 | _marker: core::marker::PhantomData<(*mut u8, core::marker::PhantomPinned)>, 35 | } 36 | 37 | #[derive(Clone, Copy, Debug)] 38 | pub enum BufferFlag { 39 | CodecConfig = 2, 40 | EndOfStream = 4, 41 | PartialFrame = 8, 42 | Encode = 1, 43 | } 44 | 45 | impl TryFrom for BufferFlag { 46 | type Error = String; 47 | 48 | fn try_from(value: i32) -> Result { 49 | match value { 50 | 2 => Ok(Self::CodecConfig), 51 | 4 => Ok(Self::EndOfStream), 52 | 8 => Ok(Self::PartialFrame), 53 | 1 => Ok(Self::Encode), 54 | _ => Err(String::from("Not Found")), 55 | } 56 | } 57 | } 58 | 59 | impl TryFrom for i32 { 60 | type Error = String; 61 | 62 | fn try_from(value: BufferFlag) -> Result { 63 | Ok(value as i32) 64 | } 65 | } 66 | 67 | impl BufferFlag { 68 | pub fn is_contained_in(&self, flag: i32) -> bool { 69 | return flag & (*self as i32) > 0; 70 | } 71 | 72 | pub fn add_to_flag(&self, flag: &mut i32) { 73 | *flag |= *self as i32; 74 | } 75 | } 76 | 77 | #[derive(Clone, Copy, Debug)] 78 | pub enum InfoFlag { 79 | OutputBuffersChanged = -3, 80 | OutputFormatChanged = -2, 81 | TryAgainLater = -1, 82 | } 83 | 84 | impl TryFrom for InfoFlag { 85 | type Error = String; 86 | 87 | fn try_from(value: i32) -> Result { 88 | match value { 89 | -3 => Ok(Self::OutputBuffersChanged), 90 | -2 => Ok(Self::OutputFormatChanged), 91 | -1 => Ok(Self::TryAgainLater), 92 | _ => Err(String::from("Not Found")), 93 | } 94 | } 95 | } 96 | 97 | impl TryFrom for i32 { 98 | type Error = String; 99 | 100 | fn try_from(value: InfoFlag) -> Result { 101 | Ok(value as i32) 102 | } 103 | } 104 | 105 | impl InfoFlag { 106 | pub fn is_contained_in(&self, flag: i32) -> bool { 107 | return flag & (*self as i32) > 0; 108 | } 109 | 110 | pub fn add_to_flag(&self, flag: &mut i32) { 111 | *flag |= *self as i32; 112 | } 113 | } 114 | 115 | #[repr(C)] 116 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 117 | pub enum CryptoInfoMode { 118 | Clear = 0, 119 | AesCtr = 1, 120 | AesWv = 2, 121 | AesCbc = 3, 122 | } 123 | 124 | #[repr(C)] 125 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] 126 | pub struct CryptoInfoPattern { 127 | pub encrypt_blocks: i32, 128 | pub skip_blocks: i32, 129 | } 130 | 131 | type _AMediaCodecOnAsyncInputAvailable = extern "C" fn( 132 | // Codec 133 | *const AMediaCodec, 134 | // Userdata 135 | userdata: *const c_void, 136 | // Index 137 | index: i32, 138 | ); 139 | 140 | type _AMediaCodecOnAsyncOutputAvailable = extern "C" fn( 141 | // Codec 142 | *const AMediaCodec, 143 | // Userdata 144 | *const c_void, 145 | // Index 146 | i32, 147 | // Buffer info 148 | *const BufferInfo, 149 | ); 150 | 151 | type _AMediaCodecOnAsyncFormatChanged = extern "C" fn( 152 | // Codec 153 | *const AMediaCodec, 154 | // Userdata 155 | *const c_void, 156 | // Format 157 | *const AMediaFormat, 158 | ); 159 | 160 | type _AMediaCodecOnAsyncError = extern "C" fn( 161 | // Codec 162 | *const AMediaCodec, 163 | // Userdata 164 | *const c_void, 165 | // Error 166 | i32, 167 | // Action code 168 | i32, 169 | // Details 170 | *const c_char, 171 | ); 172 | 173 | #[repr(C)] 174 | struct _AMediaCodecOnAsyncNotifyCallback { 175 | on_async_input_available: _AMediaCodecOnAsyncInputAvailable, 176 | on_async_output_available: _AMediaCodecOnAsyncOutputAvailable, 177 | on_async_format_changed: _AMediaCodecOnAsyncFormatChanged, 178 | on_async_error: _AMediaCodecOnAsyncError, 179 | } 180 | 181 | // FFI FUNCTIONS BEGIN 182 | 183 | #[link(name = "mediandk")] 184 | extern "C" { 185 | /// Create codec by name. Use this if you know the exact codec you want to use. 186 | /// When configuring, you will need to specify whether to use the codec as an encoder or decoder. 187 | ///
188 | /// Since: API 21 189 | fn AMediaCodec_createCodecByName(name: *const c_char) -> *mut AMediaCodec; 190 | 191 | /// Create codec by mime type. Most applications will use this, specifying a mime type obtained from media extractor. 192 | ///
193 | /// Since: API 21 194 | fn AMediaCodec_createDecoderByType(mime_type: *const c_char) -> *mut AMediaCodec; 195 | 196 | /// Create encoder by mime type. 197 | ///
198 | /// Since: API 21 199 | fn AMediaCodec_createEncoderByType(mime_type: *const c_char) -> *mut AMediaCodec; 200 | 201 | /// Delete the codec and free its resources 202 | ///
203 | /// Since: API 21 204 | fn AMediaCodec_delete(codec: *mut AMediaCodec) -> MediaStatus; 205 | 206 | /// Configure the codec. For decoding, you would typically get the format from an extractor 207 | ///
208 | /// Since: API 21 209 | fn AMediaCodec_configure( 210 | codec: *mut AMediaCodec, 211 | format: *const AMediaFormat, 212 | surface: *mut ANativeWindow, 213 | crypto: *mut AMediaCrypto, 214 | flags: u32, 215 | ) -> MediaStatus; 216 | 217 | /// Start the codec. A codec must be configured before it can be started, and must be started before buffers can be sent to it. 218 | ///
219 | /// Since: API 21 220 | fn AMediaCodec_start(codec: *mut AMediaCodec) -> MediaStatus; 221 | 222 | /// Stop the codec. 223 | ///
224 | /// Since: API 21 225 | fn AMediaCodec_stop(codec: *mut AMediaCodec) -> MediaStatus; 226 | 227 | /// Flush the codec's input and output. All indices previously returned from calls to `AMediaCodec_dequeueInputBuffer` and `AMediaCodec_dequeueOutpuBuffer` become invalid. 228 | ///
229 | /// Since: API 21 230 | fn AMediaCodec_flush(codec: *mut AMediaCodec) -> MediaStatus; 231 | 232 | /// Get an input buffer. The specified buffer index must have been previously obtained from dequeueInputBuffer, and not yet queued. 233 | ///
234 | /// Since: API 21 235 | fn AMediaCodec_getInputBuffer( 236 | codec: *mut AMediaCodec, 237 | idx: usize, 238 | out_size: *mut usize, 239 | ) -> *mut u8; 240 | 241 | /// Get an output buffer. The specified buffer index must have been previously obtained from `dequeueOutpuBuffer`, and not yet queued. 242 | ///
243 | /// Since: API 21 244 | fn AMediaCodec_getOutputBuffer( 245 | codec: *mut AMediaCodec, 246 | idx: usize, 247 | out_size: *mut usize, 248 | ) -> *mut u8; 249 | 250 | /// Get the index of the next available input buffer. An app will typically use this with `getInputBuffer` to get a pointer to the buffer, then copy the data to be encoded or decoded into the buffer before passing it to the codec. 251 | ///
252 | /// Since: API 21 253 | fn AMediaCodec_dequeueInputBuffer(codec: *mut AMediaCodec, timeout_us: i64) -> isize; 254 | 255 | /// Send the specified buffer to the codec for processing 256 | ///
257 | /// Since: API 21 258 | fn AMediaCodec_queueInputBuffer( 259 | codec: *mut AMediaCodec, 260 | idx: usize, 261 | offset: i32, 262 | size: usize, 263 | time: u64, 264 | flags: u32, 265 | ) -> MediaStatus; 266 | 267 | /// Send the specified buffer to the codec for processing 268 | ///
269 | /// Since: API 21 270 | fn AMediaCodec_queueSecureInputBuffer( 271 | codec: *mut AMediaCodec, 272 | idx: usize, 273 | offset: i32, 274 | info: *mut AMediaCodecCryptoInfo, 275 | time: u64, 276 | flags: u32, 277 | ) -> MediaStatus; 278 | 279 | /// Get the index of the next available buffer of processed data 280 | ///
281 | // Since: API 21 282 | fn AMediaCodec_dequeueOutputBuffer( 283 | codec: *mut AMediaCodec, 284 | info: *mut BufferInfo, 285 | timeout_us: i64, 286 | ) -> isize; 287 | 288 | /// Returns the format of the codec's output. 289 | /// The caller must free the returned format 290 | ///
291 | // Since: API 21 292 | fn AMediaCodec_getOutputFormat(codec: *mut AMediaCodec) -> *mut AMediaFormat; 293 | 294 | /// If you are done with a buffer, use this call to return the buffer to the codec. If you previously specified a surface when configuring this video decoder, you can optionally render the buffer. 295 | ///
296 | // Since: API 21 297 | fn AMediaCodec_releaseOutputBuffer( 298 | codec: *mut AMediaCodec, 299 | index: usize, 300 | render: bool, 301 | ) -> MediaStatus; 302 | 303 | /// Dynamically sets the output surface of a codec. 304 | /// This can only be used if the codec was configured with an output surface. The new output surface should have a compatible usage type to the original output surface. E.g. Codecs may not support switching from a SurfaceTexture (GPU readable) output to ImageReader (software readable) output. 305 | ///
306 | // Since: API 21 307 | fn AMediaCodec_setOutputSurface( 308 | codec: *mut AMediaCodec, 309 | surface: *mut ANativeWindow, 310 | ) -> MediaStatus; 311 | 312 | /// If you are done with a buffer, use this call to update its surface timestamp and return it to the codec to render it to the output surface. If you have not specified an output surface when configuring this video codec, this call will simply return the buffer to the codec. 313 | ///
314 | // Since: API 21 315 | fn AMediaCodec_releaseOutputBufferAtTime( 316 | codec: *mut AMediaCodec, 317 | idx: usize, 318 | timestamp_ns: i64, 319 | ); 320 | 321 | /// Creates a surface that can be used as input to encoder, in place of input buffers. 322 | /// 323 | /// This can only be called after the codec has been configured via `AMediaCodec_configure` and before `AMediaCodec_start` has been called. 324 | /// 325 | /// The application is responsible for releasing the surface by calling `ANativeWindow_release` when done. 326 | /// 327 | ///
328 | /// Since: API 26 329 | #[cfg(feature = "api26")] 330 | fn AMediaCodec_createInputSurface( 331 | codec: *mut AMediaCodec, 332 | surface: *mut *mut ANativeWindow, 333 | ) -> MediaStatus; 334 | 335 | /// Creates a persistent surface that can be used as the input to encoder. 336 | /// 337 | /// Persistent surface can be reused by MediaCodec instances and can be set on a new instance via `AMediaCodec_setInputSurface`. A persistent surface can be connected to at most one instance of MediaCodec at any point in time. 338 | /// 339 | /// The application is responsible for releasing the surface by calling `ANativeWindow_release` when done. 340 | /// 341 | ///
342 | /// Since: API 26 343 | #[cfg(feature = "api26")] 344 | fn AMediaCodec_createPersistentInputSurface(surface: *mut *mut ANativeWindow) -> MediaStatus; 345 | 346 | /// Set a persistent surface that can be used as input to encoder, in place of input buffers 347 | /// 348 | /// The surface provided **must** be a persistent surface created via `AMediaCodec_createPersistentInputSurface`. 349 | /// This can only be called after the codec has been configured by calling `AMediaCodec_configure` and before `AMediaCodec_start` has been called. 350 | /// 351 | ///
352 | /// Since: API 26 353 | #[cfg(feature = "api26")] 354 | fn AMediaCodec_setInputSurface( 355 | codec: *mut AMediaCodec, 356 | surface: *mut ANativeWindow, 357 | ) -> MediaStatus; 358 | 359 | /// Signal additional parameters to the codec instance. 360 | /// 361 | /// Parameters can be communicated only when the codec is running, i.e. after `AMediaCodec_start` has been called. 362 | /// 363 | /// **NOTE:** Some of these parameter changes may silently fail to apply. 364 | /// 365 | ///
366 | /// Since: API 26 367 | #[cfg(feature = "api26")] 368 | fn AMediaCodec_setParameters( 369 | codec: *mut AMediaCodec, 370 | format: *const AMediaFormat, 371 | ) -> MediaStatus; 372 | 373 | /// Signals end-of-stream on input. Equivalent to submitting an empty buffer with `AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM` set. 374 | /// 375 | /// Returns `AMEDIA_ERROR_INVALID_OPERATION` when used with an encoder not in executing state or not receiving input from a Surface created from `AMediaCodec_createInputSurface` or `AMediaCodec_createPersistentInputSurface`. 376 | /// 377 | /// Returns the previous codec error if one exists. 378 | /// Return AMEDIA_OK when completed successfully. 379 | /// 380 | ///
381 | /// Since: API 26 382 | #[cfg(feature = "api26")] 383 | fn AMediaCodec_signalEndOfInputStream(codec: *mut AMediaCodec) -> MediaStatus; 384 | 385 | /// Get format of the buffer. The specified buffer index must have been previously obtained from `dequeueOutputBuffer`. 386 | /// The caller must free the returned format. 387 | ///
388 | /// Since: API 28 389 | #[cfg(feature = "api28")] 390 | fn AMediaCodec_getBufferFormat(codec: *mut AMediaCodec, index: usize) -> *mut AMediaFormat; 391 | 392 | /// Get the component name. If the codec was created by `createDecoderByType` or `createEncoderByType`, what component is chosen is not known beforehand. Caller shall call `AMediaCodec_releaseName` to free the returned pointer. 393 | ///
394 | /// Since: API 28 395 | #[cfg(feature = "api28")] 396 | fn AMediaCodec_getName(codec: *mut AMediaCodec, out_name: *mut *mut c_char) -> MediaStatus; 397 | 398 | /// Free the memory pointed to by name which is returned by AMediaCodec_getName. 399 | ///
400 | /// Since: API 28. 401 | #[cfg(feature = "api28")] 402 | fn AMediaCodec_releaseName(codec: *mut AMediaCodec, name: *mut c_char); 403 | 404 | /// Set an asynchronous callback for actionable AMediaCodec events. 405 | /// When asynchronous callback is enabled, the client should not call `AMediaCodec_getInputBuffer`, `AMediaCodec_getOutputBuffer`, `AMediaCodec_dequeueInputBuffer` or `AMediaCodec_dequeueOutputBuffer`. 406 | /// 407 | /// Also, `AMediaCodec_flush` behaves differently in asynchronous mode. 408 | /// After calling AMediaCodec_flush, you must call AMediaCodec_start to "resume" receiving input buffers, even if an input surface was created. 409 | /// 410 | /// The specified userdata is the pointer used when those callback functions are called. 411 | /// 412 | /// All callbacks are fired on one NDK internal thread. 413 | /// `AMediaCodec_setAsyncNotifyCallback` should not be called on the callback thread. 414 | /// No heavy duty task should be performed on the callback thread. 415 | /// 416 | ///
417 | /// Since: API 28 418 | #[cfg(feature = "api28")] 419 | fn AMediaCodec_setAsyncNotifyCallback( 420 | codec: *mut AMediaCodec, 421 | callback: _AMediaCodecOnAsyncNotifyCallback, 422 | userdata: *mut c_void, 423 | ); 424 | 425 | /// Release the crypto if applicable. 426 | ///
427 | /// Since: API 28. 428 | #[cfg(feature = "api28")] 429 | fn AMediaCodec_releaseCrypto(codec: *mut AMediaCodec) -> MediaStatus; 430 | 431 | /// Call this after `AMediaCodec_configure` returns successfully to get the input format accepted by the codec. Do this to determine what optional configuration parameters were supported by the codec. 432 | /// 433 | /// The caller must free the returned format 434 | ///
435 | /// Since: API 28 436 | #[cfg(feature = "api28")] 437 | fn AMediaCodec_getInputFormat(codec: *mut AMediaCodec) -> *mut AMediaFormat; 438 | 439 | /// Returns true if the codec cannot proceed further, but can be recovered by stopping, configuring and starting again. 440 | /// 441 | ///
442 | /// Since: API 28. 443 | #[cfg(feature = "api28")] 444 | fn AMediaCodecActionCode_isRecoverable(action_code: i32) -> bool; 445 | 446 | /// Returns true if the codec error is a transient issue perhaps due to resource constraints, and that the method (or encoding/decoding) may be retried at a later time. 447 | /// 448 | ///
449 | /// Since: API 28. 450 | #[cfg(feature = "api28")] 451 | fn AMediaCodecActionCode_isTransient(action_code: i32) -> bool; 452 | 453 | /// Since: API 21 454 | fn AMediaCodecCryptoInfo_new( 455 | num_subsamples: i32, 456 | key: &[u8; 16], 457 | iv: &[u8; 16], 458 | mode: CryptoInfoMode, 459 | clearbytes: *mut usize, 460 | encrypted_bytes: *mut usize, 461 | ) -> *mut AMediaCodecCryptoInfo; 462 | 463 | /// Since: API 21 464 | fn AMediaCodecCryptoInfo_delete(info: *mut AMediaCodecCryptoInfo) -> MediaStatus; 465 | 466 | /// Since: API 21 467 | fn AMediaCodecCryptoInfo_setPattern( 468 | info: *mut AMediaCodecCryptoInfo, 469 | pattern: *mut CryptoInfoPattern, 470 | ); 471 | 472 | /// Since: API 21 473 | fn AMediaCodecCryptoInfo_getNumSubSamples(info: *mut AMediaCodecCryptoInfo) -> usize; 474 | 475 | /// Since: API 21 476 | fn AMediaCodecCryptoInfo_getKey(info: *mut AMediaCodecCryptoInfo, dst: *mut u8) -> isize; 477 | 478 | /// Since: API 21 479 | fn AMediaCodecCryptoInfo_getIV(info: *mut AMediaCodecCryptoInfo, dst: *mut u8) -> isize; 480 | 481 | /// Since: API 21 482 | fn AMediaCodecCryptoInfo_getMode(info: *mut AMediaCodecCryptoInfo) -> CryptoInfoMode; 483 | 484 | /// Since: API 21 485 | fn AMediaCodecCryptoInfo_getClearBytes( 486 | info: *mut AMediaCodecCryptoInfo, 487 | dst: *mut usize, 488 | ) -> isize; 489 | 490 | /// Since: API 21 491 | fn AMediaCodecCryptoInfo_getEncryptedBytes( 492 | info: *mut AMediaCodecCryptoInfo, 493 | dst: *mut usize, 494 | ) -> isize; 495 | } 496 | // FFI FUNCTIONS END 497 | 498 | /// This represents a buffer returned by mediacodec's input 499 | /// This buffer should be filled with input data depending on whether the codec is an encoder or decoder 500 | #[derive(Debug)] 501 | pub struct CodecInputBuffer<'a> { 502 | pub(crate) _marker: PhantomData<&'a (*mut u8, core::marker::PhantomPinned)>, 503 | pub(crate) buffer: *mut u8, 504 | pub(crate) size: usize, 505 | pub(crate) write_size: usize, 506 | index: usize, 507 | codec: *mut AMediaCodec, 508 | pub(crate) time: u64, 509 | pub(crate) flags: u32, 510 | } 511 | 512 | impl CodecInputBuffer<'_> { 513 | /// Creates a new Codec Input Buffer from the parameters 514 | fn new(codec: *mut AMediaCodec, index: usize, buffer: *mut u8, size: usize) -> Self { 515 | Self { 516 | _marker: PhantomData, 517 | buffer, 518 | size, 519 | index, 520 | codec, 521 | write_size: 0, 522 | time: 0, 523 | flags: 0, 524 | } 525 | } 526 | 527 | /// Returns this buffer's index. There's not much you can do with this 528 | pub fn index(&self) -> usize { 529 | self.index 530 | } 531 | 532 | /// The size (in bytes) of this buffer 533 | pub fn size(&self) -> usize { 534 | self.size 535 | } 536 | 537 | /// The presentation time of this buffer. If you did not set this yet, it will be zero 538 | pub fn time(&self) -> u64 { 539 | self.time 540 | } 541 | 542 | /// The size of data written into this buffer 543 | pub fn write_size(&self) -> usize { 544 | self.write_size 545 | } 546 | 547 | /// The buffer itself. It is returned as a mutable pointer 548 | /// 549 | /// The reason for this is that I find data copying primitives provided by Rust to be confusing and not performant, 550 | /// so I would recommend you just use the `copy_nonoverlapping` function to copy data to this pointer 551 | pub fn buffer(&self) -> (*mut u8, usize) { 552 | (self.buffer, self.size) 553 | } 554 | 555 | /// Set the presentation time of this buffer 556 | pub fn set_time(&mut self, time: u64) { 557 | self.time = time; 558 | } 559 | 560 | /// Set this buffer's flags 561 | pub fn set_flags(&mut self, flags: u32) { 562 | self.flags = flags; 563 | } 564 | 565 | /// Set the size of bytes written to this buffer 566 | pub fn set_write_size(&mut self, write_size: usize) { 567 | self.write_size = write_size; 568 | } 569 | } 570 | 571 | impl Drop for CodecInputBuffer<'_> { 572 | fn drop(&mut self) { 573 | unsafe { 574 | AMediaCodec_queueInputBuffer( 575 | self.codec, 576 | self.index, 577 | 0, 578 | self.write_size, 579 | self.time, 580 | self.flags, 581 | ); 582 | } 583 | } 584 | } 585 | 586 | unsafe impl Send for CodecInputBuffer<'_> {} 587 | unsafe impl Sync for CodecInputBuffer<'_> {} 588 | 589 | /// Represents a mediacodec output buffer 590 | /// 591 | /// For decoders, this is a raw frame. 592 | /// 593 | /// For encoders, this is an encoded packet 594 | #[derive(Debug)] 595 | pub struct CodecOutputBuffer<'a> { 596 | _marker: PhantomData<&'a (*mut u8, core::marker::PhantomPinned)>, 597 | codec: *mut AMediaCodec, 598 | info: BufferInfo, 599 | index: usize, 600 | using_buffers: bool, 601 | buffer: *mut u8, 602 | _size: usize, 603 | format: MediaFormat, 604 | render: bool, 605 | } 606 | 607 | impl CodecOutputBuffer<'_> { 608 | /// Create a new codec output buffer from the parameters 609 | fn new( 610 | codec: *mut AMediaCodec, 611 | info: BufferInfo, 612 | index: usize, 613 | using_buffers: bool, 614 | buffer: *mut u8, 615 | size: usize, 616 | format: MediaFormat, 617 | ) -> Self { 618 | Self { 619 | codec, 620 | info, 621 | index, 622 | using_buffers, 623 | buffer, 624 | _size: size, 625 | _marker: PhantomData, 626 | render: false, 627 | format, 628 | } 629 | } 630 | 631 | /// Returns the buffer information 632 | pub fn info(&self) -> &BufferInfo { 633 | &self.info 634 | } 635 | 636 | /// Returns the index of the codec buffer 637 | pub fn index(&self) -> usize { 638 | self.index 639 | } 640 | 641 | /// Whether we're returning raw buffers or using hardware buffers 642 | /// 643 | /// This only applies to video frames and a decoder 644 | pub fn using_buffers(&self) -> bool { 645 | self.using_buffers 646 | } 647 | 648 | /// The [MediaFormat](MediaFormat) associated with this buffer 649 | pub fn format(&self) -> &MediaFormat { 650 | &self.format 651 | } 652 | 653 | /// Returns the buffer as a u8 slice 654 | fn buffer_slice(&self) -> Option<&[u8]> { 655 | if !self.using_buffers { 656 | return None; 657 | } 658 | 659 | unsafe { 660 | // Return the size of the readable buffer, instead of the buffer size itself. 661 | // Returning the entire buffer size is useless for the output buffer, as we only need to read data from it 662 | Some(&*slice_from_raw_parts( 663 | (self.buffer as i32 + self.info.offset) as *mut u8, 664 | self.info.size as usize, 665 | )) 666 | } 667 | } 668 | 669 | /// Returns the frame contained in this buffer. 670 | /// Can either be an audio frame or a video frame 671 | pub fn frame(&self) -> Option { 672 | // Determine whether this is an audio or video frame. 673 | // We can use the mime type to do this 674 | let mime = self.format.get_string("mime")?; 675 | let is_audio: bool; 676 | 677 | // We don't know if we might get some weird mime types, so we check for both audio and video explicitly 678 | if mime.contains("audio") { 679 | is_audio = true; 680 | } else if mime.contains("video") { 681 | is_audio = false; 682 | } else { 683 | debug!("Mime is not a valid one!"); 684 | return None; 685 | } 686 | 687 | if is_audio { 688 | // Fetch the PCM Encoding 689 | let encoding = self.format.get_i32("pcm-encoding")?; 690 | let channels = self.format.get_i32("channel-count")?; 691 | 692 | // Can't have invalid channels! 693 | if channels <= 0 { 694 | debug!("Channels <= 0!"); 695 | return None; 696 | } 697 | 698 | match encoding as usize { 699 | ENCODING_PCM_16BIT => { 700 | let slice = self.buffer_slice()?; 701 | let len = slice.len() / std::mem::size_of::(); 702 | 703 | // Make sure this didn't yield a remainder 704 | if slice.len() % std::mem::size_of::() != 0 { 705 | warn!("Potentially wrong results ahead!"); 706 | } 707 | 708 | // Transmute as an i16 slice 709 | let buffer = unsafe { 710 | let buffer = std::mem::transmute::<*const u8, *const i16>(slice.as_ptr()); 711 | let raw = std::ptr::slice_from_raw_parts(buffer, len); 712 | 713 | &*raw 714 | }; 715 | 716 | return Some(Frame::Audio(AudioFrame::new( 717 | SampleFormat::S16(buffer), 718 | channels as u32, 719 | ))); 720 | } 721 | ENCODING_PCM_FLOAT => { 722 | let slice = self.buffer_slice()?; 723 | let len = slice.len() / std::mem::size_of::(); 724 | 725 | // Make sure this didn't yield a remainder 726 | if slice.len() % std::mem::size_of::() != 0 { 727 | warn!("Potentially wrong results ahead!"); 728 | } 729 | 730 | // Transmute as an i16 slice 731 | let buffer = unsafe { 732 | let buffer = std::mem::transmute::<*const u8, *const f32>(slice.as_ptr()); 733 | let raw = std::ptr::slice_from_raw_parts(buffer, len); 734 | 735 | &*raw 736 | }; 737 | 738 | return Some(Frame::Audio(AudioFrame::new( 739 | SampleFormat::F32(buffer), 740 | channels as u32, 741 | ))); 742 | } 743 | _ => { 744 | // We only care about PCM-16 and Float types 745 | return None; 746 | } 747 | } 748 | } else { 749 | // We have a video frame! Do justice to it 750 | 751 | // We have a surface buffer, so return a video frame with surface buffer for it 752 | if !self.using_buffers { 753 | return Some(Frame::Video(VideoFrame::Hardware)); 754 | } else { 755 | unimplemented!(); 756 | } 757 | } 758 | } 759 | 760 | /// Set whether this buffer should render when it gets dropped. 761 | /// This only works for video decoder buffers with a surface attached 762 | pub fn set_render(&mut self, render: bool) { 763 | self.render = render; 764 | } 765 | } 766 | 767 | impl Drop for CodecOutputBuffer<'_> { 768 | fn drop(&mut self) { 769 | unsafe { 770 | AMediaCodec_releaseOutputBuffer(self.codec, self.index, self.render); 771 | } 772 | } 773 | } 774 | 775 | unsafe impl Send for CodecOutputBuffer<'_> {} 776 | unsafe impl Sync for CodecOutputBuffer<'_> {} 777 | 778 | /// The MediaCodec structure itself. 779 | /// 780 | /// Represents either a decoder or an encoder 781 | #[derive(Debug)] 782 | pub struct MediaCodec<'a> { 783 | inner: *mut AMediaCodec, 784 | _marker: PhantomData<&'a *const u8>, 785 | using_buffers: bool, 786 | } 787 | 788 | impl<'a> MediaCodec<'a> { 789 | /// Creates a MediaCodec instance from raw pointer 790 | fn from_ptr(ptr: *mut AMediaCodec) -> Self { 791 | Self { 792 | inner: ptr, 793 | _marker: PhantomData, 794 | using_buffers: false, 795 | } 796 | } 797 | 798 | /// Creates a codec using its name 799 | pub fn new(name: &str) -> Option { 800 | unsafe { 801 | let name = CString::new(name).unwrap(); 802 | let codec = AMediaCodec_createCodecByName(name.as_ptr()); 803 | 804 | if codec.is_null() { 805 | return None; 806 | } 807 | 808 | Some(Self::from_ptr(codec)) 809 | } 810 | } 811 | 812 | /// Creates a decoder using a specific mime type 813 | pub fn create_decoder(mime_type: &str) -> Option { 814 | unsafe { 815 | let mime_type = CString::new(mime_type).unwrap(); 816 | let codec = AMediaCodec_createDecoderByType(mime_type.as_ptr()); 817 | 818 | if codec.is_null() { 819 | return None; 820 | } 821 | 822 | Some(Self::from_ptr(codec)) 823 | } 824 | } 825 | 826 | /// Creates an encoder using a specific mime type 827 | pub fn create_encoder(mime_type: &str) -> Option { 828 | unsafe { 829 | let mime_type = CString::new(mime_type).unwrap(); 830 | let codec = AMediaCodec_createEncoderByType(mime_type.as_ptr()); 831 | 832 | if codec.is_null() { 833 | return None; 834 | } 835 | 836 | Some(Self::from_ptr(codec)) 837 | } 838 | } 839 | 840 | /// Initializes the codec with the parameters. This must be called before you can start the codec 841 | pub fn init( 842 | &mut self, 843 | format: &MediaFormat, 844 | surface: Option, 845 | flags: u32, 846 | ) -> Result<(), MediaStatus> { 847 | unsafe { 848 | // configure 849 | 850 | let surface = if surface.is_some() { 851 | self.using_buffers = false; 852 | surface.unwrap().inner 853 | } else { 854 | self.using_buffers = true; 855 | null_mut() 856 | }; 857 | 858 | AMediaCodec_configure(self.inner, format.inner, surface, null_mut(), flags) 859 | .result() 860 | .map(|_value| ()) 861 | } 862 | } 863 | 864 | /// Starts the codec for processing. 865 | /// 866 | /// This must be called only after the codec has been initialized 867 | pub fn start(&mut self) -> Result<(), MediaStatus> { 868 | unsafe { AMediaCodec_start(self.inner).result().map(|_value| ()) } 869 | } 870 | 871 | /// **WARNING** 872 | /// 873 | /// Make sure you have released all pending buffers before calling this function 874 | pub fn stop(&mut self) -> Result<(), MediaStatus> { 875 | unsafe { AMediaCodec_stop(self.inner).result().map(|_| ()) } 876 | } 877 | 878 | /// **WARNING** 879 | /// 880 | /// Make sure you have released all pending buffers before calling this function 881 | pub fn flush(&mut self) -> Result<(), MediaStatus> { 882 | unsafe { AMediaCodec_flush(self.inner).result().map(|_| ()) } 883 | } 884 | 885 | /// Returns the output format of this codec (if we can find one) 886 | pub fn output_format(&self) -> Option { 887 | unsafe { 888 | let format = AMediaCodec_getOutputFormat(self.inner); 889 | if format.is_null() { 890 | return None; 891 | } 892 | 893 | Some(MediaFormat::from_raw(format)) 894 | } 895 | } 896 | 897 | /// Sets the codec output surface. This will only work if the codec has been initialized with an output surface 898 | /// before starting 899 | pub fn set_output_surface(&mut self, window: NativeWindow) -> bool { 900 | if self.using_buffers { 901 | return false; 902 | } 903 | 904 | unsafe { 905 | AMediaCodec_setOutputSurface(self.inner, window.inner); 906 | true 907 | } 908 | } 909 | 910 | /// Get an input buffer from mediacodec 911 | pub fn dequeue_input(&mut self) -> Result { 912 | unsafe { 913 | // 100us wait time is not too much, right? 914 | let index = AMediaCodec_dequeueInputBuffer(self.inner, 100); 915 | 916 | if index >= 0 { 917 | let mut out_size = 0; 918 | let buffer = AMediaCodec_getInputBuffer(self.inner, index as usize, &mut out_size); 919 | 920 | if buffer.is_null() { 921 | // Return the buffer to the codec, it's not valid 922 | AMediaCodec_queueInputBuffer(self.inner, index as usize, 0, 0, 0, 0); 923 | warn!("Got an index with a null input buffer! What is going on here??? Index: {index}"); 924 | return Err(MediaStatus::ErrorUnknown); 925 | } 926 | 927 | let buf = CodecInputBuffer::new(self.inner, index as usize, buffer, out_size); 928 | 929 | return Ok(buf); 930 | } 931 | 932 | Err(MediaStatus::try_from(index).unwrap_or(MediaStatus::ErrorUnknown)) 933 | } 934 | } 935 | 936 | /// Get an output buffer from mediacodec 937 | pub fn dequeue_output(&mut self) -> Result { 938 | unsafe { 939 | let mut info = BufferInfo::default(); 940 | let index = AMediaCodec_dequeueOutputBuffer(self.inner, &mut info, 100); 941 | let mut out_size = 0; 942 | 943 | if index >= 0 { 944 | let mut buffer = null_mut(); 945 | if self.using_buffers { 946 | buffer = AMediaCodec_getOutputBuffer(self.inner, index as usize, &mut out_size); 947 | 948 | if buffer.is_null() { 949 | AMediaCodec_releaseOutputBuffer(self.inner, index as usize, false); 950 | return Err(MediaStatus::ErrorUnknown); 951 | } 952 | } 953 | 954 | let codec_buffer = CodecOutputBuffer::new( 955 | self.inner, 956 | info, 957 | index as usize, 958 | self.using_buffers, 959 | buffer, 960 | out_size, 961 | self.output_format().unwrap(), 962 | ); 963 | 964 | return Ok(codec_buffer); 965 | } 966 | 967 | Err(MediaStatus::ErrorUnknown) 968 | } 969 | } 970 | } 971 | 972 | impl<'a> Drop for MediaCodec<'a> { 973 | fn drop(&mut self) { 974 | unsafe { 975 | AMediaCodec_delete(self.inner); 976 | } 977 | } 978 | } 979 | 980 | unsafe impl<'a> Send for MediaCodec<'a> {} 981 | unsafe impl<'a> Sync for MediaCodec<'a> {} 982 | --------------------------------------------------------------------------------