#![forbid(unsafe_code)]
use crate::auto::{AutoVideoEncodeConfig, Backend, BackendSelection, EncodePathClass};
use crate::{EncodeError, VideoEncoder, VideoInputPreference};
use mediaway_common::{
CodecKind, GpuBufferHandle, GpuDeviceHandle, NativeHandle, Packet, StreamInfo, VideoFrame,
};
use std::fmt;
use crate::windows::WindowsVideoEncoder;
#[allow(clippy::large_enum_variant)]
enum EncoderImpl {
Wmf(WindowsVideoEncoder),
Sw(mediaway_sw::av1::Av1Encoder),
Nvenc(crate::nvenc::NvencVideoEncoder),
QuickSync(crate::quicksync::QuickSyncVideoEncoder),
}
#[allow(
clippy::match_like_matches_macro,
clippy::match_same_arms,
clippy::needless_pass_by_value
)]
fn map_av1_error(e: mediaway_sw::av1::Av1Error) -> EncodeError {
use mediaway_sw::av1::Av1Error;
match e {
Av1Error::Unsupported => EncodeError::Unsupported,
Av1Error::InvalidConfig(_) => EncodeError::InvalidInput,
Av1Error::InvalidInput => EncodeError::InvalidInput,
Av1Error::Backend => EncodeError::Backend,
Av1Error::Closed => EncodeError::Closed,
_ => EncodeError::Backend,
}
}
pub struct AutoVideoEncoder {
path: EncodePathClass,
backend: Backend,
inner: EncoderImpl,
#[cfg(windows)]
bridge: Option<crate::windows::D3d12SharedEncodeBridge>,
}
impl fmt::Debug for AutoVideoEncoder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AutoVideoEncoder")
.field("path", &self.path)
.field("backend", &self.backend)
.finish_non_exhaustive()
}
}
impl AutoVideoEncoder {
pub fn open(config: &AutoVideoEncodeConfig) -> Result<Self, EncodeError> {
if config.width == 0 || config.height == 0 {
return Err(EncodeError::InvalidInput);
}
match config.backend {
BackendSelection::Explicit(Backend::Software) => return Self::try_software(config),
BackendSelection::Explicit(Backend::Nvenc) => return Self::try_nvenc(config),
BackendSelection::Explicit(Backend::QuickSync) => return Self::try_quicksync(config),
BackendSelection::Explicit(Backend::Amf) => return Err(EncodeError::NoBackend),
_ => {}
}
let ceiling = config.max_path_class;
let hardware_only = matches!(config.backend, BackendSelection::AutoHardwareOnly);
let mut last_err: Option<EncodeError> = None;
if let Some(gpu_device) = config.gpu_device {
match gpu_device {
GpuDeviceHandle::DirectX11(_) => {
let low =
config.to_low_level(VideoInputPreference::ZeroCopyGpu, Some(gpu_device));
match WindowsVideoEncoder::open(&low) {
Ok(inner) => {
return Ok(Self::with_path(
EncodePathClass::ZeroCopy,
Backend::Os,
EncoderImpl::Wmf(inner),
));
}
Err(e) => last_err = Some(e),
}
}
GpuDeviceHandle::DirectX12(handle) if ceiling >= EncodePathClass::GpuCopy => {
match Self::try_gpu_copy(config, handle) {
Ok(enc) => return Ok(enc),
Err(e) => last_err = Some(e),
}
}
_ => last_err = Some(EncodeError::Unsupported),
}
}
if hardware_only {
if let Ok(enc) = Self::try_nvenc(config) {
return Ok(enc);
}
if let Ok(enc) = Self::try_quicksync(config) {
return Ok(enc);
}
}
if ceiling >= EncodePathClass::CpuUpload {
let low = config.to_low_level(VideoInputPreference::CpuUploadOk, None);
match WindowsVideoEncoder::open(&low) {
Ok(inner) => {
return Ok(Self::with_path(
EncodePathClass::CpuUpload,
Backend::Os,
EncoderImpl::Wmf(inner),
));
}
Err(e) => last_err = Some(e),
}
}
if hardware_only || matches!(config.backend, BackendSelection::Explicit(Backend::Os)) {
return Err(last_err.unwrap_or(EncodeError::Unsupported));
}
if ceiling >= EncodePathClass::Software {
match Self::try_software(config) {
Ok(enc) => return Ok(enc),
Err(e) => last_err = Some(e),
}
}
if ceiling >= EncodePathClass::Readback {
return Err(EncodeError::NoBackend);
}
Err(last_err.unwrap_or(EncodeError::Unsupported))
}
#[cfg(windows)]
const fn with_path(path: EncodePathClass, backend: Backend, inner: EncoderImpl) -> Self {
Self {
path,
backend,
inner,
bridge: None,
}
}
#[cfg(not(windows))]
const fn with_path(path: EncodePathClass, backend: Backend, inner: EncoderImpl) -> Self {
Self {
path,
backend,
inner,
}
}
fn try_software(config: &AutoVideoEncodeConfig) -> Result<Self, EncodeError> {
if config.codec != CodecKind::Av1 {
return Err(EncodeError::Unsupported);
}
let mut sw_config =
mediaway_sw::av1::Av1EncoderConfig::new(config.width, config.height, config.time_base);
sw_config.bitrate_bps = config.bitrate_bps;
let inner = mediaway_sw::av1::Av1Encoder::open(&sw_config).map_err(map_av1_error)?;
Ok(Self::with_path(
EncodePathClass::Software,
Backend::Software,
EncoderImpl::Sw(inner),
))
}
fn try_nvenc(config: &AutoVideoEncodeConfig) -> Result<Self, EncodeError> {
let low = config.to_low_level(VideoInputPreference::CpuUploadOk, None);
let inner = crate::nvenc::NvencVideoEncoder::open(&low)?;
Ok(Self::with_path(
EncodePathClass::CpuUpload,
Backend::Nvenc,
EncoderImpl::Nvenc(inner),
))
}
fn try_quicksync(config: &AutoVideoEncodeConfig) -> Result<Self, EncodeError> {
let low = config.to_low_level(VideoInputPreference::CpuUploadOk, None);
let inner = crate::quicksync::QuickSyncVideoEncoder::open(&low)?;
Ok(Self::with_path(
EncodePathClass::CpuUpload,
Backend::QuickSync,
EncoderImpl::QuickSync(inner),
))
}
#[cfg(windows)]
fn try_gpu_copy(
config: &AutoVideoEncodeConfig,
d3d12_device: NativeHandle,
) -> Result<Self, EncodeError> {
let bridge = crate::windows::D3d12SharedEncodeBridge::open(
d3d12_device,
config.width,
config.height,
)?;
let d3d11_device = bridge.d3d11_device_handle()?;
let low = config.to_low_level(
VideoInputPreference::ZeroCopyGpu,
Some(GpuDeviceHandle::DirectX11(d3d11_device)),
);
let inner = WindowsVideoEncoder::open(&low)?;
Ok(Self {
path: EncodePathClass::GpuCopy,
backend: Backend::Os,
inner: EncoderImpl::Wmf(inner),
bridge: Some(bridge),
})
}
#[cfg(not(windows))]
const fn try_gpu_copy(
_config: &AutoVideoEncodeConfig,
_d3d12_device: NativeHandle,
) -> Result<Self, EncodeError> {
Err(EncodeError::Unsupported)
}
#[must_use]
pub const fn path_class(&self) -> EncodePathClass {
self.path
}
#[must_use]
pub const fn resolved_backend(&self) -> Backend {
self.backend
}
#[cfg(windows)]
#[must_use]
pub fn gpu_copy_target(&self) -> Option<NativeHandle> {
self.bridge
.as_ref()
.and_then(|b| b.d3d12_resource_handle().ok())
}
#[cfg(not(windows))]
#[must_use]
pub const fn gpu_copy_target(&self) -> Option<NativeHandle> {
None
}
#[cfg(windows)]
#[must_use]
pub fn gpu_copy_dx11_frame_handle(&self) -> Option<GpuBufferHandle> {
self.bridge.as_ref().and_then(|b| b.as_dx11_handle().ok())
}
#[cfg(not(windows))]
#[must_use]
pub const fn gpu_copy_dx11_frame_handle(&self) -> Option<GpuBufferHandle> {
None
}
}
impl VideoEncoder for AutoVideoEncoder {
fn stream_info(&self) -> &StreamInfo {
match &self.inner {
EncoderImpl::Wmf(enc) => enc.stream_info(),
EncoderImpl::Sw(enc) => enc.stream_info(),
EncoderImpl::Nvenc(enc) => enc.stream_info(),
EncoderImpl::QuickSync(enc) => enc.stream_info(),
}
}
fn push_frame(&mut self, frame: &VideoFrame) -> Result<(), EncodeError> {
match &mut self.inner {
EncoderImpl::Wmf(enc) => enc.push_frame(frame),
EncoderImpl::Sw(enc) => enc.push_frame(frame).map_err(map_av1_error),
EncoderImpl::Nvenc(enc) => enc.push_frame(frame),
EncoderImpl::QuickSync(enc) => enc.push_frame(frame),
}
}
fn poll_packet(&mut self) -> Result<Option<Packet>, EncodeError> {
match &mut self.inner {
EncoderImpl::Wmf(enc) => enc.poll_packet(),
EncoderImpl::Sw(enc) => enc.poll_packet().map_err(map_av1_error),
EncoderImpl::Nvenc(enc) => enc.poll_packet(),
EncoderImpl::QuickSync(enc) => enc.poll_packet(),
}
}
fn flush(&mut self) -> Result<(), EncodeError> {
match &mut self.inner {
EncoderImpl::Wmf(enc) => enc.flush(),
EncoderImpl::Sw(enc) => enc.flush().map_err(map_av1_error),
EncoderImpl::Nvenc(enc) => enc.flush(),
EncoderImpl::QuickSync(enc) => enc.flush(),
}
}
}
#[must_use]
pub fn support(codec: CodecKind) -> Vec<crate::capability::EncoderCapability> {
use crate::capability::{EncodeSupport, EncodeUnavailable, EncoderCapability};
#[cfg(windows)]
{
let live = [
Backend::Os,
Backend::Nvenc,
Backend::QuickSync,
Backend::Software,
]
.map(|backend| {
let cfg = AutoVideoEncodeConfig {
backend: BackendSelection::Explicit(backend),
..AutoVideoEncodeConfig::new(codec, 64, 64, mediaway_common::Rational::new(1, 30))
};
let support = match AutoVideoEncoder::open(&cfg) {
Ok(enc) => EncodeSupport::Supported(enc.path_class()),
Err(EncodeError::Backend) => {
EncodeSupport::Unavailable(EncodeUnavailable::NoDevice)
}
Err(_) => EncodeSupport::Unavailable(EncodeUnavailable::NotImplemented),
};
EncoderCapability::new(backend, support)
});
live.into_iter()
.chain([EncoderCapability::new(
Backend::Amf,
EncodeSupport::Unavailable(EncodeUnavailable::NotImplemented),
)])
.collect()
}
#[cfg(not(windows))]
{
let _ = codec;
[
Backend::Os,
Backend::Nvenc,
Backend::QuickSync,
Backend::Amf,
Backend::Software,
]
.into_iter()
.map(|backend| {
EncoderCapability::new(
backend,
EncodeSupport::Unavailable(EncodeUnavailable::NotImplemented),
)
})
.collect()
}
}
#[cfg(test)]
#[path = "auto_tests.rs"]
mod tests;