gstreamer/
memory_wrapped.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use glib::{prelude::*, translate::*};
4
5use std::{alloc, any::TypeId, mem, ptr};
6
7use crate::{ffi, Memory};
8
9// rustdoc-stripper-ignore-next
10/// Error type for `try_into_inner` failures on Memory and Buffer.
11#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
12pub enum MemoryIntoInnerError {
13    #[error("Memory does not use the Rust allocator (uses {actual_allocator:?})")]
14    WrongAllocator { actual_allocator: Option<String> },
15    #[error("Memory is not writable")]
16    NotWritable,
17    #[error("Cannot extract wrapped value from sub-memory (shared memory)")]
18    SubMemory,
19    #[error("Memory does not wrap anything")]
20    NothingWrapped,
21    #[error("Memory does not wrap the requested type (expected {expected:?}, found {actual:?})")]
22    TypeMismatch { expected: TypeId, actual: TypeId },
23    #[error("Buffer must contain exactly one memory block")]
24    MultipleMemoryBlocks,
25}
26
27#[repr(C)]
28struct WrappedMemory<T> {
29    mem: ffi::GstMemory,
30
31    // AsRef / AsMut values
32    data: *mut u8,
33
34    // Layout used for allocating this struct, literally `Layout::new<Self>`
35    layout: alloc::Layout,
36
37    // Offset from the beginning of the struct until `wrap`
38    wrap_offset: usize,
39    // `ptr::drop_in_place()` for `T`
40    wrap_drop_in_place: unsafe fn(*mut T),
41    // TypeId of the wrapped type for runtime type checking
42    wrap_type_id: TypeId,
43    wrap: T,
44}
45
46unsafe extern "C" fn free(_allocator: *mut ffi::GstAllocator, mem: *mut ffi::GstMemory) {
47    let mem = mem as *mut WrappedMemory<()>;
48
49    if (*mem).wrap_offset > 0 {
50        let wrap = (mem as *mut u8).add((*mem).wrap_offset) as *mut ();
51        ((*mem).wrap_drop_in_place)(wrap);
52    }
53
54    alloc::dealloc(mem as *mut u8, (*mem).layout);
55}
56
57unsafe extern "C" fn mem_map(
58    mem: *mut ffi::GstMemory,
59    _maxsize: usize,
60    _flags: ffi::GstMapFlags,
61) -> glib::ffi::gpointer {
62    let mem = mem as *mut WrappedMemory<()>;
63
64    (*mem).data as glib::ffi::gpointer
65}
66
67unsafe extern "C" fn mem_unmap(_mem: *mut ffi::GstMemory) {}
68
69unsafe extern "C" fn mem_share(
70    mem: *mut ffi::GstMemory,
71    offset: isize,
72    size: isize,
73) -> *mut ffi::GstMemory {
74    let mem = mem as *mut WrappedMemory<()>;
75
76    // Basically a re-implementation of _sysmem_share()
77
78    let parent = if (*mem).mem.parent.is_null() {
79        mem
80    } else {
81        (*mem).mem.parent as *mut WrappedMemory<()>
82    };
83
84    // Offset and size are actually usizes and the API assumes that negative values simply wrap
85    // around, so let's cast to usizes here and do wrapping arithmetic.
86    let offset = offset as usize;
87    let mut size = size as usize;
88
89    let new_offset = (*mem).mem.offset.wrapping_add(offset);
90    debug_assert!(new_offset < (*mem).mem.maxsize);
91
92    if size == usize::MAX {
93        size = (*mem).mem.size.wrapping_sub(offset);
94    }
95    debug_assert!(new_offset <= usize::MAX - size);
96    debug_assert!(new_offset + size <= (*mem).mem.maxsize);
97
98    let layout = alloc::Layout::new::<WrappedMemory<()>>();
99    let sub = alloc::alloc(layout) as *mut WrappedMemory<()>;
100
101    ffi::gst_memory_init(
102        sub as *mut ffi::GstMemory,
103        (*mem).mem.mini_object.flags | ffi::GST_MINI_OBJECT_FLAG_LOCK_READONLY,
104        (*mem).mem.allocator,
105        parent as *mut ffi::GstMemory,
106        (*mem).mem.maxsize,
107        (*mem).mem.align,
108        new_offset,
109        size,
110    );
111    ptr::write(ptr::addr_of_mut!((*sub).data), (*mem).data);
112    ptr::write(ptr::addr_of_mut!((*sub).layout), layout);
113    ptr::write(ptr::addr_of_mut!((*sub).wrap_offset), 0);
114    ptr::write(ptr::addr_of_mut!((*sub).wrap_drop_in_place), |_| ());
115
116    sub as *mut ffi::GstMemory
117}
118
119unsafe extern "C" fn mem_is_span(
120    mem1: *mut ffi::GstMemory,
121    mem2: *mut ffi::GstMemory,
122    offset: *mut usize,
123) -> glib::ffi::gboolean {
124    let mem1 = mem1 as *mut WrappedMemory<()>;
125    let mem2 = mem2 as *mut WrappedMemory<()>;
126
127    // Basically a re-implementation of _sysmem_is_span()
128    if !offset.is_null() {
129        let parent = (*mem1).mem.parent as *mut WrappedMemory<()>;
130        *offset = (*mem1).mem.offset - (*parent).mem.offset;
131    }
132
133    let is_span = (*mem1).data.add((*mem1).mem.offset).add((*mem1).mem.size)
134        == (*mem2).data.add((*mem2).mem.offset);
135
136    is_span.into_glib()
137}
138
139unsafe extern "C" fn class_init(class: glib::ffi::gpointer, _class_data: glib::ffi::gpointer) {
140    let class = class as *mut ffi::GstAllocatorClass;
141
142    (*class).free = Some(free);
143}
144
145unsafe extern "C" fn instance_init(
146    obj: *mut glib::gobject_ffi::GTypeInstance,
147    _class: glib::ffi::gpointer,
148) {
149    static ALLOCATOR_TYPE: &[u8] = b"RustGlobalAllocatorMemory\0";
150
151    let allocator = obj as *mut ffi::GstAllocator;
152
153    (*allocator).mem_type = ALLOCATOR_TYPE.as_ptr() as *const _;
154    (*allocator).mem_map = Some(mem_map);
155    (*allocator).mem_unmap = Some(mem_unmap);
156    // mem_copy not set because the fallback already does the right thing
157    (*allocator).mem_share = Some(mem_share);
158    (*allocator).mem_is_span = Some(mem_is_span);
159
160    // TODO: Could also implement alloc()
161    (*allocator).object.flags |= ffi::GST_ALLOCATOR_FLAG_CUSTOM_ALLOC;
162    (*allocator).object.flags |= ffi::GST_OBJECT_FLAG_MAY_BE_LEAKED;
163}
164
165fn rust_allocator() -> &'static crate::Allocator {
166    static RUST_ALLOCATOR: std::sync::OnceLock<crate::Allocator> = std::sync::OnceLock::new();
167
168    RUST_ALLOCATOR.get_or_init(|| unsafe {
169        struct TypeInfoWrap(glib::gobject_ffi::GTypeInfo);
170        unsafe impl Send for TypeInfoWrap {}
171        unsafe impl Sync for TypeInfoWrap {}
172
173        static TYPE_INFO: TypeInfoWrap = TypeInfoWrap(glib::gobject_ffi::GTypeInfo {
174            class_size: mem::size_of::<ffi::GstAllocatorClass>() as u16,
175            base_init: None,
176            base_finalize: None,
177            class_init: Some(class_init),
178            class_finalize: None,
179            class_data: ptr::null_mut(),
180            instance_size: mem::size_of::<ffi::GstAllocator>() as u16,
181            n_preallocs: 0,
182            instance_init: Some(instance_init),
183            value_table: ptr::null(),
184        });
185
186        let type_name = {
187            let mut idx = 0;
188
189            loop {
190                let type_name = glib::gformat!("GstRsAllocator-{}", idx);
191                if glib::gobject_ffi::g_type_from_name(type_name.as_ptr())
192                    == glib::gobject_ffi::G_TYPE_INVALID
193                {
194                    break type_name;
195                }
196                idx += 1;
197            }
198        };
199
200        let t = glib::gobject_ffi::g_type_register_static(
201            crate::Allocator::static_type().into_glib(),
202            type_name.as_ptr(),
203            &TYPE_INFO.0,
204            0,
205        );
206
207        assert!(t != glib::gobject_ffi::G_TYPE_INVALID);
208
209        from_glib_none(
210            glib::gobject_ffi::g_object_newv(t, 0, ptr::null_mut()) as *mut ffi::GstAllocator
211        )
212    })
213}
214
215// Attempts to extract the underlying wrapped value of type `T` from a raw memory pointer.
216// NOTE: The caller must ensure that the memory is freed on success
217#[inline]
218pub(crate) unsafe fn try_into_from_memory_ptr<T: 'static>(
219    mem_ptr: *mut ffi::GstMemory,
220) -> Result<T, MemoryIntoInnerError> {
221    skip_assert_initialized!();
222
223    // Check if this memory uses our rust allocator
224    if (*mem_ptr).allocator.is_null() || (*mem_ptr).allocator != rust_allocator().as_ptr() {
225        let actual_allocator = if (*mem_ptr).allocator.is_null() {
226            None
227        } else {
228            Some(
229                std::ffi::CStr::from_ptr(glib::gobject_ffi::g_type_name_from_instance(
230                    (*mem_ptr).allocator as *mut glib::gobject_ffi::GTypeInstance,
231                ))
232                .to_string_lossy()
233                .to_string(),
234            )
235        };
236        return Err(MemoryIntoInnerError::WrongAllocator { actual_allocator });
237    }
238
239    if ffi::gst_mini_object_is_writable(mem_ptr as *mut ffi::GstMiniObject) == glib::ffi::GFALSE {
240        return Err(MemoryIntoInnerError::NotWritable);
241    }
242
243    // Check that this is not a sub-memory
244    if !(*mem_ptr).parent.is_null() {
245        return Err(MemoryIntoInnerError::SubMemory);
246    }
247
248    // Cast to WrappedMemory<T> and create a reference.
249    // SAFETY: This cast is safe for reading the wrap_offset and wrap_type_id fields
250    // because these fields are in the same position regardless of the generic type T.
251    // We verify the actual type using wrap_type_id before accessing the `wrap` field.
252    let mem_wrapper = &*(mem_ptr as *mut WrappedMemory<T>);
253
254    // Check that wrap_offset is set (meaning something is wrapped)
255    if mem_wrapper.wrap_offset == 0 {
256        return Err(MemoryIntoInnerError::NothingWrapped);
257    }
258
259    // Check that the wrapped type is actually T
260    // Only after this check passes is it safe to access mem_wrapper.wrap
261    if mem_wrapper.wrap_type_id != TypeId::of::<T>() {
262        return Err(MemoryIntoInnerError::TypeMismatch {
263            expected: std::any::TypeId::of::<T>(),
264            actual: mem_wrapper.wrap_type_id,
265        });
266    }
267
268    // Extract the wrapped value
269    let mem_wrapper_mut = &mut *(mem_ptr as *mut WrappedMemory<T>);
270    let value = ptr::read(&mem_wrapper_mut.wrap);
271
272    // Mark the wrap_offset as 0 to prevent drop from running on the wrapped value
273    mem_wrapper_mut.wrap_offset = 0;
274
275    Ok(value)
276}
277
278impl Memory {
279    #[doc(alias = "gst_memory_new_wrapped")]
280    #[doc(alias = "gst_memory_new_wrapped_full")]
281    #[inline]
282    pub fn from_slice<T: AsRef<[u8]> + Send + 'static>(slice: T) -> Self {
283        assert_initialized_main_thread!();
284
285        let len = slice.as_ref().len();
286        unsafe {
287            let layout = alloc::Layout::new::<WrappedMemory<T>>();
288            let mem = alloc::alloc(layout) as *mut WrappedMemory<T>;
289
290            ffi::gst_memory_init(
291                mem as *mut ffi::GstMemory,
292                ffi::GST_MINI_OBJECT_FLAG_LOCK_READONLY,
293                rust_allocator().to_glib_none().0,
294                ptr::null_mut(),
295                len,
296                0,
297                0,
298                len,
299            );
300
301            ptr::write(ptr::addr_of_mut!((*mem).wrap), slice);
302
303            assert_eq!(len, (*mem).wrap.as_ref().len());
304            let data = (*mem).wrap.as_ref().as_ptr();
305            ptr::write(ptr::addr_of_mut!((*mem).data), mut_override(data));
306
307            ptr::write(ptr::addr_of_mut!((*mem).layout), layout);
308
309            let wrap_offset = ptr::addr_of!((*mem).wrap) as usize - mem as usize;
310            ptr::write(ptr::addr_of_mut!((*mem).wrap_offset), wrap_offset);
311
312            ptr::write(
313                ptr::addr_of_mut!((*mem).wrap_drop_in_place),
314                ptr::drop_in_place::<T>,
315            );
316
317            ptr::write(ptr::addr_of_mut!((*mem).wrap_type_id), TypeId::of::<T>());
318
319            from_glib_full(mem as *mut ffi::GstMemory)
320        }
321    }
322
323    #[doc(alias = "gst_memory_new_wrapped")]
324    #[doc(alias = "gst_memory_new_wrapped_full")]
325    #[inline]
326    pub fn from_mut_slice<T: AsMut<[u8]> + Send + 'static>(mut slice: T) -> Self {
327        assert_initialized_main_thread!();
328
329        let len = slice.as_mut().len();
330        unsafe {
331            let layout = alloc::Layout::new::<WrappedMemory<T>>();
332            let mem = alloc::alloc(layout) as *mut WrappedMemory<T>;
333
334            ffi::gst_memory_init(
335                mem as *mut ffi::GstMemory,
336                0,
337                rust_allocator().to_glib_none().0,
338                ptr::null_mut(),
339                len,
340                0,
341                0,
342                len,
343            );
344
345            ptr::write(ptr::addr_of_mut!((*mem).wrap), slice);
346
347            assert_eq!(len, (*mem).wrap.as_mut().len());
348            let data = (*mem).wrap.as_mut().as_mut_ptr();
349            ptr::write(ptr::addr_of_mut!((*mem).data), data);
350
351            ptr::write(ptr::addr_of_mut!((*mem).layout), layout);
352
353            let wrap_offset = ptr::addr_of!((*mem).wrap) as usize - mem as usize;
354            ptr::write(ptr::addr_of_mut!((*mem).wrap_offset), wrap_offset);
355
356            ptr::write(
357                ptr::addr_of_mut!((*mem).wrap_drop_in_place),
358                ptr::drop_in_place::<T>,
359            );
360
361            ptr::write(ptr::addr_of_mut!((*mem).wrap_type_id), TypeId::of::<T>());
362
363            from_glib_full(mem as *mut ffi::GstMemory)
364        }
365    }
366
367    // rustdoc-stripper-ignore-next
368    /// Attempts to extract the underlying wrapped value of type `T` from this `Memory`.
369    ///
370    /// This will only succeed if:
371    /// - The memory was created with `from_slice()` or `from_mut_slice()` wrapping type `T`
372    /// - The memory is not a sub-memory (created via sharing)
373    /// - This is the only reference to the memory (memory is writable)
374    ///
375    /// On success, the `Memory` is consumed and the original wrapped value is returned.
376    /// On failure, the original `Memory` and an error are returned in the `Err` variant.
377    ///
378    /// # Examples
379    ///
380    /// ```
381    /// use gstreamer::Memory;
382    ///
383    /// gstreamer::init().unwrap();
384    ///
385    /// let vec = vec![1u8, 2, 3, 4, 5];
386    /// let expected = vec.clone();
387    /// let mem = Memory::from_slice(vec);
388    ///
389    /// let extracted: Vec<u8> = mem.try_into_inner().unwrap();
390    /// assert_eq!(extracted, expected);
391    /// ```
392    #[inline]
393    pub fn try_into_inner<T: 'static>(self) -> Result<T, (Self, MemoryIntoInnerError)> {
394        // Check that this is the only reference
395        unsafe {
396            let mem_ptr = self.as_mut_ptr();
397
398            // Try to extract the wrapped value using the helper function
399            match try_into_from_memory_ptr(mem_ptr) {
400                Ok(value) => {
401                    // Drop the Memory (which will deallocate the wrapper but
402                    // not call the wrapped value's drop)
403                    drop(self);
404                    Ok(value)
405                }
406                Err(err) => {
407                    // Failed validation
408                    Err((self, err))
409                }
410            }
411        }
412    }
413}
414
415#[cfg(test)]
416mod tests {
417    use super::*;
418
419    #[test]
420    fn test_wrap_vec_u8() {
421        crate::init().unwrap();
422
423        let data = vec![1u8, 2, 3, 4, 5];
424        let expected = data.clone();
425
426        let mem = Memory::from_slice(data);
427        assert_eq!(mem.size(), 5);
428
429        let extracted: Vec<u8> = mem.try_into_inner().unwrap();
430        assert_eq!(extracted, expected);
431    }
432}