aotuv_lancer_vorbis_sys/
lib.rs

1#![allow(non_camel_case_types)]
2#![allow(non_snake_case)]
3
4// This `extern crate` is required for the linker to honor the linker flags `ogg_next_sys` sets on
5// its build script, and therefore make the Ogg library symbols available at runtime. See:
6// https://doc.rust-lang.org/nightly/reference/items/extern-crates.html#r-items.extern-crate.lookup
7extern crate ogg_next_sys;
8
9use ogg_next_sys::*;
10
11include!("bindings.rs");
12
13#[cfg(test)]
14mod test {
15	use std::{
16		ffi::CStr,
17		io::{Cursor, Read, Write},
18		mem::MaybeUninit,
19		os::raw::{c_int, c_long, c_void},
20		ptr, slice
21	};
22
23	use ogg_next_sys::{ogg_stream_init, ogg_stream_packetin};
24
25	use super::*;
26
27	#[test]
28	fn roundtrip_decode_and_encode() {
29		let mut input_data =
30			Cursor::new(&include_bytes!("8khz_500ms_mono_400hz_sine_wave.ogg")[..]);
31		let mut output_buffer = Cursor::new(vec![]);
32
33		// This example is hacked together by translating the encoder_example.c libvorbis
34		// example to Rust. It does not do proper error handling, and is not intended to be
35		// a proper usage example. We just want to make sure that the bindings work
36		unsafe {
37			// Tear-up decoding
38
39			let mut ogg_vorbis_file = MaybeUninit::uninit();
40			assert_eq!(
41				ov_open_callbacks(
42					&mut input_data as *mut Cursor<&[u8]> as *mut c_void,
43					ogg_vorbis_file.as_mut_ptr(),
44					ptr::null(),
45					0,
46					ov_callbacks {
47						read_func: {
48							// This read callback should match the stdio fread behavior.
49							// See: https://man7.org/linux/man-pages/man3/fread.3.html
50							unsafe extern "C" fn read_func(
51								ptr: *mut std::ffi::c_void,
52								size: usize,
53								count: usize,
54								datasource: *mut std::ffi::c_void
55							) -> usize {
56								let data = &mut *(datasource as *mut Cursor<&[u8]>);
57								let buf = slice::from_raw_parts_mut(ptr as *mut u8, size * count);
58								match data.read(buf) {
59									Ok(n) => n / size,
60									Err(_) => 0
61								}
62							}
63							Some(read_func)
64						},
65						seek_func: None,
66						close_func: None,
67						tell_func: None
68					}
69				),
70				0
71			);
72			let ogg_vorbis_file = ogg_vorbis_file.assume_init_mut();
73
74			let ogg_vorbis_info = &*ov_info(ogg_vorbis_file, -1);
75			eprintln!(
76				"Encoder version string: {}",
77				CStr::from_ptr(vorbis_version_string())
78					.to_str()
79					.unwrap_or_default()
80			);
81			eprintln!("Bitrate: {}", ov_bitrate(ogg_vorbis_file, -1));
82			eprintln!("Channels: {}", ogg_vorbis_info.channels);
83			eprintln!("Sampling frequency: {}", ogg_vorbis_info.rate);
84
85			// Tear-up encode with libvorbisenc and libvorbis
86
87			let mut vorbis_info = MaybeUninit::uninit();
88			vorbis_info_init(vorbis_info.as_mut_ptr());
89			let vorbis_info = vorbis_info.assume_init_mut();
90
91			assert_eq!(
92				// Pure VBR chosen by quality factor, no bitrate management engine
93				vorbis_encode_init_vbr(
94					vorbis_info,
95					ogg_vorbis_info.channels as c_long,
96					ogg_vorbis_info.rate,
97					-0.2 // The worst possible quality for aoTuV for the smallest size
98				),
99				0
100			);
101
102			let mut vorbis_dsp_state = MaybeUninit::uninit();
103			assert_eq!(
104				vorbis_analysis_init(vorbis_dsp_state.as_mut_ptr(), vorbis_info),
105				0
106			);
107			let vorbis_dsp_state = vorbis_dsp_state.assume_init_mut();
108
109			let mut vorbis_block = MaybeUninit::uninit();
110			assert_eq!(
111				vorbis_block_init(vorbis_dsp_state, vorbis_block.as_mut_ptr()),
112				0
113			);
114			let vorbis_block = vorbis_block.assume_init_mut();
115
116			let mut vorbis_comment = MaybeUninit::uninit();
117			vorbis_comment_init(vorbis_comment.as_mut_ptr());
118			let vorbis_comment = vorbis_comment.assume_init_mut();
119
120			// Generate header packets
121
122			let mut vorbis_identification_packet = MaybeUninit::uninit();
123			let mut vorbis_comment_packet = MaybeUninit::uninit();
124			let mut vorbis_setup_packet = MaybeUninit::uninit();
125			assert_eq!(
126				vorbis_analysis_headerout(
127					vorbis_dsp_state,
128					vorbis_comment,
129					vorbis_identification_packet.as_mut_ptr(),
130					vorbis_comment_packet.as_mut_ptr(),
131					vorbis_setup_packet.as_mut_ptr()
132				),
133				0
134			);
135			let vorbis_identification_packet = vorbis_identification_packet.assume_init_mut();
136			let vorbis_comment_packet = vorbis_comment_packet.assume_init_mut();
137			let vorbis_setup_packet = vorbis_setup_packet.assume_init_mut();
138
139			// Tear-up Ogg stream and write header packets
140
141			let mut ogg_stream = MaybeUninit::uninit();
142			assert_eq!(ogg_stream_init(ogg_stream.as_mut_ptr(), 0), 0);
143			let ogg_stream = ogg_stream.assume_init_mut();
144
145			assert_eq!(
146				ogg_stream_packetin(ogg_stream, vorbis_identification_packet),
147				0
148			);
149			assert_eq!(ogg_stream_packetin(ogg_stream, vorbis_comment_packet), 0);
150			assert_eq!(ogg_stream_packetin(ogg_stream, vorbis_setup_packet), 0);
151
152			let mut ogg_page = MaybeUninit::uninit();
153			loop {
154				if ogg_stream_flush(ogg_stream, ogg_page.as_mut_ptr()) == 0 {
155					break;
156				}
157				let ogg_page = ogg_page.assume_init_mut();
158
159				// Write final page to the output
160				output_buffer
161					.write_all(slice::from_raw_parts(
162						ogg_page.header,
163						ogg_page.header_len as usize
164					))
165					.unwrap();
166				output_buffer
167					.write_all(slice::from_raw_parts(
168						ogg_page.body,
169						ogg_page.body_len as usize
170					))
171					.unwrap();
172			}
173			let ogg_page = ogg_page.assume_init_mut();
174
175			// Encode loop
176
177			let mut sample_source_buf = MaybeUninit::uninit();
178			let mut bitstream_id = MaybeUninit::uninit();
179			let mut previous_bitstream_id = None;
180
181			loop {
182				let samples_read = ov_read_float(
183					ogg_vorbis_file,
184					sample_source_buf.as_mut_ptr(),
185					1024,
186					bitstream_id.as_mut_ptr()
187				);
188				assert!(samples_read >= 0);
189				let sample_source_buf = *sample_source_buf.assume_init_mut();
190
191				let bitstream_id = *bitstream_id.assume_init_mut();
192				assert!(
193					previous_bitstream_id.is_none() || previous_bitstream_id == Some(bitstream_id),
194					"Chained Ogg Vorbis files are not supported"
195				);
196				previous_bitstream_id = Some(bitstream_id);
197
198				if samples_read == 0 {
199					// Signal EOS
200					assert_eq!(vorbis_analysis_wrote(vorbis_dsp_state, 0), 0);
201				} else {
202					let sample_sink_buf =
203						vorbis_analysis_buffer(vorbis_dsp_state, samples_read as c_int);
204
205					// Copy the samples for each input channel to each output channel
206					for i in 0..ogg_vorbis_info.channels {
207						(*sample_source_buf.offset(i as isize)).copy_to_nonoverlapping(
208							*sample_sink_buf.offset(i as isize),
209							samples_read as usize
210						);
211					}
212
213					assert_eq!(
214						vorbis_analysis_wrote(vorbis_dsp_state, samples_read as c_int),
215						0
216					);
217				}
218
219				// Poll for Vorbis audio blocks in packets. Write those packets to Ogg pages
220				loop {
221					let blockout_result = vorbis_analysis_blockout(vorbis_dsp_state, vorbis_block);
222					assert!(blockout_result >= 0);
223					if blockout_result != 1 {
224						break;
225					}
226
227					assert_eq!(vorbis_analysis(vorbis_block, ptr::null_mut()), 0);
228					assert_eq!(vorbis_bitrate_addblock(vorbis_block), 0);
229
230					loop {
231						let mut ogg_packet = MaybeUninit::uninit();
232						let flushpacket_result =
233							vorbis_bitrate_flushpacket(vorbis_dsp_state, ogg_packet.as_mut_ptr());
234						assert!(flushpacket_result >= 0);
235						if flushpacket_result != 1 {
236							break;
237						}
238
239						ogg_stream_packetin(ogg_stream, ogg_packet.assume_init_mut());
240						if ogg_stream_pageout(ogg_stream, ogg_page) != 0 {
241							output_buffer
242								.write_all(slice::from_raw_parts(
243									ogg_page.header,
244									ogg_page.header_len as usize
245								))
246								.unwrap();
247							output_buffer
248								.write_all(slice::from_raw_parts(
249									ogg_page.body,
250									ogg_page.body_len as usize
251								))
252								.unwrap();
253						}
254					}
255				}
256
257				if samples_read == 0 {
258					break;
259				}
260			}
261
262			// libogg cleanup
263			ogg_stream_clear(ogg_stream);
264
265			// libvorbis and libvorbisenc cleanup
266			vorbis_dsp_clear(vorbis_dsp_state);
267			vorbis_block_clear(vorbis_block);
268			vorbis_comment_clear(vorbis_comment);
269			vorbis_info_clear(vorbis_info);
270
271			// libvorbisfile cleanup
272			ov_clear(ogg_vorbis_file);
273		}
274
275		// Weak sanity check for the output buffer to make sense
276		assert_eq!(&output_buffer.into_inner()[..4], b"OggS");
277	}
278}