From e72a3bfc8d6e7735ca6663c42bea576aecc4339a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Sun, 10 Dec 2023 16:22:27 +0200 Subject: [PATCH] gstreamer: Improve support for dumping memories and add same functionality to byte slices Part-of: --- Cargo.lock | 7 -- gstreamer/Cargo.toml | 1 - gstreamer/src/lib.rs | 2 + gstreamer/src/memory.rs | 85 +++++++++++++++++++---- gstreamer/src/slice.rs | 147 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 220 insertions(+), 22 deletions(-) create mode 100644 gstreamer/src/slice.rs diff --git a/Cargo.lock b/Cargo.lock index c6a92c798..af89e3048 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -845,7 +845,6 @@ dependencies = [ "option-operations", "paste", "pin-project-lite", - "pretty-hex", "ron", "serde", "serde_bytes", @@ -2017,12 +2016,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "pretty-hex" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6b968ed37d62e35b4febaba13bfa231b0b7929d68b8a94e65445a17e2d35f" - [[package]] name = "proc-macro-crate" version = "2.0.1" diff --git a/gstreamer/Cargo.toml b/gstreamer/Cargo.toml index 56763070b..776510c18 100644 --- a/gstreamer/Cargo.toml +++ b/gstreamer/Cargo.toml @@ -28,7 +28,6 @@ opt-ops = { package = "option-operations", version = "0.5" } serde = { version = "1.0", optional = true, features = ["derive"] } serde_bytes = { version = "0.11", optional = true } paste = "1.0" -pretty-hex = "0.4" thiserror = "1" smallvec = { version = "1.0", features = ["write"] } itertools = "0.12" diff --git a/gstreamer/src/lib.rs b/gstreamer/src/lib.rs index 0790c5005..ffa4f4660 100644 --- a/gstreamer/src/lib.rs +++ b/gstreamer/src/lib.rs @@ -125,6 +125,7 @@ mod memory_wrapped; pub use crate::memory::{MappedMemory, Memory, MemoryMap, MemoryRef}; #[cfg(feature = "serde")] mod buffer_serde; +pub mod slice; pub mod sample; pub use crate::sample::{Sample, SampleRef}; @@ -342,6 +343,7 @@ pub mod prelude { param_spec::GstParamSpecBuilderExt, pipeline::GstPipelineExtManual, plugin_feature::PluginFeatureExtManual, + slice::Dump, tag_setter::TagSetterExtManual, tags::{CustomTag, Tag}, task_pool::{TaskHandle, TaskPoolExtManual}, diff --git a/gstreamer/src/memory.rs b/gstreamer/src/memory.rs index e6ebf5d3f..795e322e6 100644 --- a/gstreamer/src/memory.rs +++ b/gstreamer/src/memory.rs @@ -4,7 +4,7 @@ use std::{ fmt, marker::PhantomData, mem, - ops::{Deref, DerefMut}, + ops::{Bound, Deref, DerefMut, RangeBounds}, ptr, slice, }; @@ -284,8 +284,22 @@ impl MemoryRef { unsafe { ffi::gst_memory_resize(self.as_mut_ptr(), offset, size) } } - pub fn dump(&self, size: Option) -> Dump { - Dump { memory: self, size } + #[doc(alias = "gst_util_dump_mem")] + pub fn dump(&self) -> Dump { + Dump { + memory: self, + start: Bound::Unbounded, + end: Bound::Unbounded, + } + } + + #[doc(alias = "gst_util_dump_mem")] + pub fn dump_range(&self, range: impl RangeBounds) -> Dump { + Dump { + memory: self, + start: range.start_bound().cloned(), + end: range.end_bound().cloned(), + } } } @@ -478,22 +492,25 @@ unsafe impl Sync for MappedMemory {} pub struct Dump<'a> { memory: &'a MemoryRef, - size: Option, + start: Bound, + end: Bound, } impl<'a> Dump<'a> { fn fmt(&self, f: &mut fmt::Formatter, debug: bool) -> fmt::Result { - use pretty_hex::*; - let map = self.memory.map_readable().expect("Failed to map memory"); let data = map.as_slice(); - let size = self.size.unwrap_or_else(|| self.memory.size()); - let data = &data[0..size]; + + let dump = crate::slice::Dump { + data, + start: self.start, + end: self.end, + }; if debug { - write!(f, "{:?}", data.hex_dump()) + ::fmt(&dump, f) } else { - write!(f, "{}", data.hex_dump()) + ::fmt(&dump, f) } } } @@ -920,16 +937,56 @@ mod tests { #[test] fn test_dump() { + use std::fmt::Write; + crate::init().unwrap(); + let mut s = String::new(); let mem = crate::Memory::from_slice(vec![1, 2, 3, 4]); - println!("{}", mem.dump(Some(mem.size()))); + write!(&mut s, "{:?}", mem.dump()).unwrap(); + assert_eq!( + s, + "0000: 01 02 03 04 ...." + ); + s.clear(); + write!(&mut s, "{}", mem.dump()).unwrap(); + assert_eq!(s, "01 02 03 04"); + s.clear(); let mem = crate::Memory::from_slice(vec![1, 2, 3, 4]); - println!("{:?}", mem.dump(Some(2))); + write!(&mut s, "{:?}", mem.dump_range(..)).unwrap(); + assert_eq!( + s, + "0000: 01 02 03 04 ...." + ); + s.clear(); + write!(&mut s, "{:?}", mem.dump_range(..2)).unwrap(); + assert_eq!( + s, + "0000: 01 02 .." + ); + s.clear(); + write!(&mut s, "{:?}", mem.dump_range(2..=3)).unwrap(); + assert_eq!( + s, + "0002: 03 04 .." + ); + s.clear(); + write!(&mut s, "{:?}", mem.dump_range(..100)).unwrap(); + assert_eq!(s, "",); + s.clear(); + write!(&mut s, "{:?}", mem.dump_range(90..100)).unwrap(); + assert_eq!(s, "",); + s.clear(); - let mem = crate::Memory::from_slice(vec![0; 64]); - dbg!(mem.dump(None)); + let mem = crate::Memory::from_slice(vec![0; 19]); + write!(&mut s, "{:?}", mem.dump()).unwrap(); + assert_eq!( + s, + "0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................\n\ + 0010: 00 00 00 ..." + ); + s.clear(); } #[test] diff --git a/gstreamer/src/slice.rs b/gstreamer/src/slice.rs new file mode 100644 index 000000000..35940ec1e --- /dev/null +++ b/gstreamer/src/slice.rs @@ -0,0 +1,147 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::{ + fmt, + ops::{Bound, RangeBounds}, +}; + +pub trait ByteSliceExt { + #[doc(alias = "gst_util_dump_mem")] + fn dump(&self) -> Dump; + #[doc(alias = "gst_util_dump_mem")] + fn dump_range(&self, range: impl RangeBounds) -> Dump; +} + +impl<'a> ByteSliceExt for &'a [u8] { + fn dump(&self) -> Dump { + self.dump_range(..) + } + + fn dump_range(&self, range: impl RangeBounds) -> Dump { + Dump { + data: self, + start: range.start_bound().cloned(), + end: range.end_bound().cloned(), + } + } +} + +pub struct Dump<'a> { + pub(crate) data: &'a [u8], + pub(crate) start: Bound, + pub(crate) end: Bound, +} + +impl<'a> Dump<'a> { + fn fmt(&self, f: &mut fmt::Formatter, debug: bool) -> fmt::Result { + use std::fmt::Write; + + let data = self.data; + let len = data.len(); + + // Kind of re-implementation of slice indexing to allow handling out of range values better + // with specific output strings + let mut start_idx = match self.start { + Bound::Included(idx) if idx >= len => { + write!(f, "")?; + return Ok(()); + } + Bound::Excluded(idx) if idx.checked_add(1).map_or(true, |idx| idx >= len) => { + write!(f, "")?; + return Ok(()); + } + Bound::Included(idx) => idx, + Bound::Excluded(idx) => idx + 1, + Bound::Unbounded => 0, + }; + + let end_idx = match self.end { + Bound::Included(idx) if idx.checked_add(1).map_or(true, |idx| idx > len) => { + write!(f, "")?; + return Ok(()); + } + Bound::Excluded(idx) if idx > len => { + write!(f, "")?; + return Ok(()); + } + Bound::Included(idx) => idx + 1, + Bound::Excluded(idx) => idx, + Bound::Unbounded => len, + }; + + if start_idx >= end_idx { + write!(f, "")?; + return Ok(()); + } + + let data = &data[start_idx..end_idx]; + + if debug { + for line in data.chunks(16) { + match end_idx { + 0x00_00..=0xff_ff => write!(f, "{:04x}: ", start_idx)?, + 0x01_00_00..=0xff_ff_ff => write!(f, "{:06x}: ", start_idx)?, + 0x01_00_00_00..=0xff_ff_ff_ff => write!(f, "{:08x}: ", start_idx)?, + _ => write!(f, "{:016x}: ", start_idx)?, + } + + for (i, v) in line.iter().enumerate() { + if i > 0 { + write!(f, " {:02x}", v)?; + } else { + write!(f, "{:02x}", v)?; + } + } + + for _ in line.len()..16 { + write!(f, " ")?; + } + write!(f, " ")?; + + for v in line { + if v.is_ascii() && !v.is_ascii_control() { + f.write_char((*v).into())?; + } else { + f.write_char('.')?; + } + } + + start_idx = start_idx.saturating_add(16); + if start_idx < end_idx { + writeln!(f)?; + } + } + + Ok(()) + } else { + for line in data.chunks(16) { + for (i, v) in line.iter().enumerate() { + if i > 0 { + write!(f, " {:02x}", v)?; + } else { + write!(f, "{:02x}", v)?; + } + } + + start_idx = start_idx.saturating_add(16); + if start_idx < end_idx { + writeln!(f)?; + } + } + + Ok(()) + } + } +} + +impl<'a> fmt::Display for Dump<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.fmt(f, false) + } +} + +impl<'a> fmt::Debug for Dump<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.fmt(f, true) + } +}