Skip to content

Commit fe12339

Browse files
committed
feat(tests): add decode_filter_audio example for audio filtering
1 parent 0c1746b commit fe12339

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
//! RIIR: https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/decode_filter_audio.c
2+
use anyhow::{anyhow, Context, Result};
3+
use rsmpeg::{
4+
avcodec::AVCodecContext,
5+
avfilter::{AVFilter, AVFilterGraph, AVFilterInOut},
6+
avformat::AVFormatContextInput,
7+
avutil::{get_sample_fmt_name, AVChannelLayout, AVFrame},
8+
ffi,
9+
};
10+
use std::{ffi::CStr, fs::File, io::Write, path::Path};
11+
12+
static FILTER_DESCR: &CStr = c"aresample=8000,aformat=sample_fmts=s16:channel_layouts=mono";
13+
14+
struct FilterState {
15+
graph: AVFilterGraph,
16+
}
17+
18+
fn open_input_file(filename: &CStr) -> Result<(AVFormatContextInput, AVCodecContext, usize)> {
19+
let fmt = AVFormatContextInput::open(filename).context("Cannot open input file")?;
20+
21+
let (audio_idx, dec) = fmt
22+
.find_best_stream(ffi::AVMEDIA_TYPE_AUDIO)
23+
.context("Cannot find an audio stream in the input file")?
24+
.ok_or_else(|| anyhow!("Cannot find an audio stream in the input file"))?;
25+
26+
let mut ctx = AVCodecContext::new(&dec);
27+
ctx.apply_codecpar(&fmt.streams()[audio_idx].codecpar())
28+
.context("Failed to copy codec parameters")?;
29+
ctx.open(None).context("Cannot open audio decoder")?;
30+
Ok((fmt, ctx, audio_idx))
31+
}
32+
33+
fn init_filters(
34+
fmt: &AVFormatContextInput,
35+
dec_ctx: &mut AVCodecContext,
36+
audio_stream_index: usize,
37+
filters_descr: &CStr,
38+
) -> Result<FilterState> {
39+
let mut graph = AVFilterGraph::new();
40+
let abuffer = AVFilter::get_by_name(c"abuffer").context("Cannot find abuffer")?;
41+
let abuffersink = AVFilter::get_by_name(c"abuffersink").context("Cannot find abuffersink")?;
42+
43+
// Build args like C: time_base=..:sample_rate=..:sample_fmt=..:channel_layout=..
44+
let tb = fmt.streams()[audio_stream_index].time_base;
45+
if dec_ctx.ch_layout.order == ffi::AV_CHANNEL_ORDER_UNSPEC {
46+
// default by channel count if unspecified
47+
dec_ctx.set_ch_layout(
48+
AVChannelLayout::from_nb_channels(dec_ctx.ch_layout.nb_channels).into_inner(),
49+
);
50+
}
51+
let sample_fmt_name = get_sample_fmt_name(dec_ctx.sample_fmt)
52+
.and_then(|s| s.to_str().ok())
53+
.unwrap_or("");
54+
55+
// Prepare channel_layout description (safe wrapper)
56+
let ch_layout_str = dec_ctx
57+
.ch_layout()
58+
.describe()
59+
.ok()
60+
.and_then(|s| s.into_string().ok())
61+
.unwrap_or_else(|| "?".to_string());
62+
63+
let args = format!(
64+
"time_base={}/{}:sample_rate={}:sample_fmt={}:channel_layout={}",
65+
tb.num,
66+
tb.den,
67+
dec_ctx.sample_rate,
68+
if sample_fmt_name.is_empty() {
69+
"fltp"
70+
} else {
71+
sample_fmt_name
72+
},
73+
ch_layout_str
74+
);
75+
76+
// Create source and sink filters
77+
{
78+
let args_c = std::ffi::CString::new(args).unwrap();
79+
let mut buffersrc_ctx = graph
80+
.create_filter_context(&abuffer, c"in", Some(&args_c))
81+
.context("Cannot create audio buffer source")?;
82+
let mut buffersink_ctx = graph
83+
.alloc_filter_context(&abuffersink, c"out")
84+
.ok_or_else(|| anyhow!("Cannot create audio buffer sink"))?;
85+
86+
buffersink_ctx
87+
.opt_set(c"sample_formats", c"s16")
88+
.context("Cannot set output sample format")?;
89+
buffersink_ctx
90+
.opt_set(c"channel_layouts", c"mono")
91+
.context("Cannot set output channel layout")?;
92+
buffersink_ctx
93+
.opt_set_array(c"samplerates", 0, Some(&[8000]), ffi::AV_OPT_TYPE_INT)
94+
.context("Cannot set output sample rate")?;
95+
buffersink_ctx
96+
.init_str(None)
97+
.context("Cannot initialize audio buffer sink")?;
98+
99+
// Link endpoints through the filter graph described by filters_descr
100+
let outputs = AVFilterInOut::new(c"in", &mut buffersrc_ctx, 0);
101+
let inputs = AVFilterInOut::new(c"out", &mut buffersink_ctx, 0);
102+
graph
103+
.parse_ptr(filters_descr, Some(inputs), Some(outputs))
104+
.context("avfilter_graph_parse_ptr failed")?;
105+
graph.config().context("avfilter_graph_config failed")?;
106+
}
107+
108+
// Print output summary like the C example (after graph is configured)
109+
{
110+
let sink = graph
111+
.get_filter(c"out")
112+
.ok_or_else(|| anyhow!("buffersink not found"))?;
113+
let out_srate = sink.get_sample_rate();
114+
let out_fmt = get_sample_fmt_name(sink.get_format())
115+
.and_then(|s| s.to_str().ok())
116+
.unwrap_or("?");
117+
let ch_layout = sink.get_ch_layout();
118+
let ch_desc = ch_layout
119+
.describe()
120+
.ok()
121+
.and_then(|s| s.into_string().ok())
122+
.unwrap_or_else(|| "?".to_string());
123+
eprintln!(
124+
"Output: srate:{}Hz fmt:{} chlayout:{}",
125+
out_srate, out_fmt, ch_desc
126+
);
127+
}
128+
129+
Ok(FilterState { graph })
130+
}
131+
132+
fn print_and_write_frame(mut out: &File, frame: &AVFrame) -> Result<()> {
133+
// s16le interleaved mono: dump to file
134+
let samples = (frame.nb_samples * frame.ch_layout.nb_channels) as usize;
135+
unsafe {
136+
let p = frame.data[0] as *const u8;
137+
let bytes = std::slice::from_raw_parts(p, samples * 2);
138+
out.write_all(bytes)?;
139+
}
140+
Ok(())
141+
}
142+
143+
fn decode_filter_audio(input: &CStr, out_path: &str) -> Result<()> {
144+
let (mut fmt, mut dec_ctx, audio_idx) = open_input_file(input)?;
145+
let mut filt = init_filters(&fmt, &mut dec_ctx, audio_idx, FILTER_DESCR)?;
146+
147+
if let Some(dir) = Path::new(out_path).parent() {
148+
std::fs::create_dir_all(dir).ok();
149+
}
150+
let out = File::create(out_path).with_context(|| format!("open {}", out_path))?;
151+
152+
while let Some(packet) = fmt.read_packet()? {
153+
if packet.stream_index == audio_idx as i32 {
154+
dec_ctx
155+
.send_packet(Some(&packet))
156+
.context("Error while sending a packet to the decoder")?;
157+
loop {
158+
match dec_ctx.receive_frame() {
159+
Ok(frame) => {
160+
// push decoded frame into graph
161+
{
162+
let mut src = filt
163+
.graph
164+
.get_filter(c"in")
165+
.context("buffersrc not found")?;
166+
src.buffersrc_add_frame(
167+
Some(frame),
168+
Some(ffi::AV_BUFFERSRC_FLAG_KEEP_REF as i32),
169+
)
170+
.context("Error while feeding the audio filtergraph")?;
171+
}
172+
// pull all available filtered frames
173+
let mut sink = filt
174+
.graph
175+
.get_filter(c"out")
176+
.ok_or_else(|| anyhow!("buffersink not found"))?;
177+
loop {
178+
match sink.buffersink_get_frame(None) {
179+
Ok(f) => {
180+
print_and_write_frame(&out, &f)?;
181+
}
182+
Err(rsmpeg::error::RsmpegError::BufferSinkDrainError)
183+
| Err(rsmpeg::error::RsmpegError::BufferSinkEofError) => break,
184+
Err(e) => return Err(e).context("buffersink_get_frame failed"),
185+
}
186+
}
187+
}
188+
Err(rsmpeg::error::RsmpegError::DecoderDrainError)
189+
| Err(rsmpeg::error::RsmpegError::DecoderFlushedError) => break,
190+
Err(e) => {
191+
return Err(e).context("Error while receiving a frame from the decoder")
192+
}
193+
}
194+
}
195+
}
196+
}
197+
// EOF: signal to the filter graph
198+
{
199+
let mut src = filt
200+
.graph
201+
.get_filter(c"in")
202+
.ok_or_else(|| anyhow!("buffersrc not found"))?;
203+
src.buffersrc_add_frame(None, None)
204+
.context("Error while closing the filtergraph")?;
205+
}
206+
loop {
207+
let mut sink = filt
208+
.graph
209+
.get_filter(c"out")
210+
.ok_or_else(|| anyhow!("buffersink not found"))?;
211+
match sink.buffersink_get_frame(None) {
212+
Ok(f) => {
213+
print_and_write_frame(&out, &f)?;
214+
}
215+
Err(rsmpeg::error::RsmpegError::BufferSinkDrainError)
216+
| Err(rsmpeg::error::RsmpegError::BufferSinkEofError) => break,
217+
Err(e) => return Err(e).context("buffersink_get_frame failed on flush"),
218+
}
219+
}
220+
221+
Ok(())
222+
}
223+
224+
#[test]
225+
fn decode_filter_audio_test() {
226+
let input = c"tests/assets/audios/sample1_short.aac";
227+
let out = "tests/output/decode_filter_audio/out_s16le_8k_mono.pcm";
228+
decode_filter_audio(input, out).unwrap();
229+
}

tests/ffmpeg_examples/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
mod decode_audio;
2+
#[cfg(feature = "ffmpeg8")]
3+
mod decode_filter_audio;
24
mod decode_video;
35
mod demux_decode;
46
mod encode_video;

0 commit comments

Comments
 (0)