1 // Take a look at the license at the top of the repository in the LICENSE file.
2
3 use crate::DebugLevel;
4
5 use libc::c_char;
6 use std::borrow::Cow;
7 use std::ffi::CStr;
8 use std::fmt;
9 use std::ptr;
10
11 use once_cell::sync::Lazy;
12
13 use glib::ffi::gpointer;
14 use glib::prelude::*;
15 use glib::translate::*;
16
17 #[derive(PartialEq, Eq)]
18 #[doc(alias = "GstDebugMessage")]
19 pub struct DebugMessage(ptr::NonNull<ffi::GstDebugMessage>);
20
21 impl fmt::Debug for DebugMessage {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result22 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
23 f.debug_tuple("DebugMessage").field(&self.get()).finish()
24 }
25 }
26
27 impl DebugMessage {
28 #[doc(alias = "gst_debug_message_get")]
get(&self) -> Option<Cow<str>>29 pub fn get(&self) -> Option<Cow<str>> {
30 unsafe {
31 let message = ffi::gst_debug_message_get(self.0.as_ptr());
32
33 if message.is_null() {
34 None
35 } else {
36 Some(CStr::from_ptr(message).to_string_lossy())
37 }
38 }
39 }
40 }
41
42 #[derive(PartialEq, Eq, Clone, Copy)]
43 #[doc(alias = "GstDebugCategory")]
44 pub struct DebugCategory(Option<ptr::NonNull<ffi::GstDebugCategory>>);
45
46 impl DebugCategory {
new( name: &str, color: crate::DebugColorFlags, description: Option<&str>, ) -> DebugCategory47 pub fn new(
48 name: &str,
49 color: crate::DebugColorFlags,
50 description: Option<&str>,
51 ) -> DebugCategory {
52 skip_assert_initialized!();
53 extern "C" {
54 fn _gst_debug_category_new(
55 name: *const c_char,
56 color: ffi::GstDebugColorFlags,
57 description: *const c_char,
58 ) -> *mut ffi::GstDebugCategory;
59 }
60
61 // Gets the category if it exists already
62 unsafe {
63 let ptr = _gst_debug_category_new(
64 name.to_glib_none().0,
65 color.into_glib(),
66 description.to_glib_none().0,
67 );
68 // Can be NULL if the debug system is compiled out
69 DebugCategory(ptr::NonNull::new(ptr))
70 }
71 }
72
get(name: &str) -> Option<DebugCategory>73 pub fn get(name: &str) -> Option<DebugCategory> {
74 skip_assert_initialized!();
75 unsafe {
76 extern "C" {
77 fn _gst_debug_get_category(name: *const c_char) -> *mut ffi::GstDebugCategory;
78 }
79
80 let cat = _gst_debug_get_category(name.to_glib_none().0);
81
82 if cat.is_null() {
83 None
84 } else {
85 Some(DebugCategory(Some(ptr::NonNull::new_unchecked(cat))))
86 }
87 }
88 }
89
90 #[doc(alias = "get_threshold")]
91 #[doc(alias = "gst_debug_category_get_threshold")]
threshold(self) -> crate::DebugLevel92 pub fn threshold(self) -> crate::DebugLevel {
93 match self.0 {
94 Some(cat) => unsafe { from_glib(ffi::gst_debug_category_get_threshold(cat.as_ptr())) },
95 None => crate::DebugLevel::None,
96 }
97 }
98
99 #[doc(alias = "gst_debug_category_set_threshold")]
set_threshold(self, threshold: crate::DebugLevel)100 pub fn set_threshold(self, threshold: crate::DebugLevel) {
101 if let Some(cat) = self.0 {
102 unsafe { ffi::gst_debug_category_set_threshold(cat.as_ptr(), threshold.into_glib()) }
103 }
104 }
105
106 #[doc(alias = "gst_debug_category_reset_threshold")]
reset_threshold(self)107 pub fn reset_threshold(self) {
108 if let Some(cat) = self.0 {
109 unsafe { ffi::gst_debug_category_reset_threshold(cat.as_ptr()) }
110 }
111 }
112
113 #[doc(alias = "get_color")]
114 #[doc(alias = "gst_debug_category_get_color")]
color(self) -> crate::DebugColorFlags115 pub fn color(self) -> crate::DebugColorFlags {
116 match self.0 {
117 Some(cat) => unsafe { from_glib(ffi::gst_debug_category_get_color(cat.as_ptr())) },
118 None => crate::DebugColorFlags::empty(),
119 }
120 }
121
122 #[doc(alias = "get_name")]
123 #[doc(alias = "gst_debug_category_get_name")]
name<'a>(self) -> &'a str124 pub fn name<'a>(self) -> &'a str {
125 match self.0 {
126 Some(cat) => unsafe {
127 CStr::from_ptr(ffi::gst_debug_category_get_name(cat.as_ptr()))
128 .to_str()
129 .unwrap()
130 },
131 None => "",
132 }
133 }
134
135 #[doc(alias = "get_description")]
136 #[doc(alias = "gst_debug_category_get_description")]
description<'a>(self) -> Option<&'a str>137 pub fn description<'a>(self) -> Option<&'a str> {
138 match self.0 {
139 Some(cat) => unsafe {
140 let ptr = ffi::gst_debug_category_get_description(cat.as_ptr());
141
142 if ptr.is_null() {
143 None
144 } else {
145 Some(CStr::from_ptr(ptr).to_str().unwrap())
146 }
147 },
148 None => None,
149 }
150 }
151
152 #[inline]
153 #[doc(alias = "gst_debug_log")]
log<O: IsA<glib::Object>>( self, obj: Option<&O>, level: crate::DebugLevel, file: &str, module: &str, line: u32, args: fmt::Arguments, )154 pub fn log<O: IsA<glib::Object>>(
155 self,
156 obj: Option<&O>,
157 level: crate::DebugLevel,
158 file: &str,
159 module: &str,
160 line: u32,
161 args: fmt::Arguments,
162 ) {
163 let cat = match self.0 {
164 Some(cat) => cat,
165 None => return,
166 };
167
168 unsafe {
169 if level.into_glib() as i32 > cat.as_ref().threshold {
170 return;
171 }
172 }
173
174 let obj_ptr = match obj {
175 Some(obj) => obj.to_glib_none().0 as *mut glib::gobject_ffi::GObject,
176 None => ptr::null_mut(),
177 };
178
179 unsafe {
180 ffi::gst_debug_log(
181 cat.as_ptr(),
182 level.into_glib(),
183 file.to_glib_none().0,
184 module.to_glib_none().0,
185 line as i32,
186 obj_ptr,
187 fmt::format(args).replace("%", "%%").to_glib_none().0,
188 );
189 }
190 }
191 }
192
193 unsafe impl Sync for DebugCategory {}
194 unsafe impl Send for DebugCategory {}
195
196 impl fmt::Debug for DebugCategory {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result197 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
198 f.debug_tuple("DebugCategory").field(&self.name()).finish()
199 }
200 }
201
202 pub static CAT_RUST: Lazy<DebugCategory> = Lazy::new(|| {
203 DebugCategory::new(
204 "GST_RUST",
205 crate::DebugColorFlags::UNDERLINE,
206 Some("GStreamer's Rust binding core"),
207 )
208 });
209
210 macro_rules! declare_debug_category_from_name(
211 ($cat:ident, $cat_name:expr) => (
212 pub static $cat: Lazy<DebugCategory> = Lazy::new(|| DebugCategory::get($cat_name)
213 .expect(&format!("Unable to find `DebugCategory` with name {}", $cat_name)));
214 );
215 );
216
217 declare_debug_category_from_name!(CAT_DEFAULT, "default");
218 declare_debug_category_from_name!(CAT_GST_INIT, "GST_INIT");
219 declare_debug_category_from_name!(CAT_MEMORY, "GST_MEMORY");
220 declare_debug_category_from_name!(CAT_PARENTAGE, "GST_PARENTAGE");
221 declare_debug_category_from_name!(CAT_STATES, "GST_STATES");
222 declare_debug_category_from_name!(CAT_SCHEDULING, "GST_SCHEDULING");
223 declare_debug_category_from_name!(CAT_BUFFER, "GST_BUFFER");
224 declare_debug_category_from_name!(CAT_BUFFER_LIST, "GST_BUFFER_LIST");
225 declare_debug_category_from_name!(CAT_BUS, "GST_BUS");
226 declare_debug_category_from_name!(CAT_CAPS, "GST_CAPS");
227 declare_debug_category_from_name!(CAT_CLOCK, "GST_CLOCK");
228 declare_debug_category_from_name!(CAT_ELEMENT_PADS, "GST_ELEMENT_PADS");
229 declare_debug_category_from_name!(CAT_PADS, "GST_PADS");
230 declare_debug_category_from_name!(CAT_PERFORMANCE, "GST_PERFORMANCE");
231 declare_debug_category_from_name!(CAT_PIPELINE, "GST_PIPELINE");
232 declare_debug_category_from_name!(CAT_PLUGIN_LOADING, "GST_PLUGIN_LOADING");
233 declare_debug_category_from_name!(CAT_PLUGIN_INFO, "GST_PLUGIN_INFO");
234 declare_debug_category_from_name!(CAT_PROPERTIES, "GST_PROPERTIES");
235 declare_debug_category_from_name!(CAT_NEGOTIATION, "GST_NEGOTIATION");
236 declare_debug_category_from_name!(CAT_REFCOUNTING, "GST_REFCOUNTING");
237 declare_debug_category_from_name!(CAT_ERROR_SYSTEM, "GST_ERROR_SYSTEM");
238 declare_debug_category_from_name!(CAT_EVENT, "GST_EVENT");
239 declare_debug_category_from_name!(CAT_MESSAGE, "GST_MESSAGE");
240 declare_debug_category_from_name!(CAT_PARAMS, "GST_PARAMS");
241 declare_debug_category_from_name!(CAT_CALL_TRACE, "GST_CALL_TRACE");
242 declare_debug_category_from_name!(CAT_SIGNAL, "GST_SIGNAL");
243 declare_debug_category_from_name!(CAT_PROBE, "GST_PROBE");
244 declare_debug_category_from_name!(CAT_REGISTRY, "GST_REGISTRY");
245 declare_debug_category_from_name!(CAT_QOS, "GST_QOS");
246 declare_debug_category_from_name!(CAT_META, "GST_META");
247 declare_debug_category_from_name!(CAT_LOCKING, "GST_LOCKING");
248 declare_debug_category_from_name!(CAT_CONTEXT, "GST_CONTEXT");
249
250 #[macro_export]
251 macro_rules! gst_error(
252 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
253 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, obj: $obj, $($args)*)
254 }};
255 ($cat:expr, $($args:tt)*) => { {
256 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Error, $($args)*)
257 }};
258 );
259
260 #[macro_export]
261 macro_rules! gst_warning(
262 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
263 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, obj: $obj, $($args)*)
264 }};
265 ($cat:expr, $($args:tt)*) => { {
266 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Warning, $($args)*)
267 }};
268 );
269
270 #[macro_export]
271 macro_rules! gst_fixme(
272 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
273 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, obj: $obj, $($args)*)
274 }};
275 ($cat:expr, $($args:tt)*) => { {
276 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Fixme, $($args)*)
277 }};
278 );
279
280 #[macro_export]
281 macro_rules! gst_info(
282 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
283 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, obj: $obj, $($args)*)
284 }};
285 ($cat:expr, $($args:tt)*) => { {
286 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Info, $($args)*)
287 }};
288 );
289
290 #[macro_export]
291 macro_rules! gst_debug(
292 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
293 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, obj: $obj, $($args)*)
294 }};
295 ($cat:expr, $($args:tt)*) => { {
296 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Debug, $($args)*)
297 }};
298 );
299
300 #[macro_export]
301 macro_rules! gst_log(
302 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
303 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, obj: $obj, $($args)*)
304 }};
305 ($cat:expr, $($args:tt)*) => { {
306 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Log, $($args)*)
307 }};
308 );
309
310 #[macro_export]
311 macro_rules! gst_trace(
312 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
313 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, obj: $obj, $($args)*)
314 }};
315 ($cat:expr, $($args:tt)*) => { {
316 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Trace, $($args)*)
317 }};
318 );
319
320 #[macro_export]
321 macro_rules! gst_memdump(
322 ($cat:expr, obj: $obj:expr, $($args:tt)*) => { {
323 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, obj: $obj, $($args)*)
324 }};
325 ($cat:expr, $($args:tt)*) => { {
326 $crate::gst_log_with_level!($cat.clone(), level: $crate::DebugLevel::Memdump, $($args)*)
327 }};
328 );
329
330 #[macro_export]
331 macro_rules! gst_log_with_level(
332 ($cat:expr, level: $level:expr, obj: $obj:expr, $($args:tt)*) => { {
333 $crate::DebugCategory::log($cat.clone(), Some($obj), $level, file!(),
334 module_path!(), line!(), format_args!($($args)*))
335 }};
336 ($cat:expr, level: $level:expr, $($args:tt)*) => { {
337 $crate::DebugCategory::log($cat.clone(), None as Option<&$crate::glib::Object>, $level, file!(),
338 module_path!(), line!(), format_args!($($args)*))
339 }};
340 );
341
log_handler<T>( category: *mut ffi::GstDebugCategory, level: ffi::GstDebugLevel, file: *const c_char, function: *const c_char, line: i32, object: *mut glib::gobject_ffi::GObject, message: *mut ffi::GstDebugMessage, user_data: gpointer, ) where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&LoggedObject>, &DebugMessage) + Send + Sync + 'static,342 unsafe extern "C" fn log_handler<T>(
343 category: *mut ffi::GstDebugCategory,
344 level: ffi::GstDebugLevel,
345 file: *const c_char,
346 function: *const c_char,
347 line: i32,
348 object: *mut glib::gobject_ffi::GObject,
349 message: *mut ffi::GstDebugMessage,
350 user_data: gpointer,
351 ) where
352 T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&LoggedObject>, &DebugMessage)
353 + Send
354 + Sync
355 + 'static,
356 {
357 if category.is_null() {
358 return;
359 }
360 let category = DebugCategory(Some(ptr::NonNull::new_unchecked(category)));
361 let level = from_glib(level);
362 let file = CStr::from_ptr(file).to_string_lossy();
363 let function = CStr::from_ptr(function).to_string_lossy();
364 let line = line as u32;
365 let object = ptr::NonNull::new(object).map(LoggedObject);
366 let message = DebugMessage(ptr::NonNull::new_unchecked(message));
367 let handler = &*(user_data as *mut T);
368 (handler)(
369 category,
370 level,
371 &file,
372 &function,
373 line,
374 object.as_ref(),
375 &message,
376 );
377 }
378
log_handler_data_free<T>(data: gpointer)379 unsafe extern "C" fn log_handler_data_free<T>(data: gpointer) {
380 let data = Box::from_raw(data as *mut T);
381 drop(data);
382 }
383
384 #[derive(Debug)]
385 pub struct DebugLogFunction(ptr::NonNull<std::os::raw::c_void>);
386
387 // The contained pointer is never dereferenced and has no thread affinity.
388 // It may be convenient to send it or share it between threads to allow cleaning
389 // up log functions from other threads than the one that created it.
390 unsafe impl Send for DebugLogFunction {}
391 unsafe impl Sync for DebugLogFunction {}
392
393 #[derive(Debug)]
394 #[doc(alias = "GObject")]
395 pub struct LoggedObject(ptr::NonNull<glib::gobject_ffi::GObject>);
396
397 impl LoggedObject {
as_ptr(&self) -> *mut glib::gobject_ffi::GObject398 pub fn as_ptr(&self) -> *mut glib::gobject_ffi::GObject {
399 self.0.as_ptr()
400 }
401 }
402
403 impl fmt::Display for LoggedObject {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result404 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405 unsafe {
406 let ptr = self.0.as_ptr();
407 let g_type_instance = &mut (*ptr).g_type_instance;
408 if glib::gobject_ffi::g_type_check_instance_is_fundamentally_a(
409 g_type_instance,
410 glib::gobject_ffi::g_object_get_type(),
411 ) != glib::ffi::GFALSE
412 {
413 let type_ = (*g_type_instance.g_class).g_type;
414
415 if glib::gobject_ffi::g_type_is_a(type_, ffi::gst_pad_get_type())
416 != glib::ffi::GFALSE
417 {
418 let name_ptr = (*(ptr as *mut ffi::GstObject)).name;
419 let name = if name_ptr.is_null() {
420 "<null>"
421 } else {
422 CStr::from_ptr(name_ptr)
423 .to_str()
424 .unwrap_or("<invalid name>")
425 };
426
427 let parent_ptr = (*(ptr as *mut ffi::GstObject)).parent;
428 let parent_name = if parent_ptr.is_null() {
429 "<null>"
430 } else {
431 let name_ptr = (*(parent_ptr as *mut ffi::GstObject)).name;
432 if name_ptr.is_null() {
433 "<null>"
434 } else {
435 CStr::from_ptr(name_ptr)
436 .to_str()
437 .unwrap_or("<invalid name>")
438 }
439 };
440
441 write!(f, "{}:{}", parent_name, name)
442 } else if glib::gobject_ffi::g_type_is_a(type_, ffi::gst_object_get_type())
443 != glib::ffi::GFALSE
444 {
445 let name_ptr = (*(ptr as *mut ffi::GstObject)).name;
446 let name = if name_ptr.is_null() {
447 "<null>"
448 } else {
449 CStr::from_ptr(name_ptr)
450 .to_str()
451 .unwrap_or("<invalid name>")
452 };
453 write!(f, "{}", name)
454 } else {
455 let type_name = CStr::from_ptr(glib::gobject_ffi::g_type_name(type_));
456 write!(
457 f,
458 "{}:{:?}",
459 type_name.to_str().unwrap_or("<invalid type>"),
460 ptr
461 )
462 }
463 } else {
464 write!(f, "{:?}", ptr)
465 }
466 }
467 }
468 }
469
470 #[doc(alias = "gst_debug_add_log_function")]
debug_add_log_function<T>(function: T) -> DebugLogFunction where T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&LoggedObject>, &DebugMessage) + Send + Sync + 'static,471 pub fn debug_add_log_function<T>(function: T) -> DebugLogFunction
472 where
473 T: Fn(DebugCategory, DebugLevel, &str, &str, u32, Option<&LoggedObject>, &DebugMessage)
474 + Send
475 + Sync
476 + 'static,
477 {
478 skip_assert_initialized!();
479 unsafe {
480 let user_data = Box::new(function);
481 let user_data_ptr = Box::into_raw(user_data) as gpointer;
482 ffi::gst_debug_add_log_function(
483 Some(log_handler::<T>),
484 user_data_ptr,
485 Some(log_handler_data_free::<T>),
486 );
487 DebugLogFunction(ptr::NonNull::new_unchecked(user_data_ptr))
488 }
489 }
490
debug_remove_default_log_function()491 pub fn debug_remove_default_log_function() {
492 skip_assert_initialized!();
493 unsafe {
494 ffi::gst_debug_remove_log_function(None);
495 }
496 }
497
498 #[doc(alias = "gst_debug_remove_log_function_by_data")]
debug_remove_log_function(log_fn: DebugLogFunction)499 pub fn debug_remove_log_function(log_fn: DebugLogFunction) {
500 skip_assert_initialized!();
501 unsafe {
502 ffi::gst_debug_remove_log_function_by_data(log_fn.0.as_ptr());
503 }
504 }
505
506 #[cfg(test)]
507 mod tests {
508 use super::*;
509 use std::sync::mpsc;
510 use std::sync::{Arc, Mutex};
511
512 #[test]
513 #[doc(alias = "get_existing")]
existing()514 fn existing() {
515 crate::init().unwrap();
516
517 let perf_cat = DebugCategory::get("GST_PERFORMANCE")
518 .expect("Unable to find `DebugCategory` with name \"GST_PERFORMANCE\"");
519 assert_eq!(perf_cat.name(), CAT_PERFORMANCE.name());
520 }
521
522 #[test]
new_and_log()523 fn new_and_log() {
524 crate::init().unwrap();
525
526 let cat = DebugCategory::new(
527 "test-cat",
528 crate::DebugColorFlags::empty(),
529 Some("some debug category"),
530 );
531
532 gst_error!(cat, "meh");
533 gst_warning!(cat, "meh");
534 gst_fixme!(cat, "meh");
535 gst_info!(cat, "meh");
536 gst_debug!(cat, "meh");
537 gst_log!(cat, "meh");
538 gst_trace!(cat, "meh");
539 gst_memdump!(cat, "meh");
540
541 let obj = crate::Bin::new(Some("meh"));
542 gst_error!(cat, obj: &obj, "meh");
543 gst_warning!(cat, obj: &obj, "meh");
544 gst_fixme!(cat, obj: &obj, "meh");
545 gst_info!(cat, obj: &obj, "meh");
546 gst_debug!(cat, obj: &obj, "meh");
547 gst_log!(cat, obj: &obj, "meh");
548 gst_trace!(cat, obj: &obj, "meh");
549 gst_memdump!(cat, obj: &obj, "meh");
550 }
551
552 #[test]
log_handler()553 fn log_handler() {
554 crate::init().unwrap();
555
556 let cat = DebugCategory::new(
557 "test-cat-log",
558 crate::DebugColorFlags::empty(),
559 Some("some debug category"),
560 );
561 cat.set_threshold(DebugLevel::Info);
562 let obj = crate::Bin::new(Some("meh"));
563
564 let (sender, receiver) = mpsc::channel();
565
566 let sender = Arc::new(Mutex::new(sender));
567
568 let handler = move |category: DebugCategory,
569 level: DebugLevel,
570 _file: &str,
571 _function: &str,
572 _line: u32,
573 _object: Option<&LoggedObject>,
574 message: &DebugMessage| {
575 let cat = DebugCategory::get("test-cat-log").unwrap();
576
577 if category != cat {
578 // This test can run in parallel with other tests, including new_and_log above.
579 // We cannot be certain we only see our own messages.
580 return;
581 }
582
583 assert_eq!(level, DebugLevel::Info);
584 assert_eq!(&message.get().unwrap(), "meh");
585 let _ = sender.lock().unwrap().send(());
586 };
587
588 debug_remove_default_log_function();
589 let log_fn = debug_add_log_function(handler);
590 gst_info!(cat, obj: &obj, "meh");
591
592 receiver.recv().unwrap();
593
594 debug_remove_log_function(log_fn);
595
596 gst_info!(cat, obj: &obj, "meh2");
597 assert!(receiver.recv().is_err());
598 }
599 }
600