1 // Copyright (C) 2016-2017 Sebastian Dröge <sebastian@centricular.com>
2 //
3 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
6 // option. This file may not be copied, modified, or distributed
7 // except according to those terms.
8
9 use DebugLevel;
10
11 use libc::c_char;
12 use std::borrow::Cow;
13 use std::ffi::CStr;
14 use std::fmt;
15 use std::ptr;
16
17 use gobject_sys;
18 use gst_sys;
19
20 use glib::translate::{from_glib, from_glib_borrow, ToGlib, ToGlibPtr};
21 use glib::IsA;
22 use glib_sys::gpointer;
23
24 #[derive(PartialEq, Eq)]
25 pub struct DebugMessage(ptr::NonNull<gst_sys::GstDebugMessage>);
26
27 impl fmt::Debug for DebugMessage {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result28 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29 f.debug_tuple("DebugMessage").field(&self.get()).finish()
30 }
31 }
32
33 impl DebugMessage {
get(&self) -> Option<Cow<str>>34 pub fn get(&self) -> Option<Cow<str>> {
35 unsafe {
36 let message = gst_sys::gst_debug_message_get(self.0.as_ptr());
37
38 if message.is_null() {
39 None
40 } else {
41 Some(CStr::from_ptr(message).to_string_lossy())
42 }
43 }
44 }
45 }
46
47 #[derive(PartialEq, Eq, Clone, Copy)]
48 pub struct DebugCategory(ptr::NonNull<gst_sys::GstDebugCategory>);
49
50 impl DebugCategory {
new(name: &str, color: ::DebugColorFlags, description: Option<&str>) -> DebugCategory51 pub fn new(name: &str, color: ::DebugColorFlags, description: Option<&str>) -> DebugCategory {
52 extern "C" {
53 fn _gst_debug_category_new(
54 name: *const c_char,
55 color: gst_sys::GstDebugColorFlags,
56 description: *const c_char,
57 ) -> *mut gst_sys::GstDebugCategory;
58 }
59
60 // Gets the category if it exists already
61 unsafe {
62 let ptr = _gst_debug_category_new(
63 name.to_glib_none().0,
64 color.to_glib(),
65 description.to_glib_none().0,
66 );
67 assert!(!ptr.is_null());
68 DebugCategory(ptr::NonNull::new_unchecked(ptr))
69 }
70 }
71
get(name: &str) -> Option<DebugCategory>72 pub fn get(name: &str) -> Option<DebugCategory> {
73 unsafe {
74 extern "C" {
75 fn _gst_debug_get_category(name: *const c_char) -> *mut gst_sys::GstDebugCategory;
76 }
77
78 let cat = _gst_debug_get_category(name.to_glib_none().0);
79
80 if cat.is_null() {
81 None
82 } else {
83 Some(DebugCategory(ptr::NonNull::new_unchecked(cat)))
84 }
85 }
86 }
87
get_threshold(self) -> ::DebugLevel88 pub fn get_threshold(self) -> ::DebugLevel {
89 from_glib(unsafe { gst_sys::gst_debug_category_get_threshold(self.0.as_ptr()) })
90 }
91
set_threshold(self, threshold: ::DebugLevel)92 pub fn set_threshold(self, threshold: ::DebugLevel) {
93 unsafe { gst_sys::gst_debug_category_set_threshold(self.0.as_ptr(), threshold.to_glib()) }
94 }
95
reset_threshold(self)96 pub fn reset_threshold(self) {
97 unsafe { gst_sys::gst_debug_category_reset_threshold(self.0.as_ptr()) }
98 }
99
get_color(self) -> ::DebugColorFlags100 pub fn get_color(self) -> ::DebugColorFlags {
101 unsafe { from_glib(gst_sys::gst_debug_category_get_color(self.0.as_ptr())) }
102 }
103
get_name<'a>(self) -> &'a str104 pub fn get_name<'a>(self) -> &'a str {
105 unsafe {
106 CStr::from_ptr(gst_sys::gst_debug_category_get_name(self.0.as_ptr()))
107 .to_str()
108 .unwrap()
109 }
110 }
111
get_description<'a>(self) -> Option<&'a str>112 pub fn get_description<'a>(self) -> Option<&'a str> {
113 unsafe {
114 let ptr = gst_sys::gst_debug_category_get_description(self.0.as_ptr());
115
116 if ptr.is_null() {
117 None
118 } else {
119 Some(CStr::from_ptr(ptr).to_str().unwrap())
120 }
121 }
122 }
123
124 #[inline]
log<O: IsA<::Object>>( self, obj: Option<&O>, level: ::DebugLevel, file: &str, module: &str, line: u32, args: fmt::Arguments, )125 pub fn log<O: IsA<::Object>>(
126 self,
127 obj: Option<&O>,
128 level: ::DebugLevel,
129 file: &str,
130 module: &str,
131 line: u32,
132 args: fmt::Arguments,
133 ) {
134 unsafe {
135 if level.to_glib() as i32 > self.0.as_ref().threshold {
136 return;
137 }
138 }
139
140 let obj_ptr = match obj {
141 Some(obj) => obj.to_glib_none().0 as *mut gobject_sys::GObject,
142 None => ptr::null_mut(),
143 };
144
145 unsafe {
146 gst_sys::gst_debug_log(
147 self.0.as_ptr(),
148 level.to_glib(),
149 file.to_glib_none().0,
150 module.to_glib_none().0,
151 line as i32,
152 obj_ptr,
153 fmt::format(args).replace("%", "%%").to_glib_none().0,
154 );
155 }
156 }
157 }
158
159 unsafe impl Sync for DebugCategory {}
160 unsafe impl Send for DebugCategory {}
161
162 impl fmt::Debug for DebugCategory {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result163 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
164 f.debug_tuple("DebugCategory")
165 .field(&self.get_name())
166 .finish()
167 }
168 }
169
170 lazy_static! {
171 pub static ref CAT_RUST: DebugCategory = DebugCategory::new(
172 "GST_RUST",
173 ::DebugColorFlags::UNDERLINE,
174 Some("GStreamer's Rust binding core"),
175 );
176 }
177
178 macro_rules! declare_debug_category_from_name(
179 ($cat:ident, $cat_name:expr) => (
180 lazy_static! {
181 pub static ref $cat: DebugCategory = DebugCategory::get($cat_name)
182 .expect(&format!("Unable to find `DebugCategory` with name {}", $cat_name));
183 }
184 );
185 );
186
187 declare_debug_category_from_name!(CAT_DEFAULT, "default");
188 declare_debug_category_from_name!(CAT_GST_INIT, "GST_INIT");
189 declare_debug_category_from_name!(CAT_MEMORY, "GST_MEMORY");
190 declare_debug_category_from_name!(CAT_PARENTAGE, "GST_PARENTAGE");
191 declare_debug_category_from_name!(CAT_STATES, "GST_STATES");
192 declare_debug_category_from_name!(CAT_SCHEDULING, "GST_SCHEDULING");
193 declare_debug_category_from_name!(CAT_BUFFER, "GST_BUFFER");
194 declare_debug_category_from_name!(CAT_BUFFER_LIST, "GST_BUFFER_LIST");
195 declare_debug_category_from_name!(CAT_BUS, "GST_BUS");
196 declare_debug_category_from_name!(CAT_CAPS, "GST_CAPS");
197 declare_debug_category_from_name!(CAT_CLOCK, "GST_CLOCK");
198 declare_debug_category_from_name!(CAT_ELEMENT_PADS, "GST_ELEMENT_PADS");
199 declare_debug_category_from_name!(CAT_PADS, "GST_PADS");
200 declare_debug_category_from_name!(CAT_PERFORMANCE, "GST_PERFORMANCE");
201 declare_debug_category_from_name!(CAT_PIPELINE, "GST_PIPELINE");
202 declare_debug_category_from_name!(CAT_PLUGIN_LOADING, "GST_PLUGIN_LOADING");
203 declare_debug_category_from_name!(CAT_PLUGIN_INFO, "GST_PLUGIN_INFO");
204 declare_debug_category_from_name!(CAT_PROPERTIES, "GST_PROPERTIES");
205 declare_debug_category_from_name!(CAT_NEGOTIATION, "GST_NEGOTIATION");
206 declare_debug_category_from_name!(CAT_REFCOUNTING, "GST_REFCOUNTING");
207 declare_debug_category_from_name!(CAT_ERROR_SYSTEM, "GST_ERROR_SYSTEM");
208 declare_debug_category_from_name!(CAT_EVENT, "GST_EVENT");
209 declare_debug_category_from_name!(CAT_MESSAGE, "GST_MESSAGE");
210 declare_debug_category_from_name!(CAT_PARAMS, "GST_PARAMS");
211 declare_debug_category_from_name!(CAT_CALL_TRACE, "GST_CALL_TRACE");
212 declare_debug_category_from_name!(CAT_SIGNAL, "GST_SIGNAL");
213 declare_debug_category_from_name!(CAT_PROBE, "GST_PROBE");
214 declare_debug_category_from_name!(CAT_REGISTRY, "GST_REGISTRY");
215 declare_debug_category_from_name!(CAT_QOS, "GST_QOS");
216 declare_debug_category_from_name!(CAT_META, "GST_META");
217 declare_debug_category_from_name!(CAT_LOCKING, "GST_LOCKING");
218 declare_debug_category_from_name!(CAT_CONTEXT, "GST_CONTEXT");
219
220 #[macro_export]
221 macro_rules! gst_error(
222 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
223 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, obj: $obj, $($args)*)
224 }};
225 ($cat:expr, $($args:tt)*) => { {
226 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, $($args)*)
227 }};
228 );
229
230 #[macro_export]
231 macro_rules! gst_warning(
232 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
233 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, obj: $obj, $($args)*)
234 }};
235 ($cat:expr, $($args:tt)*) => { {
236 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, $($args)*)
237 }};
238 );
239
240 #[macro_export]
241 macro_rules! gst_fixme(
242 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
243 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, obj: $obj, $($args)*)
244 }};
245 ($cat:expr, $($args:tt)*) => { {
246 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, $($args)*)
247 }};
248 );
249
250 #[macro_export]
251 macro_rules! gst_info(
252 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
253 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, obj: $obj, $($args)*)
254 }};
255 ($cat:expr, $($args:tt)*) => { {
256 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, $($args)*)
257 }};
258 );
259
260 #[macro_export]
261 macro_rules! gst_debug(
262 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
263 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, obj: $obj, $($args)*)
264 }};
265 ($cat:expr, $($args:tt)*) => { {
266 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, $($args)*)
267 }};
268 );
269
270 #[macro_export]
271 macro_rules! gst_log(
272 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
273 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, obj: $obj, $($args)*)
274 }};
275 ($cat:expr, $($args:tt)*) => { {
276 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, $($args)*)
277 }};
278 );
279
280 #[macro_export]
281 macro_rules! gst_trace(
282 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
283 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, obj: $obj, $($args)*)
284 }};
285 ($cat:expr, $($args:tt)*) => { {
286 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, $($args)*)
287 }};
288 );
289
290 #[macro_export]
291 macro_rules! gst_memdump(
292 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
293 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, obj: $obj, $($args)*)
294 }};
295 ($cat:expr, $($args:tt)*) => { {
296 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, $($args)*)
297 }};
298 );
299
300 #[macro_export]
301 macro_rules! gst_log_with_level(
302 ($cat:expr, level: $level:expr, obj: $obj:expr, $($args:tt)*) => { {
303 $crate::DebugCategory::log($cat.clone(), Some($obj), $level, file!(),
304 module_path!(), line!(), format_args!($($args)*))
305 }};
306 ($cat:expr, level: $level:expr, $($args:tt)*) => { {
307 $crate::DebugCategory::log($cat.clone(), None as Option<&$crate::Object>, $level, file!(),
308 module_path!(), line!(), format_args!($($args)*))
309 }};
310 );
311
log_handler<T>( category: *mut gst_sys::GstDebugCategory, level: gst_sys::GstDebugLevel, file: *const c_char, function: *const c_char, line: i32, object: *mut gobject_sys::GObject, message: *mut gst_sys::GstDebugMessage, user_data: gpointer, ) where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage) + Send + Sync + 'static,312 unsafe extern "C" fn log_handler<T>(
313 category: *mut gst_sys::GstDebugCategory,
314 level: gst_sys::GstDebugLevel,
315 file: *const c_char,
316 function: *const c_char,
317 line: i32,
318 object: *mut gobject_sys::GObject,
319 message: *mut gst_sys::GstDebugMessage,
320 user_data: gpointer,
321 ) where
322 T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage)
323 + Send
324 + Sync
325 + 'static,
326 {
327 let category = DebugCategory(ptr::NonNull::new_unchecked(category));
328 let level = from_glib(level);
329 let file = CStr::from_ptr(file).to_string_lossy();
330 let function = CStr::from_ptr(function).to_string_lossy();
331 let line = line as u32;
332 let object: Option<glib::Object> = from_glib_borrow(object);
333 let message = DebugMessage(ptr::NonNull::new_unchecked(message));
334 let handler = &*(user_data as *mut T);
335 (handler)(
336 category,
337 level,
338 &file,
339 &function,
340 line,
341 object.as_ref(),
342 &message,
343 );
344 }
345
log_handler_data_free<T>(data: gpointer)346 unsafe extern "C" fn log_handler_data_free<T>(data: gpointer) {
347 let data = Box::from_raw(data as *mut T);
348 drop(data);
349 }
350
351 #[derive(Debug)]
352 pub struct DebugLogFunction(ptr::NonNull<std::os::raw::c_void>);
353
354 // The contained pointer is never dereferenced and has no thread affinity.
355 // It may be convenient to send it or share it between threads to allow cleaning
356 // up log functions from other threads than the one that created it.
357 unsafe impl Send for DebugLogFunction {}
358 unsafe impl Sync for DebugLogFunction {}
359
debug_add_log_function<T>(function: T) -> DebugLogFunction where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage) + Send + Sync + 'static,360 pub fn debug_add_log_function<T>(function: T) -> DebugLogFunction
361 where
362 T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&glib::Object>, &DebugMessage)
363 + Send
364 + Sync
365 + 'static,
366 {
367 unsafe {
368 let user_data = Box::new(function);
369 let user_data_ptr = Box::into_raw(user_data) as gpointer;
370 gst_sys::gst_debug_add_log_function(
371 Some(log_handler::<T>),
372 user_data_ptr,
373 Some(log_handler_data_free::<T>),
374 );
375 DebugLogFunction(ptr::NonNull::new_unchecked(user_data_ptr))
376 }
377 }
378
debug_remove_default_log_function()379 pub fn debug_remove_default_log_function() {
380 unsafe {
381 gst_sys::gst_debug_remove_log_function(Some(gst_sys::gst_debug_log_default));
382 }
383 }
384
debug_remove_log_function(log_fn: DebugLogFunction)385 pub fn debug_remove_log_function(log_fn: DebugLogFunction) {
386 unsafe {
387 let removed = gst_sys::gst_debug_remove_log_function_by_data(log_fn.0.as_ptr());
388 assert_eq!(removed, 1);
389 }
390 }
391
392 #[cfg(test)]
393 mod tests {
394 use super::*;
395 use std::sync::mpsc;
396 use std::sync::{Arc, Mutex};
397
398 #[test]
get_existing()399 fn get_existing() {
400 ::init().unwrap();
401
402 let perf_cat = DebugCategory::get("GST_PERFORMANCE")
403 .expect("Unable to find `DebugCategory` with name \"GST_PERFORMANCE\"");
404 assert_eq!(perf_cat.get_name(), CAT_PERFORMANCE.get_name());
405 }
406
407 #[test]
new_and_log()408 fn new_and_log() {
409 ::init().unwrap();
410
411 let cat = DebugCategory::new(
412 "test-cat",
413 ::DebugColorFlags::empty(),
414 Some("some debug category"),
415 );
416
417 gst_error!(cat, "meh");
418 gst_warning!(cat, "meh");
419 gst_fixme!(cat, "meh");
420 gst_info!(cat, "meh");
421 gst_debug!(cat, "meh");
422 gst_log!(cat, "meh");
423 gst_trace!(cat, "meh");
424 gst_memdump!(cat, "meh");
425
426 let obj = ::Bin::new(Some("meh"));
427 gst_error!(cat, obj: &obj, "meh");
428 gst_warning!(cat, obj: &obj, "meh");
429 gst_fixme!(cat, obj: &obj, "meh");
430 gst_info!(cat, obj: &obj, "meh");
431 gst_debug!(cat, obj: &obj, "meh");
432 gst_log!(cat, obj: &obj, "meh");
433 gst_trace!(cat, obj: &obj, "meh");
434 gst_memdump!(cat, obj: &obj, "meh");
435 }
436
437 #[test]
log_handler()438 fn log_handler() {
439 ::init().unwrap();
440
441 let cat = DebugCategory::new(
442 "test-cat-log",
443 ::DebugColorFlags::empty(),
444 Some("some debug category"),
445 );
446 cat.set_threshold(DebugLevel::Info);
447 let obj = ::Bin::new(Some("meh"));
448
449 let (sender, receiver) = mpsc::channel();
450
451 let sender = Arc::new(Mutex::new(sender));
452
453 let handler = move |category: DebugCategory,
454 level: DebugLevel,
455 _file: &str,
456 _function: &str,
457 _line: u32,
458 _object: Option<&glib::Object>,
459 message: &DebugMessage| {
460 let cat = DebugCategory::get("test-cat-log").unwrap();
461
462 if category != cat {
463 // This test can run in parallel with other tests, including new_and_log above.
464 // We cannot be certain we only see our own messages.
465 return;
466 }
467
468 assert_eq!(level, DebugLevel::Info);
469 assert_eq!(&message.get().unwrap(), "meh");
470 let _ = sender.lock().unwrap().send(());
471 };
472
473 debug_remove_default_log_function();
474 let log_fn = debug_add_log_function(handler);
475 gst_info!(cat, obj: &obj, "meh");
476
477 receiver.recv().unwrap();
478
479 debug_remove_log_function(log_fn);
480
481 gst_info!(cat, obj: &obj, "meh2");
482 assert!(receiver.recv().is_err());
483 }
484 }
485