|
| 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 | +} |
0 commit comments