1use libc::{c_char, c_int};
5use log::info;
6use parking_lot::Once;
7use std::convert::TryFrom;
8use std::ffi::CStr;
9use std::fmt::{self, Write};
10
11static START: Once = Once::new();
12
13pub mod avcodec;
14pub mod avformat;
15pub mod avutil;
16#[cfg(feature = "swscale")]
17pub mod swscale;
18
19pub use avutil::Error;
20
21type RustLogCallback = extern "C" fn(
22 avc_item_name: *const c_char,
23 avc: *const libc::c_void,
24 level: libc::c_int,
25 fmt: *const c_char,
26 vl: *mut libc::c_void,
27);
28
29extern "C" {
31 static moonfire_ffmpeg_version: *const libc::c_char;
32
33 fn moonfire_ffmpeg_init(cb: RustLogCallback);
34
35 fn moonfire_ffmpeg_vsnprintf(
36 buf: *mut u8,
37 size: usize,
38 fmt: *const c_char,
39 vl: *mut libc::c_void,
40 ) -> c_int;
41}
42
43pub struct Ffmpeg {}
44
45#[derive(Copy, Clone)]
46struct Version(libc::c_int);
47
48impl Version {
49 fn major(self) -> libc::c_int {
50 (self.0 >> 16) & 0xFF
51 }
52 fn minor(self) -> libc::c_int {
53 (self.0 >> 8) & 0xFF
54 }
55}
56
57impl fmt::Display for Version {
58 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59 write!(
60 f,
61 "{}.{}.{}",
62 (self.0 >> 16) & 0xFF,
63 (self.0 >> 8) & 0xFF,
64 self.0 & 0xFF
65 )
66 }
67}
68
69struct Library {
70 name: &'static str,
71 compiled: Version,
72 running: Version,
73 configuration: &'static CStr,
74}
75
76impl Library {
77 fn new(
78 name: &'static str,
79 compiled: libc::c_int,
80 running: libc::c_int,
81 configuration: &'static CStr,
82 ) -> Self {
83 Library {
84 name,
85 compiled: Version(compiled),
86 running: Version(running),
87 configuration,
88 }
89 }
90
91 fn is_compatible(&self) -> bool {
92 self.running.major() == self.compiled.major()
93 && self.running.minor() >= self.compiled.minor()
94 }
95}
96
97impl fmt::Display for Library {
98 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99 write!(
102 f,
103 "{}: compiled={} running={} configuration={:?}",
104 self.name, self.compiled, self.running, self.configuration
105 )
106 }
107}
108
109thread_local! {
120 static LOG_BUF: std::cell::RefCell<Vec<u8>> = std::cell::RefCell::new(Vec::with_capacity(1024));
121}
122
123unsafe fn append_vprintf(buf: &mut Vec<u8>, fmt: *const libc::c_char, vl: *mut libc::c_void) {
125 let len = buf.len();
126 let left = buf.capacity() - len;
127 let ret = moonfire_ffmpeg_vsnprintf(buf.as_mut_ptr().add(len), left, fmt, vl);
128 let ret = match usize::try_from(ret) {
129 Ok(r) => r,
130 Err(_) => {
131 buf.extend(b"(vsnprintf failed)");
132 return;
133 }
134 };
135 if ret >= left {
136 buf.reserve(ret + 1);
139 let ret2 = moonfire_ffmpeg_vsnprintf(buf.as_mut_ptr().add(len), ret + 1, fmt, vl);
140 assert_eq!(
141 usize::try_from(ret2).expect("2nd vsnprintf should succeed"),
142 ret
143 );
144 }
145 buf.set_len(len + ret);
146}
147
148extern "C" fn log_callback(
152 avc_item_name: *const c_char,
153 avc: *const libc::c_void,
154 level: libc::c_int,
155 fmt: *const c_char,
156 vl: *mut libc::c_void,
157) {
158 let log_level = avutil::convert_level(level);
159
160 if log::max_level()
162 .to_level()
163 .map(|l| l < log_level)
164 .unwrap_or(true)
165 {
166 return;
167 }
168 let avc_item_name = if avc_item_name.is_null() {
169 "null"
170 } else {
171 unsafe { CStr::from_ptr(avc_item_name) }
172 .to_str()
173 .unwrap_or("bad_utf8")
174 };
175 let target = format!("moonfire_ffmpeg::{}", avc_item_name);
176 let metadata = log::Metadata::builder()
177 .level(avutil::convert_level(level))
178 .target(&target)
179 .build();
180 let logger = log::logger();
181 if !logger.enabled(&metadata) {
182 return;
183 }
184
185 LOG_BUF.with(move |b| {
186 unsafe { log_callback_inner(&mut *b.borrow_mut(), logger, metadata, avc, fmt, vl) };
187 });
188}
189
190unsafe fn log_callback_inner(
192 buf: &mut Vec<u8>,
193 logger: &dyn log::Log,
194 metadata: log::Metadata,
195 avc: *const libc::c_void,
196 fmt: *const c_char,
197 vl: *mut libc::c_void,
198) {
199 append_vprintf(buf, fmt, vl);
200
201 if !buf
202 .last()
203 .map(|&b| b == b'\r' || b == b'\n')
204 .unwrap_or(false)
205 {
206 return; }
208
209 for c in buf.iter_mut() {
212 if *c < 0x08 || (*c > 0x0D && *c < 0x20) {
213 *c = b'?';
214 }
215 }
216
217 let s = String::from_utf8_lossy(&buf[0..buf.len() - 1]);
218 let target = metadata.target();
219 logger.log(
220 &log::RecordBuilder::new()
221 .args(format_args!("{:?}: {}", avc, &s))
222 .metadata(metadata)
223 .module_path(Some(target))
224 .build(),
225 );
226 buf.clear();
227}
228
229impl Ffmpeg {
230 pub fn new() -> Ffmpeg {
231 START.call_once(|| unsafe {
232 moonfire_ffmpeg_init(log_callback);
235
236 let libs = &[
237 Library::new(
238 "avutil",
239 avutil::moonfire_ffmpeg_compiled_libavutil_version,
240 avutil::avutil_version(),
241 CStr::from_ptr(avutil::avutil_configuration()),
242 ),
243 Library::new(
244 "avcodec",
245 avcodec::moonfire_ffmpeg_compiled_libavcodec_version,
246 avcodec::avcodec_version(),
247 CStr::from_ptr(avcodec::avcodec_configuration()),
248 ),
249 Library::new(
250 "avformat",
251 avformat::moonfire_ffmpeg_compiled_libavformat_version,
252 avformat::avformat_version(),
253 CStr::from_ptr(avformat::avformat_configuration()),
254 ),
255 #[cfg(feature = "swscale")]
256 Library::new(
257 "swscale",
258 swscale::moonfire_ffmpeg_compiled_libswscale_version,
259 swscale::swscale_version(),
260 CStr::from_ptr(swscale::swscale_configuration()),
261 ),
262 ];
263 let mut msg = format!(
264 "\ncompiled={:?} running={:?}",
265 CStr::from_ptr(moonfire_ffmpeg_version),
266 CStr::from_ptr(avutil::av_version_info())
267 );
268 let mut compatible = true;
269 for l in libs {
270 write!(&mut msg, "\n{}", l).unwrap();
271 if !l.is_compatible() {
272 compatible = false;
273 msg.push_str(" <- not ABI-compatible!");
274 }
275 }
276 if !compatible {
277 panic!("Incompatible ffmpeg versions:{}", msg);
278 }
279 avformat::av_register_all();
280 if avformat::avformat_network_init() < 0 {
281 panic!("avformat_network_init failed");
282 }
283 info!("Initialized ffmpeg. Versions:{}", msg);
284 });
285 Ffmpeg {}
286 }
287}
288
289#[cfg(test)]
290mod tests {
291 use crate::avutil;
292 use cstr::*;
293 use parking_lot::Mutex;
294
295 #[test]
297 fn test_init() {
298 super::Ffmpeg::new();
299 }
300
301 #[test]
302 fn test_is_compatible() {
303 use ::libc::c_int;
305 struct Test(c_int, c_int, c_int, c_int, c_int, c_int, bool);
306
307 let tests = &[
308 Test(55, 1, 2, 55, 1, 2, true), Test(55, 1, 2, 55, 2, 1, true), Test(55, 1, 3, 55, 1, 2, true), Test(55, 2, 2, 55, 1, 2, false), Test(55, 1, 2, 56, 1, 2, false), Test(56, 1, 2, 55, 1, 2, false), ];
315
316 for t in tests {
317 let l = super::Library::new(
318 "avutil",
319 (t.0 << 16) | (t.1 << 8) | t.2,
320 (t.3 << 16) | (t.4 << 8) | t.5,
321 cstr!(""),
322 );
323 assert!(l.is_compatible() == t.6, "{} expected={}", l, t.6);
324 }
325 }
326
327 struct DummyLogger(Mutex<Vec<String>>);
328
329 impl log::Log for DummyLogger {
330 fn enabled(&self, _metadata: &log::Metadata) -> bool {
331 true
332 }
333 fn log(&self, record: &log::Record) {
334 let mut l = self.0.lock();
335 l.push(format!(
336 "{}: {}: {}",
337 record.level(),
338 record.target(),
339 record.args()
340 ));
341 }
342 fn flush(&self) {}
343 }
344
345 #[test]
346 fn test_logging() {
347 super::Ffmpeg::new();
348 let logger = Box::leak(Box::new(DummyLogger(Mutex::new(Vec::new()))));
349 log::set_logger(logger).unwrap();
350 log::set_max_level(log::LevelFilter::Trace);
351 unsafe {
352 avutil::av_log(
353 std::ptr::null(),
354 avutil::AV_LOG_INFO,
355 cstr!("foo %d\n").as_ptr(),
356 42 as i32,
357 );
358 avutil::av_log(
359 std::ptr::null(),
360 avutil::AV_LOG_INFO,
361 cstr!("partial ").as_ptr(),
362 1,
363 );
364 avutil::av_log(
365 std::ptr::null(),
366 avutil::AV_LOG_INFO,
367 cstr!("log\n").as_ptr(),
368 1,
369 );
370 avutil::av_log(
371 std::ptr::null(),
372 avutil::AV_LOG_INFO,
373 cstr!("bar\n").as_ptr(),
374 );
375 };
376 let l = logger.0.lock();
377 println!("{:?}", &l[..]);
378 assert_eq!(l.len(), 3);
379 assert_eq!(&l[0][..], "INFO: moonfire_ffmpeg::null: 0x0: foo 42");
380 assert_eq!(&l[1][..], "INFO: moonfire_ffmpeg::null: 0x0: partial log");
381 assert_eq!(&l[2][..], "INFO: moonfire_ffmpeg::null: 0x0: bar");
382 }
383}