Skip to content

Commit 7988c43

Browse files
committed
core (io): Bypass MediaSourceStream buffer for large reads.
Currently, all reads flow through the MSS ring buffer. If a read is so large that it would do multiple loops around the ring buffer, then read directly into the destination buffer from the source and writeback only the amount required to the ring buffer to maintain seekback capabilities. This optimization provides significant time-to-start improvements for movies with many attachments.
1 parent ea66e4c commit 7988c43

1 file changed

Lines changed: 149 additions & 42 deletions

File tree

symphonia-core/src/io/media_source_stream.rs

Lines changed: 149 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ impl Default for MediaSourceStreamOptions {
4646
/// excess data buffered on consecutive `seek()` calls.
4747
///
4848
/// Second, to better support non-seekable sources, `MediaSourceStream` implements a configurable
49-
/// length buffer cache. By default, the buffer caches allows backtracking by up-to the minimum of
50-
/// either `buffer_len - 32kB` or the total number of bytes read since instantiation or the last
51-
/// buffer cache invalidation. Note that regular a `seek()` will invalidate the buffer cache.
49+
/// length buffer. By default, the buffer allows backtracking by up-to the minimum of either
50+
/// `buffer_len - 32kB` or the total number of bytes read since instantiation or the last buffer
51+
/// invalidation. Note that regular a `seek()` will invalidate the buffer.
5252
pub struct MediaSourceStream<'s> {
5353
/// The source reader.
5454
inner: Box<dyn MediaSource + 's>,
@@ -90,17 +90,17 @@ impl<'s> MediaSourceStream<'s> {
9090
}
9191
}
9292

