#![cfg_attr(
not(any(test, all(feature = "hwencode", target_os = "windows"))),
allow(dead_code)
)]
use std::ffi::CString;
use alpha2_media::{ColorMetadata, EncodeError};
use rsmpeg::avcodec::{AVBSFContext, AVCodecParameters, AVPacket};
use rsmpeg::avutil::AVRational;
use rsmpeg::error::RsmpegError;
use rsmpeg::ffi;
use crate::convert::encode_adapter_error_at;
pub(crate) struct EncodedVideoFilter {
chain: AVBSFContext,
initial_extradata: Option<Vec<u8>>,
}
impl EncodedVideoFilter {
pub(crate) fn new(
parameters: &AVCodecParameters,
color: ColorMetadata,
time_base: AVRational,
) -> Result<Self, EncodeError> {
let spec = chain_spec(parameters.codec_id, color)?;
let chain = alpha2_ffmpeg_bridge::bitstream_filter_list(&spec, parameters, time_base)
.map_err(|error| {
encode_adapter_error_at(
&format!("opening the bitstream filters {}", spec.to_string_lossy()),
error,
)
})?;
let initial_extradata = Some(alpha2_ffmpeg_bridge::codec_parameters_extradata(
&chain.par_out(),
))
.filter(|extradata| !extradata.is_empty());
Ok(Self {
chain,
initial_extradata,
})
}
pub(crate) fn filter(&mut self, mut packet: AVPacket) -> Result<Vec<AVPacket>, EncodeError> {
if packet.size <= 0 {
return Ok(Vec::new());
}
self.chain
.send_packet(Some(&mut packet))
.map_err(|error| encode_adapter_error_at("bitstream filter send failed", error))?;
self.drain()
}
pub(crate) fn flush(&mut self) -> Result<Vec<AVPacket>, EncodeError> {
self.chain
.send_packet(None)
.map_err(|error| encode_adapter_error_at("bitstream filter flush failed", error))?;
self.drain()
}
fn drain(&mut self) -> Result<Vec<AVPacket>, EncodeError> {
let mut packets = Vec::new();
loop {
let mut packet = AVPacket::new();
match self.chain.receive_packet(&mut packet) {
Ok(()) => {
if let Some(extradata) = self.initial_extradata.take() {
let in_band = alpha2_ffmpeg_bridge::packet_has_side_data(
&packet,
ffi::AV_PKT_DATA_NEW_EXTRADATA,
);
if !in_band {
alpha2_ffmpeg_bridge::set_packet_side_data(
&mut packet,
ffi::AV_PKT_DATA_NEW_EXTRADATA,
&extradata,
)
.map_err(|error| {
encode_adapter_error_at("attaching the parameter sets", error)
})?;
}
}
packets.push(packet);
}
Err(RsmpegError::BitstreamDrainError | RsmpegError::BitstreamFlushedError) => {
return Ok(packets);
}
Err(error) => {
return Err(encode_adapter_error_at(
"bitstream filter receive failed",
error,
));
}
}
}
}
}
fn chain_spec(codec: ffi::AVCodecID, color: ColorMetadata) -> Result<CString, EncodeError> {
let (primaries, transfer, matrix, range) = crate::convert::color_tags(color);
let full = u8::from(range == ffi::AVCOL_RANGE_JPEG);
let metadata = match codec {
ffi::AV_CODEC_ID_H264 | ffi::AV_CODEC_ID_HEVC => format!(
"{}=colour_primaries={primaries}:transfer_characteristics={transfer}:\
matrix_coefficients={matrix}:video_full_range_flag={full}",
if codec == ffi::AV_CODEC_ID_H264 {
"h264_metadata"
} else {
"hevc_metadata"
}
),
ffi::AV_CODEC_ID_AV1 => format!(
"av1_metadata=color_primaries={primaries}:transfer_characteristics={transfer}:\
matrix_coefficients={matrix}:color_range={full}"
),
other => {
return Err(EncodeError::Adapter(format!(
"the export's bitstream filters take H.264, HEVC or AV1, not codec {other}"
)));
}
};
CString::new(format!("{metadata},extract_extradata=remove=1"))
.map_err(|_| EncodeError::Adapter("a bitstream filter list holds a NUL".into()))
}