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}