Skip to content

Commit 06f6695

Browse files
committed
feat(tests): enhance ffmpeg examples with improved channel layout handling and logging
1 parent f354cc6 commit 06f6695

File tree

4 files changed

+156
-59
lines changed

4 files changed

+156
-59
lines changed

tests/ffmpeg_examples/demux_decode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rsmpeg::{
88
get_sample_fmt_name, sample_fmt_is_planar, ts2timestr, AVChannelLayout, AVFrame,
99
},
1010
error::RsmpegError,
11-
ffi,
11+
ffi::{self, AV_CHANNEL_LAYOUT_MONO},
1212
};
1313
use std::{ffi::CStr, fs, io::Write, path::Path};
1414

@@ -300,7 +300,7 @@ fn demux_decode(input_raw: &CStr, video_out: &str, audio_out: &str) -> Result<()
300300
planar_name
301301
);
302302
sfmt = get_packed_sample_fmt(sfmt).context("Cannot get packed sample fmt")?;
303-
ch_layout = AVChannelLayout::from_nb_channels(1);
303+
ch_layout = unsafe { AVChannelLayout::new(AV_CHANNEL_LAYOUT_MONO) };
304304
}
305305
let fmt = get_format_from_sample_fmt(sfmt).ok_or_else(|| {
306306
let name = get_sample_fmt_name(sfmt)

tests/ffmpeg_examples/encode_video.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,18 @@ fn encode(
2020
frame: Option<&AVFrame>,
2121
file: &mut BufWriter<File>,
2222
) -> Result<()> {
23+
if let Some(frame) = frame {
24+
println!("Send frame {:3}", frame.pts);
25+
}
26+
2327
encode_context.send_frame(frame)?;
2428
loop {
2529
let packet = match encode_context.receive_packet() {
2630
Ok(packet) => packet,
2731
Err(RsmpegError::EncoderDrainError) | Err(RsmpegError::EncoderFlushedError) => break,
2832
Err(e) => return Err(e.into()),
2933
};
34+
println!("Write packet {:3} (size={:5})", packet.pts, packet.size);
3035
let data = unsafe { std::slice::from_raw_parts(packet.data, packet.size as usize) };
3136
file.write_all(data)?;
3237
}

tests/ffmpeg_examples/filter_audio.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ use anyhow::{Context, Result};
33
use rsmpeg::{
44
avfilter::{AVFilter, AVFilterGraph},
55
avutil::{
6-
get_bytes_per_sample, get_sample_fmt_name, sample_fmt_is_planar, AVChannelLayout,
6+
get_bytes_per_sample, get_sample_fmt_name, sample_fmt_is_planar, AVChannelLayoutRef,
77
AVDictionary, AVFrame, AVMD5,
88
},
9-
ffi,
9+
ffi::{self, AV_CHANNEL_LAYOUT_5POINT0},
1010
};
11+
use std::f32::consts::PI;
1112

1213
const INPUT_SAMPLERATE: i32 = 48_000;
1314
const FRAME_SIZE: i32 = 1024;
@@ -20,7 +21,7 @@ fn init_filter_graph() -> Result<AVFilterGraph> {
2021
let aformat = AVFilter::get_by_name(c"aformat").context("aformat not found")?;
2122

2223
// abuffer options via AVOptions, like the C example
23-
let ch_layout = AVChannelLayout::from_nb_channels(5).describe()?;
24+
let ch_layout = unsafe { AVChannelLayoutRef::new(&AV_CHANNEL_LAYOUT_5POINT0) }.describe()?;
2425
let sample_fmt = get_sample_fmt_name(ffi::AV_SAMPLE_FMT_FLTP).unwrap_or(c"fltp");
2526

2627
// Create abuffer as "src"
@@ -119,26 +120,28 @@ fn process_output(md5: &mut AVMD5, frame: &AVFrame) -> Result<()> {
119120
}
120121

121122
fn get_input(frame_num: i64) -> Result<AVFrame> {
122-
let mut f = AVFrame::new();
123-
f.set_sample_rate(INPUT_SAMPLERATE);
124-
f.set_format(ffi::AV_SAMPLE_FMT_FLTP);
125-
f.set_ch_layout(rsmpeg::avutil::AVChannelLayout::from_nb_channels(5).into_inner());
126-
f.set_nb_samples(FRAME_SIZE);
127-
f.set_pts(frame_num * FRAME_SIZE as i64);
128-
f.get_buffer(0)?;
123+
// Replicate C example: FRAME_SIZE samples, 5 channels (5.0 layout), FLTP
124+
let mut frame = AVFrame::new();
125+
frame.set_sample_rate(INPUT_SAMPLERATE);
126+
frame.set_format(ffi::AV_SAMPLE_FMT_FLTP);
127+
// Use exact 5.0 channel layout constant instead of default-from-count
128+
frame.set_ch_layout(AV_CHANNEL_LAYOUT_5POINT0);
129+
frame.set_nb_samples(FRAME_SIZE);
130+
frame.set_pts(frame_num * FRAME_SIZE as i64);
131+
frame.get_buffer(0)?;
129132

130-
// fill planar float samples: 5 channels
133+
// Fill each planar channel with: sin(2 * PI * (frame_num + sample_index) * (channel+1) / FRAME_SIZE)
131134
unsafe {
132135
for ch in 0..5 {
133-
let ptr = f.data[ch] as *mut f32;
134-
for i in 0..FRAME_SIZE as isize {
135-
let val =
136-
((frame_num as f32 + i as f32) * (ch as f32 + 1.0) / FRAME_SIZE as f32).sin();
137-
*ptr.offset(i) = val;
136+
let base = frame.data[ch] as *mut f32;
137+
for j in 0..FRAME_SIZE as isize {
138+
let phase = 2.0 * PI * (frame_num as f32 + j as f32) * (ch as f32 + 1.0)
139+
/ FRAME_SIZE as f32;
140+
*base.offset(j) = phase.sin();
138141
}
139142
}
140143
}
141-
Ok(f)
144+
Ok(frame)
142145
}
143146

144147
pub fn filter_audio_process(duration: f32) -> Result<usize> {

tests/ffmpeg_examples/resample_audio.rs

Lines changed: 129 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
11
//! RIIR: https://github.com/FFmpeg/FFmpeg/blob/master/doc/examples/resample_audio.c
22
use rsmpeg::{
3-
avutil::{AVChannelLayout, AVFrame},
4-
ffi,
3+
avutil::{av_rescale_rnd, get_bytes_per_sample, AVChannelLayout, AVSamples},
4+
ffi::{self, AV_CHANNEL_LAYOUT_STEREO, AV_CHANNEL_LAYOUT_SURROUND},
55
swresample::SwrContext,
66
};
7-
use std::io::Write;
7+
use std::{f64::consts::PI, io::Write};
8+
9+
fn get_format_from_sample_fmt(sample_fmt: ffi::AVSampleFormat) -> Option<&'static str> {
10+
#[cfg(target_endian = "big")]
11+
let sample_fmt_entries = [
12+
(ffi::AV_SAMPLE_FMT_U8, "u8"),
13+
(ffi::AV_SAMPLE_FMT_S16, "s16be"),
14+
(ffi::AV_SAMPLE_FMT_S32, "s32be"),
15+
(ffi::AV_SAMPLE_FMT_FLT, "f32be"),
16+
(ffi::AV_SAMPLE_FMT_DBL, "f64be"),
17+
];
18+
#[cfg(target_endian = "little")]
19+
let sample_fmt_entries = [
20+
(ffi::AV_SAMPLE_FMT_U8, "u8"),
21+
(ffi::AV_SAMPLE_FMT_S16, "s16le"),
22+
(ffi::AV_SAMPLE_FMT_S32, "s32le"),
23+
(ffi::AV_SAMPLE_FMT_FLT, "f32le"),
24+
(ffi::AV_SAMPLE_FMT_DBL, "f64le"),
25+
];
26+
sample_fmt_entries
27+
.iter()
28+
.find(|(fmt, _)| *fmt == sample_fmt)
29+
.map(|(_, fmt)| *fmt)
30+
}
831

932
fn fill_samples(
1033
buf: &mut [f64],
@@ -14,7 +37,7 @@ fn fill_samples(
1437
t: &mut f64,
1538
) {
1639
let tincr = 1.0 / sample_rate as f64;
17-
let c = 2.0 * std::f64::consts::PI * 440.0;
40+
let c = 2.0 * PI * 440.0;
1841
let mut dstp = buf.as_mut_ptr();
1942
for _ in 0..nb_samples {
2043
unsafe {
@@ -29,52 +52,118 @@ fn fill_samples(
2952
}
3053

3154
pub fn resample_audio_run(out_path: &str) -> anyhow::Result<usize> {
32-
// src: stereo dbl 48k, dst: 5.1 s16 44.1k
33-
let src_ch = AVChannelLayout::from_nb_channels(2);
34-
let dst_ch = AVChannelLayout::from_nb_channels(6);
55+
// Match C example: src=stereo 48kHz DBL, dst=surround 44.1kHz S16
56+
let src_ch_layout = unsafe { AVChannelLayout::new(AV_CHANNEL_LAYOUT_STEREO) }; // stereo
57+
let dst_ch_layout = unsafe { AVChannelLayout::new(AV_CHANNEL_LAYOUT_SURROUND) }; // surround
58+
let src_rate = 48000;
59+
let dst_rate = 44100;
60+
let src_sample_fmt = ffi::AV_SAMPLE_FMT_DBL;
61+
let dst_sample_fmt = ffi::AV_SAMPLE_FMT_S16;
62+
let src_nb_samples = 1024;
63+
let src_nb_channels = src_ch_layout.nb_channels;
64+
let dst_nb_channels = dst_ch_layout.nb_channels;
65+
3566
let mut swr = SwrContext::new(
36-
&dst_ch.into_inner(),
37-
ffi::AV_SAMPLE_FMT_S16,
38-
44100,
39-
&src_ch.into_inner(),
40-
ffi::AV_SAMPLE_FMT_DBL,
41-
48000,
67+
&dst_ch_layout,
68+
dst_sample_fmt,
69+
dst_rate,
70+
&src_ch_layout,
71+
src_sample_fmt,
72+
src_rate,
4273
)
4374
.unwrap();
4475
swr.init().unwrap();
4576

46-
// build src frame
47-
let nb = 1024;
48-
let mut src = AVFrame::new();
49-
src.set_nb_samples(nb);
50-
src.set_format(ffi::AV_SAMPLE_FMT_DBL);
51-
src.set_sample_rate(48000);
52-
src.set_ch_layout(AVChannelLayout::from_nb_channels(2).into_inner());
53-
src.get_buffer(0).unwrap();
54-
// For packed formats (non-planar), samples are interleaved per channel in data[0].
55-
let mut t = 0.0f64;
56-
let samples_mut =
57-
unsafe { std::slice::from_raw_parts_mut(src.data[0] as *mut f64, (nb * 2) as usize) };
58-
fill_samples(samples_mut, nb as usize, 2, 48000usize, &mut t);
77+
// Allocate source samples buffer using AVSamples like C example
78+
let src_data = AVSamples::new(src_nb_channels, src_nb_samples, src_sample_fmt, 0).unwrap();
5979

60-
// dst frame (let swr allocate)
61-
let mut dst = AVFrame::new();
62-
dst.set_nb_samples(0);
63-
dst.set_format(ffi::AV_SAMPLE_FMT_S16);
64-
dst.set_sample_rate(44100);
65-
dst.set_ch_layout(AVChannelLayout::from_nb_channels(6).into_inner());
80+
// Calculate destination buffer size with some extra space for resampling
81+
let max_dst_nb_samples = av_rescale_rnd(
82+
src_nb_samples as i64,
83+
dst_rate as i64,
84+
src_rate as i64,
85+
ffi::AV_ROUND_UP,
86+
) as i32;
6687

67-
swr.convert_frame(Some(&src), &mut dst).unwrap();
88+
let mut dst_data =
89+
AVSamples::new(dst_nb_channels, max_dst_nb_samples, dst_sample_fmt, 1).unwrap();
6890

69-
// Write interleaved s16 to file: nb_samples * channels * 2 bytes
70-
let bytes = (dst.nb_samples as usize) * (dst.ch_layout.nb_channels as usize) * 2usize;
7191
let mut f = std::fs::File::create(out_path)?;
72-
unsafe {
73-
let p = dst.data[0] as *const u8;
74-
let buf = std::slice::from_raw_parts(p, bytes);
75-
f.write_all(buf)?;
92+
let mut t = 0.0f64;
93+
let mut total_bytes = 0usize;
94+
95+
// Generate 10 seconds of audio like C example
96+
loop {
97+
// Generate synthetic audio (sine wave at 440Hz)
98+
let samples_mut = unsafe {
99+
std::slice::from_raw_parts_mut(
100+
src_data.audio_data[0] as *mut f64,
101+
(src_nb_samples * src_nb_channels) as usize,
102+
)
103+
};
104+
fill_samples(
105+
samples_mut,
106+
src_nb_samples as usize,
107+
src_nb_channels as usize,
108+
src_rate as usize,
109+
&mut t,
110+
);
111+
112+
// Calculate actual destination samples needed including buffered samples
113+
let dst_nb_samples = av_rescale_rnd(
114+
swr.get_delay(src_rate as usize) as i64 + src_nb_samples as i64,
115+
dst_rate as i64,
116+
src_rate as i64,
117+
ffi::AV_ROUND_UP,
118+
) as i32;
119+
120+
// Reallocate destination buffer if needed
121+
if dst_nb_samples > max_dst_nb_samples {
122+
dst_data = AVSamples::new(dst_nb_channels, dst_nb_samples, dst_sample_fmt, 1).unwrap();
123+
}
124+
125+
// Convert to destination format
126+
let ret = unsafe {
127+
swr.convert(
128+
dst_data.audio_data.as_mut_ptr(),
129+
dst_nb_samples,
130+
src_data.audio_data.as_ptr() as *const *const u8,
131+
src_nb_samples,
132+
)
133+
}
134+
.unwrap();
135+
136+
let dst_bufsize = get_bytes_per_sample(dst_sample_fmt).unwrap() as usize
137+
* ret as usize
138+
* dst_nb_channels as usize;
139+
140+
println!("t:{:.6} in:{} out:{}", t, src_nb_samples, ret);
141+
142+
unsafe {
143+
let p = dst_data.audio_data[0] as *const u8;
144+
let buf = std::slice::from_raw_parts(p, dst_bufsize);
145+
f.write_all(buf)?;
146+
}
147+
total_bytes += dst_bufsize;
148+
149+
if t >= 10.0 {
150+
break;
151+
}
76152
}
77-
Ok(bytes)
153+
154+
let fmt = get_format_from_sample_fmt(dst_sample_fmt).unwrap_or("?");
155+
let layout_str = dst_ch_layout
156+
.describe()
157+
.map(|x| x.to_string_lossy().into_owned())
158+
.unwrap_or_else(|_| "unknown".into());
159+
160+
eprintln!(
161+
"Resampling succeeded. Play the output file with the command:\n\
162+
ffplay -f {} -channel_layout {} -channels {} -ar {} {}",
163+
fmt, layout_str, dst_nb_channels, dst_rate, out_path
164+
);
165+
166+
Ok(total_bytes)
78167
}
79168

80169
#[test]

0 commit comments

Comments
 (0)