93-
/// Returns if the buffer has been exhausted This is a marginally more efficient way of checking
94-
/// if `unread_buffer_len() == 0`.
93+
/// Returns if the ring buffer has been exhausted. This is a marginally more efficient way of
94+
/// checking if `unread_buffer_len() == 0`.
9595
#[inline(always)]
96-
fn is_buffer_exhausted(&self) -> bool {
96+
fn is_buffer_empty(&self) -> bool {
9797
self.read_pos == self.write_pos
9898
}
9999

100-
/// If the buffer has been exhausted, fetch a new block of data to replenish the buffer.
101-
fn fetch(&mut self) -> io::Result<()> {
100+
/// If the ring buffer has been exhausted, fetch a new block of data to replenish the buffer.
101+
fn refill_buffer(&mut self) -> io::Result<()> {
102102
// Only fetch when the ring buffer is empty.
103-
if self.is_buffer_exhausted() {
103+
if self.is_buffer_empty() {
104104
// Split the vector at the write position to get slices of the two contiguous regions of
105105
// the ring buffer.
106106
let (vec1, vec0) = self.ring.split_at_mut(self.write_pos);
@@ -135,27 +135,115 @@ impl<'s> MediaSourceStream<'s> {
135135
Ok(())
136136
}
137137

138-
/// If the buffer has been exhausted, fetch a new block of data to replenish the buffer. If
139-
/// no more data could be fetched, return an end-of-stream error.
140-
fn fetch_or_eof(&mut self) -> io::Result<()> {
141-
self.fetch()?;
138+
/// If the ring buffer has been exhausted, fetch a new block of data to replenish the buffer. If
139+
/// no more data could be fetched, return an UnexpectedEof error.
140+
fn refill_buffer_or_eof(&mut self) -> io::Result<()> {
141+
self.refill_buffer()?;
142142

143-
if self.is_buffer_exhausted() {
143+
if self.is_buffer_empty() {
144144
return unexpected_eof_error();
145145
}
146146

147147
Ok(())
148148
}
149149

150-
/// Advances the read position by `len` bytes, taking into account wrap-around.
150+
/// Read as much as possible from the ring buffer to fill `buf`.
151+
fn read_from_buffer<'b>(&mut self, mut buf: &'b mut [u8]) -> &'b mut [u8] {
152+
// Keep reading from the ring buffer until either the ring buffer is exhausted, or `buf` has
153+
// been filled.
154+
while !buf.is_empty() {
155+
let Some(src) = self.maybe_get_readable_slice()
156+
else {
157+
break;
158+
};
159+
160+
let count = buf.len().min(src.len());
161+
let (dst, rest) = buf.split_at_mut(count);
162+
dst.copy_from_slice(&src[..count]);
163+
buf = rest;
164+
self.consume(count);
165+
}
166+
167+
// Return unwritten portion of `buf`.
168+
buf
169+
}
170+
171+
/// Read from the inner source directly to fill `buf`, and then writeback only the required
172+
/// amount into the ring buffer. For large reads, this removes most redundant copies through
173+
/// the ring buffer.
174+
///
175+
/// Panics if the ring buffer is not empty.
176+
fn read_from_source<'b>(&mut self, buf: &'b mut [u8]) -> io::Result<&'b mut [u8]> {
177+
assert!(self.is_buffer_empty());
178+
179+
let read_len = self.inner.read(buf)?;
180+
181+
// Update the read and write positions, taking into account wrap-around. Since the ring
182+
// buffer was exhausted these are equal.
183+
let ring_pos = self.write_pos.wrapping_add(read_len) & self.ring_mask;
184+
self.write_pos = ring_pos;
185+
self.read_pos = ring_pos;
186+
187+
// Update the stream position accounting.
188+
self.abs_pos += read_len as u64;
189+
self.rel_pos += read_len as u64;
190+
191+
// Clamp to the largest possible block size that fits best for the amount read.
192+
self.read_block_len = read_len.min(Self::MAX_BLOCK_LEN).next_power_of_two();
193+
194+
// Now, writeback into the ring buffer using what was read into `buf`.
195+
196+
// The amount of bytes to writeback into the ring buffer.
197+
let wb_len = read_len.min(self.ring.len());
198+
// The write position at which the writeback will start.
199+
let wb_pos = ring_pos.wrapping_sub(wb_len) & self.ring_mask;
200+
201+
// Split the ring buffer into the two continguous write regions.
202+
let (wb1, wb0) = self.ring.split_at_mut(wb_pos);
203+
204+
let src = &buf[read_len - wb_len..read_len];
205+
206+
if wb0.len() >= wb_len {
207+
wb0[..wb_len].copy_from_slice(src);
208+
}
209+
else {
210+
let (src0, src1) = src.split_at(wb0.len());
211+
212+
let rem = wb_len - wb0.len();
213+
wb0.copy_from_slice(src0);
214+
wb1[..rem].copy_from_slice(&src1[..rem]);
215+
};
216+
217+
// Return unwritten portion of `buf`.
218+
Ok(&mut buf[read_len..])
219+
}
220+
221+
/// Returns `true` if an operation of length `len` is considered large and eligible for
222+
/// optimizations that bypass the ring buffer.
151223
#[inline(always)]
152-
fn consume(&mut self, len: usize) {
153-
self.read_pos = (self.read_pos + len) & self.ring_mask;
224+
fn is_large_operation(&self, len: u64) -> bool {
225+
len > 2 * self.ring.len() as u64
154226
}
155227

156-
/// Gets the largest contiguous slice of buffered data starting from the read position.
228+
/// Try to get the current contiguous slice of readable data from the ring buffer. Returns
229+
/// `None` if the ring buffer is empty.
157230
#[inline(always)]
158-
fn continguous_buf(&self) -> &[u8] {
231+
fn maybe_get_readable_slice(&self) -> Option<&[u8]> {
232+
if self.write_pos > self.read_pos {
233+
Some(&self.ring[self.read_pos..self.write_pos])
234+
}
235+
else if self.write_pos < self.read_pos {
236+
Some(&self.ring[self.read_pos..])
237+
}
238+
else {
239+
None
240+
}
241+
}
242+
243+
/// Get the current contiguous slice of readable data from the ring buffer. Returns an empty
244+
/// slice if the ring buffer is empty.
245+
#[inline(always)]
246+
fn get_readable_slice(&self) -> &[u8] {
159247
if self.write_pos >= self.read_pos {
160248
&self.ring[self.read_pos..self.write_pos]
161249
}
@@ -164,6 +252,12 @@ impl<'s> MediaSourceStream<'s> {
164252
}
165253
}
166254

255+
/// Advances the read position by `len` bytes, taking into account wrap-around.
256+
#[inline(always)]
257+
fn consume(&mut self, len: usize) {
258+
self.read_pos = (self.read_pos + len) & self.ring_mask;
259+
}
260+
167261
/// Resets the read-ahead buffer, and sets the absolute stream position to `pos`.
168262
fn reset(&mut self, pos: u64) {
169263
self.read_pos = 0;
@@ -190,20 +284,36 @@ impl io::Read for MediaSourceStream<'_> {
190284
fn read(&mut self, mut buf: &mut [u8]) -> io::Result<usize> {
191285
let read_len = buf.len();
192286

193-
while !buf.is_empty() {
194-
// Refill the the buffer cache if required.
195-
self.fetch()?;
287+
// First, read as much as possible from the ring buffer.
288+
buf = self.read_from_buffer(buf);
196289

197-
// Consume bytes from the readable portion of the buffer cache and copy them into the
198-
// remaining portion of the caller's buffer.
199-
match self.continguous_buf().read(buf) {
200-
Ok(0) => break,
201-
Ok(count) => {
202-
buf = &mut buf[count..];
203-
self.consume(count);
290+
// Then, if the remainder is large optimizible, read directly from the source into the
291+
// remaining portion of `buf`. Note, to be true, `buf` must have length significantly larger
292+
// than 0, which means the ring buffer was emptied in the first step. Therefore, it is safe
293+
// to call `read_from_source` which panics if ring buffer is not empty.
294+
if self.is_large_operation(buf.len() as u64) {
295+
buf = self.read_from_source(buf)?;
296+
}
297+
else {
298+
// Or, continuously buffer data into the ring buffer from the source, and then read from
299+
// the ring buffer.
300+
while !buf.is_empty() {
301+
// Refill the the ring buffer as required.
302+
self.refill_buffer()?;
303+
304+
// Consume bytes from the readable portion of the ring buffer and copy them into the
305+
// remaining portion of the caller's buffer.
306+
match self.maybe_get_readable_slice() {
307+
Some(src) => {
308+
let count = buf.len().min(src.len());
309+
let (dst, rest) = buf.split_at_mut(count);
310+
dst.copy_from_slice(&src[..count]);
311+
buf = rest;
312+
self.consume(count);
313+
}
314+
// The fetch operation did not buffer anything. There is no more to read.
315+
None => break,
204316
}
205-
Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
206-
Err(e) => return Err(e),
207317
}
208318
}
209319

@@ -240,8 +350,8 @@ impl ReadBytes for MediaSourceStream<'_> {
240350
// This function, read_byte, is inlined for performance. To reduce code bloat, place the
241351
// read-ahead buffer replenishment in a seperate function. Call overhead will be negligible
242352
// compared to the actual underlying read.
243-
if self.is_buffer_exhausted() {
244-
self.fetch_or_eof()?;
353+
if self.is_buffer_empty() {
354+
self.refill_buffer_or_eof()?;
245355
}
246356

247357
let value = self.ring[self.read_pos];
@@ -253,7 +363,7 @@ impl ReadBytes for MediaSourceStream<'_> {
253363
fn read_double_bytes(&mut self) -> io::Result<[u8; 2]> {
254364
let mut bytes = [0; 2];
255365

256-
let buf = self.continguous_buf();
366+
let buf = self.get_readable_slice();
257367

258368
if buf.len() >= 2 {
259369
bytes.copy_from_slice(&buf[..2]);
@@ -271,7 +381,7 @@ impl ReadBytes for MediaSourceStream<'_> {
271381
fn read_triple_bytes(&mut self) -> io::Result<[u8; 3]> {
272382
let mut bytes = [0; 3];
273383

274-
let buf = self.continguous_buf();
384+
let buf = self.get_readable_slice();
275385

276386
if buf.len() >= 3 {
277387
bytes.copy_from_slice(&buf[..3]);
@@ -288,7 +398,7 @@ impl ReadBytes for MediaSourceStream<'_> {
288398
fn read_quad_bytes(&mut self) -> io::Result<[u8; 4]> {
289399
let mut bytes = [0; 4];
290400

291-
let buf = self.continguous_buf();
401+
let buf = self.get_readable_slice();
292402

293403
if buf.len() >= 4 {
294404
bytes.copy_from_slice(&buf[..4]);
@@ -341,18 +451,15 @@ impl ReadBytes for MediaSourceStream<'_> {
341451
// If the stream is seekable and the number of bytes to ignore is large, perform a seek
342452
// first. Note that ignored bytes are rewindable. Therefore, ensure the ring-buffer is
343453
// full after the seek just like if bytes were ignored by consuming them instead.
344-
let ring_len = self.ring.len() as u64;
345-
346-
// Only apply the optimization if seeking 2x or more than the ring-buffer size.
347-
while count >= 2 * ring_len && self.is_seekable() {
348-
let delta = count.clamp(0, i64::MAX as u64).sub(ring_len);
454+
while self.is_large_operation(count) && self.is_seekable() {
455+
let delta = count.min(i64::MAX as u64).sub(self.ring.len() as u64);
349456
self.seek(io::SeekFrom::Current(delta as i64))?;
350457
count -= delta;
351458
}
352459

353460
// Ignore the remaining bytes be consuming samples from the ring-buffer.
354461
while count > 0 {
355-
self.fetch_or_eof()?;
462+
self.refill_buffer_or_eof()?;
356463
let discard_count = cmp::min(self.unread_buffer_len() as u64, count);
357464
self.consume(discard_count as usize);
358465
count -= discard_count;

0 commit comments

Comments
 (0)