feat: scale tests
This commit is contained in:
parent
cf3ce4348e
commit
f8a085af09
@ -1,8 +1,5 @@
|
|||||||
use ffmpeg_rs_raw::{Decoder, Demuxer, DemuxerInfo, Filter, Scaler};
|
use ffmpeg_rs_raw::{Decoder, Demuxer, DemuxerInfo, Filter};
|
||||||
use ffmpeg_sys_the_third::AVHWDeviceType::{
|
use ffmpeg_sys_the_third::AVHWDeviceType::AV_HWDEVICE_TYPE_CUDA;
|
||||||
AV_HWDEVICE_TYPE_CUDA, AV_HWDEVICE_TYPE_D3D12VA, AV_HWDEVICE_TYPE_MEDIACODEC,
|
|
||||||
AV_HWDEVICE_TYPE_OPENCL, AV_HWDEVICE_TYPE_VDPAU, AV_HWDEVICE_TYPE_VULKAN,
|
|
||||||
};
|
|
||||||
use ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVMediaType};
|
use ffmpeg_sys_the_third::{av_frame_free, av_packet_free, AVMediaType};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
use std::env::args;
|
use std::env::args;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::{options_to_dict, return_ffmpeg_error, StreamInfoChannel};
|
use crate::{options_to_dict, return_ffmpeg_error, rstr, StreamInfoChannel};
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::ffi::CStr;
|
use std::ffi::CStr;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
@ -46,9 +46,7 @@ impl Drop for DecoderCodecContext {
|
|||||||
impl Display for DecoderCodecContext {
|
impl Display for DecoderCodecContext {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
unsafe {
|
unsafe {
|
||||||
let codec_name = CStr::from_ptr(avcodec_get_name((*self.codec).id))
|
let codec_name = rstr!(avcodec_get_name((*self.codec).id));
|
||||||
.to_str()
|
|
||||||
.unwrap();
|
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"DecoderCodecContext: codec={}, hw={}",
|
"DecoderCodecContext: codec={}, hw={}",
|
||||||
@ -56,9 +54,7 @@ impl Display for DecoderCodecContext {
|
|||||||
if self.hw_config.is_null() {
|
if self.hw_config.is_null() {
|
||||||
"no"
|
"no"
|
||||||
} else {
|
} else {
|
||||||
CStr::from_ptr(av_hwdevice_get_type_name((*self.hw_config).device_type))
|
rstr!(av_hwdevice_get_type_name((*self.hw_config).device_type))
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -148,7 +144,7 @@ impl Decoder {
|
|||||||
if codec.is_null() {
|
if codec.is_null() {
|
||||||
anyhow::bail!(
|
anyhow::bail!(
|
||||||
"Failed to find codec: {}",
|
"Failed to find codec: {}",
|
||||||
CStr::from_ptr(avcodec_get_name((*codec_par).codec_id)).to_str()?
|
rstr!(avcodec_get_name((*codec_par).codec_id))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
let context = avcodec_alloc_context3(codec);
|
let context = avcodec_alloc_context3(codec);
|
||||||
@ -159,7 +155,7 @@ impl Decoder {
|
|||||||
let mut ret = avcodec_parameters_to_context(context, (*stream).codecpar);
|
let mut ret = avcodec_parameters_to_context(context, (*stream).codecpar);
|
||||||
return_ffmpeg_error!(ret, "Failed to copy codec parameters to context");
|
return_ffmpeg_error!(ret, "Failed to copy codec parameters to context");
|
||||||
|
|
||||||
let codec_name = CStr::from_ptr(avcodec_get_name((*codec).id)).to_str()?;
|
let codec_name = rstr!(avcodec_get_name((*codec).id));
|
||||||
// try use HW decoder
|
// try use HW decoder
|
||||||
let mut hw_config = ptr::null();
|
let mut hw_config = ptr::null();
|
||||||
if let Some(ref hw_types) = self.hw_decoder_types {
|
if let Some(ref hw_types) = self.hw_decoder_types {
|
||||||
@ -171,9 +167,7 @@ impl Decoder {
|
|||||||
if hw_config.is_null() {
|
if hw_config.is_null() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let hw_name =
|
let hw_name = rstr!(av_hwdevice_get_type_name((*hw_config).device_type));
|
||||||
CStr::from_ptr(av_hwdevice_get_type_name((*hw_config).device_type))
|
|
||||||
.to_str()?;
|
|
||||||
if !hw_types.contains(&(*hw_config).device_type) {
|
if !hw_types.contains(&(*hw_config).device_type) {
|
||||||
debug!("skipping hwaccel={}_{}", codec_name, hw_name);
|
debug!("skipping hwaccel={}_{}", codec_name, hw_name);
|
||||||
continue;
|
continue;
|
||||||
@ -234,7 +228,7 @@ impl Decoder {
|
|||||||
|
|
||||||
let mut pkgs = Vec::new();
|
let mut pkgs = Vec::new();
|
||||||
while ret >= 0 {
|
while ret >= 0 {
|
||||||
let mut frame = av_frame_alloc();
|
let frame = av_frame_alloc();
|
||||||
ret = avcodec_receive_frame(ctx.context, frame);
|
ret = avcodec_receive_frame(ctx.context, frame);
|
||||||
if ret < 0 {
|
if ret < 0 {
|
||||||
if ret == AVERROR_EOF || ret == AVERROR(libc::EAGAIN) {
|
if ret == AVERROR_EOF || ret == AVERROR(libc::EAGAIN) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::return_ffmpeg_error;
|
use crate::{cstr, return_ffmpeg_error};
|
||||||
use crate::{get_ffmpeg_error_msg, DemuxerInfo, StreamChannelType, StreamInfoChannel};
|
use crate::{get_ffmpeg_error_msg, DemuxerInfo, StreamChannelType, StreamInfoChannel};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use ffmpeg_sys_the_third::*;
|
use ffmpeg_sys_the_third::*;
|
||||||
@ -68,7 +68,7 @@ impl Demuxer {
|
|||||||
match &mut self.input {
|
match &mut self.input {
|
||||||
DemuxerInput::Url(input) => avformat_open_input(
|
DemuxerInput::Url(input) => avformat_open_input(
|
||||||
&mut self.ctx,
|
&mut self.ctx,
|
||||||
format!("{}\0", input).as_ptr() as *const libc::c_char,
|
cstr!(input),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
),
|
),
|
||||||
@ -89,7 +89,7 @@ impl Demuxer {
|
|||||||
avformat_open_input(
|
avformat_open_input(
|
||||||
&mut self.ctx,
|
&mut self.ctx,
|
||||||
if let Some(url) = url {
|
if let Some(url) = url {
|
||||||
format!("{}\0", url).as_ptr() as *const libc::c_char
|
cstr!(url)
|
||||||
} else {
|
} else {
|
||||||
ptr::null_mut()
|
ptr::null_mut()
|
||||||
},
|
},
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
use crate::{cstr, return_ffmpeg_error, set_opts};
|
use crate::{cstr, return_ffmpeg_error, rstr, set_opts};
|
||||||
use anyhow::Error;
|
use anyhow::Error;
|
||||||
use ffmpeg_sys_the_third::{
|
use ffmpeg_sys_the_third::{
|
||||||
av_free, av_strdup, avfilter_get_by_name, avfilter_graph_alloc, avfilter_graph_alloc_filter,
|
av_free, av_strdup, avfilter_get_by_name, avfilter_graph_alloc, avfilter_graph_alloc_filter,
|
||||||
avfilter_graph_config, avfilter_graph_create_filter, avfilter_graph_dump, avfilter_graph_parse,
|
avfilter_graph_config, avfilter_graph_create_filter, avfilter_graph_dump, avfilter_graph_parse,
|
||||||
avfilter_graph_parse2, avfilter_graph_parse_ptr, avfilter_inout_alloc, AVFilterContext,
|
avfilter_inout_alloc, AVFilterContext, AVFilterGraph, AVFrame,
|
||||||
AVFilterGraph, AVFrame,
|
|
||||||
};
|
};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -26,7 +25,7 @@ impl Filter {
|
|||||||
///
|
///
|
||||||
/// https://ffmpeg.org/ffmpeg-filters.html
|
/// https://ffmpeg.org/ffmpeg-filters.html
|
||||||
pub unsafe fn parse(graph: &str) -> Result<Self, Error> {
|
pub unsafe fn parse(graph: &str) -> Result<Self, Error> {
|
||||||
let mut ctx = avfilter_graph_alloc();
|
let ctx = avfilter_graph_alloc();
|
||||||
let inputs = avfilter_inout_alloc();
|
let inputs = avfilter_inout_alloc();
|
||||||
let outputs = avfilter_inout_alloc();
|
let outputs = avfilter_inout_alloc();
|
||||||
let src = avfilter_get_by_name(cstr!("buffer"));
|
let src = avfilter_get_by_name(cstr!("buffer"));
|
||||||
@ -95,9 +94,8 @@ impl Filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn build(&mut self) -> Result<(), Error> {
|
pub unsafe fn build(&mut self) -> Result<(), Error> {
|
||||||
let mut d = avfilter_graph_dump(self.graph, ptr::null_mut());
|
let d = rstr!(avfilter_graph_dump(self.graph, ptr::null_mut()));
|
||||||
debug!("{}", CStr::from_ptr(d).to_string_lossy());
|
debug!("{}", d);
|
||||||
av_free(d as *mut _);
|
|
||||||
|
|
||||||
let ret = avfilter_graph_config(self.graph, ptr::null_mut());
|
let ret = avfilter_graph_config(self.graph, ptr::null_mut());
|
||||||
return_ffmpeg_error!(ret, "Failed to build filter");
|
return_ffmpeg_error!(ret, "Failed to build filter");
|
||||||
|
29
src/lib.rs
29
src/lib.rs
@ -36,24 +36,26 @@ macro_rules! cstr {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! rstr {
|
||||||
|
($str:expr) => {
|
||||||
|
core::ffi::CStr::from_ptr($str).to_str().unwrap()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn get_ffmpeg_error_msg(ret: libc::c_int) -> String {
|
fn get_ffmpeg_error_msg(ret: libc::c_int) -> String {
|
||||||
unsafe {
|
unsafe {
|
||||||
const BUF_SIZE: usize = 512;
|
const BUF_SIZE: usize = 512;
|
||||||
let mut buf: [libc::c_char; BUF_SIZE] = [0; BUF_SIZE];
|
let mut buf: [libc::c_char; BUF_SIZE] = [0; BUF_SIZE];
|
||||||
av_make_error_string(buf.as_mut_ptr(), BUF_SIZE, ret);
|
av_make_error_string(buf.as_mut_ptr(), BUF_SIZE, ret);
|
||||||
String::from(CStr::from_ptr(buf.as_ptr()).to_str().unwrap())
|
rstr!(buf.as_ptr()).to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe fn options_to_dict(options: HashMap<String, String>) -> Result<*mut AVDictionary, Error> {
|
unsafe fn options_to_dict(options: HashMap<String, String>) -> Result<*mut AVDictionary, Error> {
|
||||||
let mut dict = ptr::null_mut();
|
let mut dict = ptr::null_mut();
|
||||||
for (key, value) in options {
|
for (key, value) in options {
|
||||||
let ret = av_dict_set(
|
let ret = av_dict_set(&mut dict, cstr!(key), cstr!(value), 0);
|
||||||
&mut dict,
|
|
||||||
format!("{}\0", key).as_ptr() as *const libc::c_char,
|
|
||||||
format!("{}\0", value).as_ptr() as *const libc::c_char,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
return_ffmpeg_error!(ret);
|
return_ffmpeg_error!(ret);
|
||||||
}
|
}
|
||||||
Ok(dict)
|
Ok(dict)
|
||||||
@ -93,11 +95,7 @@ fn list_opts(ctx: *mut libc::c_void) -> Result<Vec<String>, Error> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret.push(
|
ret.push(rstr!((*opt_ptr).name).to_string());
|
||||||
CStr::from_ptr((*opt_ptr).name)
|
|
||||||
.to_string_lossy()
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
@ -106,12 +104,7 @@ fn list_opts(ctx: *mut libc::c_void) -> Result<Vec<String>, Error> {
|
|||||||
fn set_opts(ctx: *mut libc::c_void, options: HashMap<String, String>) -> Result<(), Error> {
|
fn set_opts(ctx: *mut libc::c_void, options: HashMap<String, String>) -> Result<(), Error> {
|
||||||
unsafe {
|
unsafe {
|
||||||
for (key, value) in options {
|
for (key, value) in options {
|
||||||
let ret = av_opt_set(
|
let ret = av_opt_set(ctx, cstr!(key), cstr!(value), AV_OPT_SEARCH_CHILDREN);
|
||||||
ctx,
|
|
||||||
format!("{}\0", key).as_ptr() as *const libc::c_char,
|
|
||||||
format!("{}\0", value).as_ptr() as *const libc::c_char,
|
|
||||||
AV_OPT_SEARCH_CHILDREN,
|
|
||||||
);
|
|
||||||
return_ffmpeg_error!(ret);
|
return_ffmpeg_error!(ret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
82
src/scale.rs
82
src/scale.rs
@ -1,12 +1,13 @@
|
|||||||
use std::mem::transmute;
|
use std::mem::transmute;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
|
|
||||||
use crate::return_ffmpeg_error;
|
use crate::{return_ffmpeg_error, rstr};
|
||||||
use anyhow::{bail, Error};
|
use anyhow::{bail, Error};
|
||||||
use ffmpeg_sys_the_third::{
|
use ffmpeg_sys_the_third::{
|
||||||
av_frame_alloc, av_frame_copy_props, sws_freeContext, sws_getContext, sws_init_context,
|
av_frame_alloc, av_frame_copy_props, av_get_pix_fmt_name, sws_freeContext, sws_getContext,
|
||||||
sws_scale_frame, AVFrame, AVPixelFormat, SwsContext, SWS_BILINEAR,
|
sws_scale_frame, AVFrame, AVPixelFormat, SwsContext, SWS_BILINEAR,
|
||||||
};
|
};
|
||||||
|
use log::trace;
|
||||||
|
|
||||||
pub struct Scaler {
|
pub struct Scaler {
|
||||||
width: u16,
|
width: u16,
|
||||||
@ -17,8 +18,6 @@ pub struct Scaler {
|
|||||||
|
|
||||||
unsafe impl Send for Scaler {}
|
unsafe impl Send for Scaler {}
|
||||||
|
|
||||||
unsafe impl Sync for Scaler {}
|
|
||||||
|
|
||||||
impl Drop for Scaler {
|
impl Drop for Scaler {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -28,12 +27,18 @@ impl Drop for Scaler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Scaler {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Scaler {
|
impl Scaler {
|
||||||
pub fn new(width: u16, height: u16, format: AVPixelFormat) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
width,
|
width: 0,
|
||||||
height,
|
height: 0,
|
||||||
format,
|
format: AVPixelFormat::AV_PIX_FMT_YUV420P,
|
||||||
ctx: ptr::null_mut(),
|
ctx: ptr::null_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,6 +80,16 @@ impl Scaler {
|
|||||||
bail!("Failed to create scalar context");
|
bail!("Failed to create scalar context");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
trace!(
|
||||||
|
"scale setup: {}x{}@{} => {}x{}@{}",
|
||||||
|
(*frame).width,
|
||||||
|
(*frame).height,
|
||||||
|
rstr!(av_get_pix_fmt_name(transmute((*frame).format))),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
rstr!(av_get_pix_fmt_name(format))
|
||||||
|
);
|
||||||
|
|
||||||
self.width = width;
|
self.width = width;
|
||||||
self.height = height;
|
self.height = height;
|
||||||
self.format = format;
|
self.format = format;
|
||||||
@ -104,3 +119,54 @@ impl Scaler {
|
|||||||
Ok(dst_frame)
|
Ok(dst_frame)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ffmpeg_sys_the_third::{
|
||||||
|
av_frame_alloc, av_frame_free, av_frame_get_buffer, AVFrame, AVPixelFormat,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe fn blank_frame() -> *mut AVFrame {
|
||||||
|
let frame = av_frame_alloc();
|
||||||
|
(*frame).width = 512;
|
||||||
|
(*frame).height = 512;
|
||||||
|
(*frame).format = AVPixelFormat::AV_PIX_FMT_RGB24 as libc::c_int;
|
||||||
|
av_frame_get_buffer(frame, 0);
|
||||||
|
frame
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scale_rgb24_yuv420() {
|
||||||
|
unsafe {
|
||||||
|
let mut frame = blank_frame();
|
||||||
|
let mut scaler = Scaler::new();
|
||||||
|
|
||||||
|
// downscale
|
||||||
|
let mut out_frame = scaler
|
||||||
|
.process_frame(frame, 128, 128, AVPixelFormat::AV_PIX_FMT_YUV420P)
|
||||||
|
.expect("Failed to process frame");
|
||||||
|
assert_eq!((*out_frame).width, 128);
|
||||||
|
assert_eq!((*out_frame).height, 128);
|
||||||
|
assert_eq!(
|
||||||
|
(*out_frame).format,
|
||||||
|
transmute(AVPixelFormat::AV_PIX_FMT_YUV420P)
|
||||||
|
);
|
||||||
|
av_frame_free(&mut out_frame);
|
||||||
|
|
||||||
|
// upscale
|
||||||
|
let mut out_frame = scaler
|
||||||
|
.process_frame(frame, 1024, 1024, AVPixelFormat::AV_PIX_FMT_YUV420P)
|
||||||
|
.expect("Failed to process frame");
|
||||||
|
assert_eq!((*out_frame).width, 1024);
|
||||||
|
assert_eq!((*out_frame).height, 1024);
|
||||||
|
assert_eq!(
|
||||||
|
(*out_frame).format,
|
||||||
|
transmute(AVPixelFormat::AV_PIX_FMT_YUV420P)
|
||||||
|
);
|
||||||
|
av_frame_free(&mut out_frame);
|
||||||
|
|
||||||
|
av_frame_free(&mut frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use crate::format_time;
|
use crate::{format_time, rstr};
|
||||||
use ffmpeg_sys_the_third::{
|
use ffmpeg_sys_the_third::{
|
||||||
av_get_pix_fmt_name, av_get_sample_fmt_name, avcodec_get_name, AVMediaType, AVStream,
|
av_get_pix_fmt_name, av_get_sample_fmt_name, avcodec_get_name, AVMediaType, AVStream,
|
||||||
};
|
};
|
||||||
@ -130,44 +130,36 @@ impl StreamInfoChannel {
|
|||||||
|
|
||||||
impl Display for StreamInfoChannel {
|
impl Display for StreamInfoChannel {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
let codec_name = unsafe { CStr::from_ptr(avcodec_get_name(transmute(self.codec as i32))) };
|
let codec_name = unsafe { rstr!(avcodec_get_name(transmute(self.codec as i32))) };
|
||||||
match self.channel_type {
|
match self.channel_type {
|
||||||
StreamChannelType::Video => write!(
|
StreamChannelType::Video => write!(
|
||||||
f,
|
f,
|
||||||
"{} #{}: codec={},size={}x{},fps={:.3},pix_fmt={}",
|
"{} #{}: codec={},size={}x{},fps={:.3},pix_fmt={}",
|
||||||
self.channel_type,
|
self.channel_type,
|
||||||
self.index,
|
self.index,
|
||||||
codec_name.to_str().unwrap(),
|
codec_name,
|
||||||
self.width,
|
self.width,
|
||||||
self.height,
|
self.height,
|
||||||
self.fps,
|
self.fps,
|
||||||
unsafe {
|
unsafe { rstr!(av_get_pix_fmt_name(transmute(self.format as libc::c_int))) },
|
||||||
CStr::from_ptr(av_get_pix_fmt_name(transmute(self.format as libc::c_int)))
|
|
||||||
}
|
|
||||||
.to_str()
|
|
||||||
.unwrap(),
|
|
||||||
),
|
),
|
||||||
StreamChannelType::Audio => write!(
|
StreamChannelType::Audio => write!(
|
||||||
f,
|
f,
|
||||||
"{} #{}: codec={},format={},sample_rate={}",
|
"{} #{}: codec={},format={},sample_rate={}",
|
||||||
self.channel_type,
|
self.channel_type,
|
||||||
self.index,
|
self.index,
|
||||||
codec_name.to_str().unwrap(),
|
codec_name,
|
||||||
unsafe {
|
unsafe {
|
||||||
CStr::from_ptr(av_get_sample_fmt_name(transmute(
|
rstr!(av_get_sample_fmt_name(transmute(
|
||||||
self.format as libc::c_int,
|
self.format as libc::c_int,
|
||||||
)))
|
)))
|
||||||
}
|
},
|
||||||
.to_str()
|
|
||||||
.unwrap(),
|
|
||||||
self.sample_rate,
|
self.sample_rate,
|
||||||
),
|
),
|
||||||
StreamChannelType::Subtitle => write!(
|
StreamChannelType::Subtitle => write!(
|
||||||
f,
|
f,
|
||||||
"{} #{}: codec={}",
|
"{} #{}: codec={}",
|
||||||
self.channel_type,
|
self.channel_type, self.index, codec_name
|
||||||
self.index,
|
|
||||||
codec_name.to_str().unwrap()
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user