Skip to main content

ez_ffmpeg/flv/
flv_buffer.rs

1//! A high-performance ring buffer implementation designed for FLV (Flash Video) data processing.
2//!
3//! This buffer is optimized for:
4//! - Minimizing memory fragmentation by using a pre-allocated ring buffer
5//! - Reducing memory allocation/deallocation overhead
6//! - High-performance memory operations using unsafe `copy_nonoverlapping`
7//!
8//! The buffer size is always a power of two to enable efficient modulo operations
9//! using bitwise AND operations.
10//!
11//! # Memory Management
12//! - Uses a ring buffer to cache data and prevent frequent memory allocations
13//! - Only resizes when absolutely necessary to maintain performance
14//! - All memory operations are performed using `copy_nonoverlapping` for maximum efficiency
15//!
16//! # Safety
17//! While the implementation uses unsafe code for performance optimization,
18//! all unsafe operations are carefully bounded and checked to maintain memory safety.
19//!
20use crate::flv::flv_header::FlvHeader;
21use crate::flv::flv_tag::FlvTag;
22use crate::flv::flv_tag_header::FlvTagHeader;
23use crate::flv::{FLV_HEADER_LENGTH, FLV_TAG_HEADER_LENGTH, PREVIOUS_TAG_SIZE_LENGTH};
24use byteorder::{BigEndian, ReadBytesExt};
25use log::{debug, warn};
26use std::io;
27use std::io::Cursor;
28
29#[derive(Debug)]
30pub struct FlvBuffer {
31    buffer: Vec<u8>,               // Internal buffer
32    head: usize,                   // Index of the first valid byte in the buffer
33    tail: usize,                   // Index where new data will be written
34    flv_header: Option<FlvHeader>, // Store the parsed FLV header (if available)
35    header_parsed: bool,           // Flag to track if FLV file header has been parsed
36    initial_capacity: usize,       // Initial buffer capacity
37}
38
39impl FlvBuffer {
40    /// Creates a new FlvBuffer with default capacity (1M).
41    pub fn new() -> Self {
42        Self::with_capacity(1024 * 1024)
43    }
44
45    /// Creates a new FlvBuffer with the specified capacity.
46    /// The actual capacity will be rounded up to the next power of two.
47    pub fn with_capacity(capacity: usize) -> Self {
48        let capacity = if capacity.is_power_of_two() {
49            capacity
50        } else {
51            capacity.checked_next_power_of_two().unwrap_or(usize::MAX)
52        };
53
54        FlvBuffer {
55            buffer: vec![0; capacity],
56            head: 0,
57            tail: 0,
58            flv_header: None,
59            header_parsed: false, // Initially, the header is not parsed
60            initial_capacity: capacity,
61        }
62    }
63
64    /// Calculates the current length of valid data in the buffer.
65    /// Uses efficient bitwise operations for modulo calculation.
66    #[inline]
67    fn len(&self) -> usize {
68        /*if self.tail >= self.head {
69            self.tail - self.head
70        } else {
71            self.buffer.len() - self.head + self.tail
72        }*/
73        (self.tail.wrapping_sub(self.head)) & (self.buffer.len() - 1)
74    }
75
76    /// Calculates the available space in the buffer.
77    /// Uses efficient bitwise operations for modulo calculation.
78    #[inline]
79    fn available_space(&self) -> usize {
80        // self.buffer.len() - self.len() - 1
81        (self.head.wrapping_sub(self.tail).wrapping_sub(1)) & (self.buffer.len() - 1)
82    }
83
84    /// Writes data into the ring buffer.
85    ///
86    /// This method handles:
87    /// - Buffer resizing if needed
88    /// - Wrapping around the buffer end
89    /// - High-performance memory copying using `copy_nonoverlapping`
90    ///
91    /// # Performance
92    /// Uses unsafe `copy_nonoverlapping` for optimal memory copying performance,
93    /// avoiding bounds checks and overlapping memory verification.
94    pub fn write_data(&mut self, data: &[u8]) {
95        let data_len = data.len();
96        if data_len == 0 {
97            return;
98        }
99
100        // Resize the buffer if needed to accommodate the new data.
101        if data_len > self.available_space() {
102            self.resize_buffer(self.len() + data_len + 1);
103        }
104
105        // Write data to the buffer, handling the wrap-around if necessary.
106        if self.tail >= self.head {
107            let available_at_end = self.buffer.len() - self.tail;
108
109            if data_len <= available_at_end {
110                // Can write the entire chunk in one go
111                // self.buffer[self.tail..self.tail + data_len].copy_from_slice(data);
112                unsafe {
113                    std::ptr::copy_nonoverlapping(
114                        data.as_ptr(),
115                        self.buffer.as_mut_ptr().add(self.tail),
116                        data_len,
117                    );
118                }
119
120                self.tail += data_len;
121            } else {
122                // Need to wrap around.
123                if available_at_end > 0 {
124                    // self.buffer[self.tail..].copy_from_slice(&data[..available_at_end]);
125                    unsafe {
126                        std::ptr::copy_nonoverlapping(
127                            data.as_ptr(),
128                            self.buffer.as_mut_ptr().add(self.tail),
129                            available_at_end,
130                        );
131                    }
132                }
133                // self.buffer[..data_len - available_at_end].copy_from_slice(&data[available_at_end..]);
134                unsafe {
135                    std::ptr::copy_nonoverlapping(
136                        data.as_ptr().add(available_at_end),
137                        self.buffer.as_mut_ptr(),
138                        data_len - available_at_end,
139                    );
140                }
141
142                self.tail = data_len - available_at_end; //Tail is now at start of buffer.
143            }
144        } else {
145            // Head is after tail - just write to the end.
146            // self.buffer[self.tail..self.tail + data_len].copy_from_slice(data);
147            unsafe {
148                std::ptr::copy_nonoverlapping(
149                    data.as_ptr(),
150                    self.buffer.as_mut_ptr().add(self.tail),
151                    data_len,
152                );
153            }
154
155            self.tail += data_len;
156        }
157
158        // Wrap around the tail if it reaches the end of the buffer.
159        if self.tail == self.buffer.len() {
160            self.tail = 0;
161        }
162    }
163
164    /// Resizes the buffer to accommodate more data.
165    ///
166    /// # Notes
167    /// - New capacity is always a power of two
168    /// - Maintains data continuity during resize
169    /// - Uses high-performance memory copying
170    fn resize_buffer(&mut self, new_capacity: usize) {
171        let new_capacity = new_capacity
172            .checked_next_power_of_two()
173            .unwrap_or(usize::MAX)
174            .max(self.initial_capacity);
175
176        let mut new_buffer = vec![0; new_capacity];
177
178        // Calculate the current data length BEFORE replacing the buffer
179        let current_len = self.len();
180
181        // Copy existing data directly to new buffer
182        if self.tail > self.head {
183            // Single contiguous span. A stray safe copy_from_slice used to run
184            // here in addition to the unsafe copy below, memcpy'ing the same
185            // region twice per resize; only the unsafe copy (matching the
186            // wrap-around branches) remains.
187            unsafe {
188                std::ptr::copy_nonoverlapping(
189                    self.buffer.as_ptr().add(self.head),
190                    new_buffer.as_mut_ptr(),
191                    current_len,
192                );
193            }
194        } else if current_len > 0 {
195            let first_part = self.buffer.len() - self.head;
196            // new_buffer[..first_part].copy_from_slice(&self.buffer[self.head..]);
197            unsafe {
198                std::ptr::copy_nonoverlapping(
199                    self.buffer.as_ptr().add(self.head),
200                    new_buffer.as_mut_ptr(),
201                    first_part,
202                );
203            }
204
205            // new_buffer[first_part..current_len].copy_from_slice(&self.buffer[..self.tail]);
206            unsafe {
207                std::ptr::copy_nonoverlapping(
208                    self.buffer.as_ptr(),
209                    new_buffer.as_mut_ptr().add(first_part),
210                    current_len - first_part,
211                );
212            }
213        }
214
215        self.buffer = new_buffer;
216        self.head = 0;
217        self.tail = current_len;
218    }
219
220    /// Skips the PreviousTagSize field (4 bytes) after reading a tag.
221    #[inline]
222    fn skip_previous_tag_size(&mut self) {
223        self.head += PREVIOUS_TAG_SIZE_LENGTH;
224        if self.head >= self.buffer.len() {
225            self.head -= self.buffer.len(); // Handle wrap-around.
226        }
227    }
228
229    /// Attempts to parse the FLV file header from the buffer.
230    ///
231    /// # Returns
232    /// - `Ok(())` if parsing succeeds or needs to be deferred
233    /// - `Err` if an IO error occurs during parsing
234    fn parse_flv_header(&mut self) -> io::Result<()> {
235        if self.header_parsed {
236            return Ok(()); // Header already parsed
237        }
238
239        // Check if there is enough data in buffer for an FLV header.
240        if self.len() < FLV_HEADER_LENGTH {
241            return Ok(()); // Not enough data, skip parsing.
242        }
243
244        let mut temp_buffer = [0u8; FLV_HEADER_LENGTH];
245        self.read_data(self.head, &mut temp_buffer);
246
247        let mut reader = Cursor::new(&temp_buffer);
248
249        // Check if the file starts with "FLV"
250        let flv_signature = reader.read_u24::<BigEndian>()?;
251        debug!("FLV Signature: {:#X}", flv_signature);
252        if flv_signature != 0x464C56 {
253            // "FLV" in ASCII
254            self.skip_previous_tag_size();
255            self.header_parsed = true;
256            return Ok(()); // Skip files that don't start with "FLV"
257        }
258
259        // Read the version (should be 1)
260        let version = reader.read_u8()?;
261        debug!("FLV Version: {}", version);
262        if version != 1 {
263            self.skip_previous_tag_size();
264            self.header_parsed = true;
265            return Ok(()); // Skip if the version is not 1
266        }
267
268        // Read the flags (should be 0x05 for audio and video presence)
269        let flags = reader.read_u8()?;
270        debug!("FLV Flags: {:#X}", flags);
271        match flags {
272            0x01 => debug!("Audio: No, Video: Yes"),
273            0x04 => debug!("Audio: Yes, Video: No"),
274            0x05 => debug!("Audio: Yes, Video: Yes"),
275            _ => {
276                self.skip_previous_tag_size();
277                self.header_parsed = true;
278                return Ok(());
279            } // Skip invalid flags
280        }
281
282        // Read the data offset (indicates where the data starts)
283        let data_offset = reader.read_u32::<BigEndian>()?;
284        if data_offset != 9 {
285            self.skip_previous_tag_size();
286            self.header_parsed = true;
287            return Ok(());
288        }
289        debug!("FLV Data Offset: {}", data_offset);
290
291        // Store the header information
292        self.flv_header = Some(FlvHeader { flags });
293
294        // Mark the header as parsed
295        self.header_parsed = true;
296
297        // Advance cursor past the FLV header and the subsequent PreviousTagSize
298        self.head += FLV_HEADER_LENGTH;
299        self.skip_previous_tag_size();
300
301        Ok(())
302    }
303
304    /// Returns a reference to the parsed FLV header, if available.
305    pub fn get_flv_header(&self) -> Option<&FlvHeader> {
306        self.flv_header.as_ref()
307    }
308
309    /// Attempts to parse and return a complete FLV tag from the buffer.
310    ///
311    /// # Returns
312    /// - `Some(FlvTag)` if a complete tag is available
313    /// - `None` if there isn't enough data for a complete tag
314    pub fn get_flv_tag(&mut self) -> Option<FlvTag> {
315        // Check if there's enough data to read a complete FLV Tag
316        if self.len() < FLV_TAG_HEADER_LENGTH {
317            return None; // Not enough data to read a tag header
318        }
319
320        // Ensure the FLV file header is parsed or skip if not valid
321        if let Err(e) = self.parse_flv_header() {
322            warn!("Failed parsing FLV header: {}", e);
323            return None; // Return None if header parsing fails
324        }
325
326        // Create a reader that can handle buffer wrap-around
327        let mut header_reader = CursorRing::new(&self.buffer, self.head, self.buffer.len());
328
329        // Parse the FLV Tag Header
330        let tag_type = header_reader.read_u8().ok()?;
331        let data_size = header_reader.read_u24::<BigEndian>().ok()?;
332        let timestamp = header_reader.read_u24::<BigEndian>().ok()?;
333        let timestamp_ext = header_reader.read_u8().ok()?;
334        let _stream_id = header_reader.read_u24::<BigEndian>().ok()?;
335
336        // Calculate the total size of the tag, including the header and PreviousTagSize
337        let total_tag_size = FLV_TAG_HEADER_LENGTH + data_size as usize + PREVIOUS_TAG_SIZE_LENGTH;
338
339        // Check if there's enough data to read the full tag data
340        if self.len() < total_tag_size {
341            return None; // Not enough data to read the tag data and PreviousTagSize
342        }
343
344        // Read the tag data into a fresh buffer. Append the ring segment(s)
345        // instead of `vec![0u8; data_size]` + overwrite, which zero-filled the
346        // whole payload (a full-payload memset per FLV tag on the ingest path)
347        // just to immediately overwrite it.
348        //
349        // The per-tag allocation itself is required: the returned tag's
350        // `Bytes` escapes into long-lived consumers (e.g. an RTMP GOP cache
351        // that replays it to late joiners), so a pooled or recycled buffer
352        // could never be reclaimed while any such consumer still holds it.
353        let mut data = Vec::with_capacity(data_size as usize);
354        let data_start = self.head + FLV_TAG_HEADER_LENGTH;
355        self.read_data_append(data_start, data_size as usize, &mut data);
356
357        // Create the FLV Tag
358        let flv_tag = FlvTag {
359            header: FlvTagHeader {
360                tag_type,
361                data_size,
362                timestamp,
363                timestamp_ext,
364                stream_id: 0, // Always 0
365            },
366            data: bytes::Bytes::from(data),
367            previous_tag_size: (FLV_TAG_HEADER_LENGTH + data_size as usize) as u32, // Store PreviousTagSize
368        };
369
370        // Advance the head past the entire tag (header + data + PreviousTagSize)
371        self.head += total_tag_size;
372
373        // Handle wrap-around for head.
374        while self.head >= self.buffer.len() {
375            self.head -= self.buffer.len();
376        }
377
378        Some(flv_tag)
379    }
380
381    /// Reads data from the ring buffer, safely handling wrap-around.
382    ///
383    /// # Performance
384    /// Uses unsafe `copy_nonoverlapping` for optimal memory copying,
385    /// with careful bounds checking to ensure safety.
386    fn read_data(&self, start: usize, buffer: &mut [u8]) {
387        let buffer_size = self.buffer.len();
388        if buffer_size == 0 || buffer.is_empty() {
389            return;
390        }
391
392        let normalized_start = start % buffer_size;
393        let request_len = buffer.len();
394        let safe_len = request_len.min(buffer_size);
395
396        let (first_len, second_len) = {
397            let virtual_end = normalized_start + safe_len;
398            if virtual_end <= buffer_size {
399                (safe_len, 0)
400            } else {
401                (
402                    buffer_size - normalized_start,
403                    safe_len - (buffer_size - normalized_start),
404                )
405            }
406        };
407
408        // buffer[..first_len].copy_from_slice(&self.buffer[normalized_start..normalized_start  + first_len]);
409        unsafe {
410            std::ptr::copy_nonoverlapping(
411                self.buffer.as_ptr().add(normalized_start),
412                buffer.as_mut_ptr(),
413                first_len,
414            );
415        }
416
417        if second_len > 0 {
418            // buffer[first_len..first_len + second_len].copy_from_slice(&self.buffer[..second_len]);
419            unsafe {
420                std::ptr::copy_nonoverlapping(
421                    self.buffer.as_ptr(),
422                    buffer.as_mut_ptr().add(first_len),
423                    second_len,
424                );
425            }
426        }
427    }
428
429    /// Appends `len` bytes starting at `start` (ring-wrapped) onto `out`,
430    /// splitting across the wrap point like [`read_data`] but without a
431    /// pre-zeroed destination — the caller passes a `Vec::with_capacity(len)`,
432    /// so no calloc/memset is paid before the copy. Safe `extend_from_slice`
433    /// (a memcpy) replaces the unsafe pointer copies since the source is a plain
434    /// slice of `self.buffer`.
435    fn read_data_append(&self, start: usize, len: usize, out: &mut Vec<u8>) {
436        let buffer_size = self.buffer.len();
437        if buffer_size == 0 || len == 0 {
438            return;
439        }
440
441        let normalized_start = start % buffer_size;
442        let safe_len = len.min(buffer_size);
443        let virtual_end = normalized_start + safe_len;
444        if virtual_end <= buffer_size {
445            out.extend_from_slice(&self.buffer[normalized_start..normalized_start + safe_len]);
446        } else {
447            let first_len = buffer_size - normalized_start;
448            out.extend_from_slice(&self.buffer[normalized_start..]);
449            out.extend_from_slice(&self.buffer[..safe_len - first_len]);
450        }
451    }
452}
453
454/// A cursor implementation that handles ring buffer wrap-around.
455/// Used for reading FLV tag headers efficiently.
456struct CursorRing<'a> {
457    buffer: &'a [u8],
458    position: usize,
459    buffer_size: usize,
460}
461
462impl<'a> CursorRing<'a> {
463    fn new(buffer: &'a [u8], start: usize, buffer_size: usize) -> Self {
464        Self {
465            buffer,
466            position: start,
467            buffer_size,
468        }
469    }
470}
471
472impl<'a> io::Read for CursorRing<'a> {
473    #[inline(always)]
474    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
475        let mut bytes_read = 0;
476        let buf_len = buf.len();
477        let buffer_end = self.buffer_size;
478
479        let wrap_around = self.position + buf_len > buffer_end;
480
481        if wrap_around {
482            let first_part_len = buffer_end - self.position;
483            // buf[..first_part_len].copy_from_slice(&self.buffer[self.position..]);
484            unsafe {
485                std::ptr::copy_nonoverlapping(
486                    self.buffer.as_ptr().add(self.position),
487                    buf.as_mut_ptr(),
488                    first_part_len,
489                );
490            }
491            self.position = 0;
492            bytes_read += first_part_len;
493        }
494
495        let remaining_len = buf_len - bytes_read;
496        if remaining_len > 0 {
497            // buf[bytes_read..].copy_from_slice(&self.buffer[self.position..self.position + remaining_len]);
498            unsafe {
499                std::ptr::copy_nonoverlapping(
500                    self.buffer.as_ptr().add(self.position),
501                    buf.as_mut_ptr().add(bytes_read),
502                    remaining_len,
503                );
504            }
505            self.position += remaining_len;
506            bytes_read += remaining_len;
507        }
508
509        if self.position == buffer_end {
510            self.position = 0;
511        }
512
513        Ok(bytes_read)
514    }
515}
516
517#[cfg(test)]
518mod tests {
519
520    #[test]
521    fn test_len() {
522        assert_eq!(base_len(0, 10, 16), len(0, 10, 16));
523        assert_eq!(base_len(1, 3, 16), len(1, 3, 16));
524        assert_eq!(base_len(3, 5, 16), len(3, 5, 16));
525        assert_eq!(base_len(4, 4, 16), len(4, 4, 16));
526        assert_eq!(base_len(10, 2, 16), len(10, 2, 16));
527        assert_eq!(base_len(8, 3, 16), len(8, 3, 16));
528        assert_eq!(base_len(9, 0, 16), len(9, 0, 16));
529    }
530
531    fn len(head: usize, tail: usize, buffer_len: usize) -> usize {
532        (tail.wrapping_sub(head)) & (buffer_len - 1)
533    }
534
535    fn base_len(head: usize, tail: usize, buffer_len: usize) -> usize {
536        if tail >= head {
537            tail - head
538        } else {
539            buffer_len - head + tail
540        }
541    }
542
543    #[test]
544    fn test_available_space() {
545        assert_eq!(base_available_space(0, 10, 16), available_space(0, 10, 16));
546        assert_eq!(base_available_space(1, 3, 16), available_space(1, 3, 16));
547        assert_eq!(base_available_space(3, 5, 16), available_space(3, 5, 16));
548        assert_eq!(base_available_space(4, 5, 16), available_space(4, 5, 16));
549        assert_eq!(base_available_space(10, 2, 16), available_space(10, 2, 16));
550        assert_eq!(base_available_space(8, 3, 16), available_space(8, 3, 16));
551        assert_eq!(base_available_space(9, 0, 16), available_space(9, 0, 16));
552    }
553
554    fn base_available_space(head: usize, tail: usize, buffer_len: usize) -> usize {
555        buffer_len - len(head, tail, buffer_len) - 1
556    }
557
558    fn available_space(head: usize, tail: usize, buffer_len: usize) -> usize {
559        (head.wrapping_sub(tail).wrapping_sub(1)) & (buffer_len - 1)
560    }
561
562    /// `read_data_append` (the zero-fill-free tag reader) must produce the
563    /// same bytes as `read_data`, including across the ring wrap point.
564    #[test]
565    fn read_data_append_matches_read_data_across_wrap() {
566        use super::FlvBuffer;
567        let mut b = FlvBuffer::new();
568        // 8-byte ring holding 0..8; head=6, tail=3 => len 5, so a 5-byte read
569        // wraps: segment [6,7] then [0,1,2].
570        b.buffer = (0u8..8).collect();
571        b.head = 6;
572        b.tail = 3;
573
574        let mut via_read_data = vec![0u8; 5];
575        b.read_data(6, &mut via_read_data);
576
577        let mut via_append = Vec::with_capacity(5);
578        b.read_data_append(6, 5, &mut via_append);
579
580        assert_eq!(
581            via_append,
582            vec![6u8, 7, 0, 1, 2],
583            "wrap-around segments must be [6,7] then [0,1,2]"
584        );
585        assert_eq!(
586            via_append, via_read_data,
587            "read_data_append must match read_data byte-for-byte"
588        );
589
590        // Non-wrapping read from the same buffer.
591        let mut contiguous = Vec::with_capacity(3);
592        b.read_data_append(1, 3, &mut contiguous);
593        assert_eq!(contiguous, vec![1u8, 2, 3]);
594    }
595}