#[cfg(all(not(feature = "audio"), not(feature = "video")))]
compile_error!("enable the `audio` and/or `video` feature on mediaway-encoder-apple");
use crate::EncodeError;
#[cfg(feature = "audio")]
use crate::{AudioEncoder, AudioEncoderConfig};
use crate::{VideoEncoder, VideoEncoderConfig};
#[cfg(feature = "audio")]
use mediaway_common::AudioFrame;
use mediaway_common::VideoFrame;
use mediaway_common::{Bytes, Packet, StreamInfo};
#[cfg(all(any(target_os = "macos", target_os = "ios"), feature = "audio"))]
mod audiotoolbox;
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod videotoolbox;
pub struct AppleVideoEncoder {
#[cfg(any(target_os = "macos", target_os = "ios"))]
inner: Option<videotoolbox::VideoToolboxVideoEncoder>,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
_priv: (),
}
impl AppleVideoEncoder {
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn open(config: &VideoEncoderConfig) -> Result<Self, EncodeError> {
let inner = videotoolbox::VideoToolboxVideoEncoder::open(config)?;
Ok(Self { inner: Some(inner) })
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub const fn open(_config: &VideoEncoderConfig) -> Result<Self, EncodeError> {
Err(EncodeError::Unsupported)
}
}
#[cfg(any(target_os = "macos", target_os = "ios"))]
impl VideoEncoder for AppleVideoEncoder {
fn stream_info(&self) -> &StreamInfo {
#[allow(
clippy::option_if_let_else,
reason = "map_or_else forces 'static vs 'self lifetime clash"
)]
if let Some(e) = self.inner.as_ref() {
e.stream_info()
} else {
closed_stream_info()
}
}
fn push_frame(&mut self, frame: &VideoFrame) -> Result<(), EncodeError> {
self.inner
.as_mut()
.ok_or(EncodeError::Closed)?
.push_frame(frame)
}
fn poll_packet(&mut self) -> Result<Option<Packet>, EncodeError> {
self.inner
.as_mut()
.ok_or(EncodeError::Closed)?
.poll_packet()
}
fn flush(&mut self) -> Result<(), EncodeError> {
self.inner.as_mut().ok_or(EncodeError::Closed)?.flush()
}
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
impl VideoEncoder for AppleVideoEncoder {
fn stream_info(&self) -> &StreamInfo {
closed_stream_info()
}
fn push_frame(&mut self, _frame: &VideoFrame) -> Result<(), EncodeError> {
Err(EncodeError::Unsupported)
}
fn poll_packet(&mut self) -> Result<Option<Packet>, EncodeError> {
Ok(None)
}
fn flush(&mut self) -> Result<(), EncodeError> {
Err(EncodeError::Unsupported)
}
}
#[cfg(feature = "audio")]
pub struct AppleAudioEncoder {
#[cfg(any(target_os = "macos", target_os = "ios"))]
inner: Option<AudioBackend>,
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
_priv: (),
}
#[cfg(all(feature = "audio", any(target_os = "macos", target_os = "ios")))]
enum AudioBackend {
Aac(audiotoolbox::AacEncoder),
Opus(audiotoolbox::OpusEncoder),
}
#[cfg(feature = "audio")]
impl AppleAudioEncoder {
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub fn open(config: &AudioEncoderConfig) -> Result<Self, EncodeError> {
let inner = match config.codec {
mediaway_common::CodecKind::Aac => {
AudioBackend::Aac(audiotoolbox::AacEncoder::open(config)?)
}
mediaway_common::CodecKind::Opus => {
AudioBackend::Opus(audiotoolbox::OpusEncoder::open(config)?)
}
_ => return Err(EncodeError::Unsupported),
};
Ok(Self { inner: Some(inner) })
}
#[cfg(not(any(target_os = "macos", target_os = "ios")))]
pub const fn open(_config: &AudioEncoderConfig) -> Result<Self, EncodeError> {
Err(EncodeError::Unsupported)
}
}
#[cfg(all(feature = "audio", any(target_os = "macos", target_os = "ios")))]
impl AudioEncoder for AppleAudioEncoder {
fn stream_info(&self) -> &StreamInfo {
match self.inner.as_ref() {
Some(AudioBackend::Aac(e)) => e.stream_info(),
Some(AudioBackend::Opus(e)) => e.stream_info(),
None => closed_audio_stream_info(),
}
}
fn push_frame(&mut self, frame: &AudioFrame) -> Result<(), EncodeError> {
match self.inner.as_mut() {
Some(AudioBackend::Aac(e)) => e.push_frame(frame),
Some(AudioBackend::Opus(e)) => e.push_frame(frame),
None => Err(EncodeError::Closed),
}
}
fn poll_packet(&mut self) -> Result<Option<Packet>, EncodeError> {
match self.inner.as_mut() {
Some(AudioBackend::Aac(e)) => e.poll_packet(),
Some(AudioBackend::Opus(e)) => e.poll_packet(),
None => Err(EncodeError::Closed),
}
}
fn flush(&mut self) -> Result<(), EncodeError> {
match self.inner.as_mut() {
Some(AudioBackend::Aac(e)) => e.flush(),
Some(AudioBackend::Opus(e)) => e.flush(),
None => Err(EncodeError::Closed),
}
}
}
#[cfg(all(feature = "audio", not(any(target_os = "macos", target_os = "ios"))))]
impl AudioEncoder for AppleAudioEncoder {
fn stream_info(&self) -> &StreamInfo {
closed_audio_stream_info()
}
fn push_frame(&mut self, _frame: &AudioFrame) -> Result<(), EncodeError> {
Err(EncodeError::Unsupported)
}
fn poll_packet(&mut self) -> Result<Option<Packet>, EncodeError> {
Ok(None)
}
fn flush(&mut self) -> Result<(), EncodeError> {
Err(EncodeError::Unsupported)
}
}
#[cfg(feature = "audio")]
fn closed_audio_stream_info() -> &'static StreamInfo {
use std::sync::OnceLock;
static INFO: OnceLock<StreamInfo> = OnceLock::new();
INFO.get_or_init(|| StreamInfo::Audio {
id: 0,
codec: mediaway_common::CodecKind::Aac,
time_base: mediaway_common::Rational::new(1, 48_000),
extra_data: Bytes::new(),
sample_rate: 0,
channels: 0,
})
}
fn closed_stream_info() -> &'static StreamInfo {
use std::sync::OnceLock;
static INFO: OnceLock<StreamInfo> = OnceLock::new();
INFO.get_or_init(|| StreamInfo::Video {
id: 0,
codec: mediaway_common::CodecKind::H264,
time_base: mediaway_common::Rational::new(1, 30),
geometry: mediaway_common::VideoGeometry {
width: 0,
height: 0,
},
extra_data: Bytes::new(),
})
}
#[cfg(all(test, any(target_os = "macos", target_os = "ios")))]
#[path = "lib_tests.rs"]
mod tests;