Skip to content

Commit 5474b22

Browse files
committed
docs: rewrite and clarify decoder documentation and API comments
- Improve clarity and conciseness of doc comments for all decoders - Remove redundant and overly verbose explanations - Add missing details to trait and method docs - Update examples to use consistent file paths and types - Move Settings struct visibility to crate-internal - Refactor Path/PathBuf decoder constructors for optimal hinting - Standardize iterator and trait method documentation across formats - Remove misleading or outdated implementation notes - Fix Zero source bits_per_sample to return None - Update seek test attributes for feature consistency
1 parent 5e815ad commit 5474b22

File tree

11 files changed

+298
-592
lines changed

11 files changed

+298
-592
lines changed

src/decoder/builder.rs

Lines changed: 120 additions & 160 deletions
Large diffs are not rendered by default.

src/decoder/flac.rs

Lines changed: 22 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
//!
77
//! # Features
88
//!
9-
//! - **Bit depths**: Full support for 8, 16, 24, and 32-bit audio (including 12 and 20-bit)
9+
//! - **Bit depths**: Full support for 8, 12, 16, 20, 24, and 32-bit audio
1010
//! - **Sample rates**: Supports all FLAC-compatible sample rates (1Hz to 655,350Hz)
1111
//! - **Channels**: Supports mono, stereo, and multi-channel audio (up to 8 channels)
1212
//! - **Seeking**: Full forward and backward seeking with sample-accurate positioning
@@ -80,7 +80,7 @@ use crate::{
8080

8181
/// Reader options for `claxon` FLAC decoder.
8282
///
83-
/// Configured to skip metadata parsing and vorbis comments for faster initialization.
83+
/// Configured to skip metadata parsing and Vorbis comments for faster initialization.
8484
/// This improves decoder creation performance by only parsing essential stream information
8585
/// needed for audio playback.
8686
///
@@ -139,7 +139,7 @@ where
139139
///
140140
/// Used for calculating the correct memory layout when accessing interleaved samples.
141141
/// FLAC blocks can have variable sizes, so this changes per block.
142-
current_block_channel_len: usize,
142+
current_block_samples_per_channel: usize,
143143

144144
/// Current position within the current block.
145145
///
@@ -297,7 +297,7 @@ where
297297
Ok(Self {
298298
reader: Some(reader),
299299
current_block: Vec::with_capacity(max_block_size),
300-
current_block_channel_len: 1,
300+
current_block_samples_per_channel: 1,
301301
current_block_off: 0,
302302
bits_per_sample: spec.bits_per_sample,
303303
sample_rate: SampleRate::new(sample_rate)
@@ -350,15 +350,10 @@ where
350350
{
351351
/// Returns the number of samples before parameters change.
352352
///
353-
/// For FLAC, this always returns `None` because audio parameters (sample rate, channels, bit
354-
/// depth) never change during the stream. This allows Rodio to optimize by not frequently
355-
/// checking for parameter changes.
356-
///
357-
/// # Implementation Note
353+
/// # Returns
358354
///
359-
/// FLAC streams have fixed parameters throughout their duration, unlike some formats
360-
/// that may have parameter changes at specific points. This enables optimizations
361-
/// in the audio pipeline by avoiding frequent parameter validation.
355+
/// For FLAC, this always returns `None` because audio parameters (sample rate, channels, bit
356+
/// depth) never change during the stream.
362357
#[inline]
363358
fn current_span_len(&self) -> Option<usize> {
364359
None
@@ -374,26 +369,17 @@ where
374369
///
375370
/// # Guarantees
376371
///
377-
/// The returned value is constant for the lifetime of the decoder and matches
378-
/// the channel count specified in the FLAC stream metadata.
372+
/// The returned value is constant for the lifetime of the decoder.
379373
#[inline]
380374
fn channels(&self) -> ChannelCount {
381375
self.channels
382376
}
383377

384378
/// Returns the sample rate in Hz.
385379
///
386-
/// Common rates that FLAC supports are:
387-
/// - **44.1kHz**: CD quality (most common)
388-
/// - **48kHz**: Professional audio standard
389-
/// - **96kHz**: High-resolution audio
390-
/// - **192kHz**: Ultra high-resolution audio
391-
///
392380
/// # Guarantees
393381
///
394-
/// The returned value is constant for the lifetime of the decoder and matches
395-
/// the sample rate specified in the FLAC stream metadata. This value is
396-
/// available immediately upon decoder creation.
382+
/// The returned value is constant for the lifetime of the decoder.
397383
#[inline]
398384
fn sample_rate(&self) -> SampleRate {
399385
self.sample_rate
@@ -404,32 +390,24 @@ where
404390
/// FLAC metadata contains the total number of samples, allowing accurate duration calculation.
405391
/// This is available immediately upon decoder creation without needing to scan the entire file.
406392
///
407-
/// Returns `None` only for malformed FLAC files missing sample count metadata.
408-
///
409-
/// # Accuracy
393+
/// # Returns
410394
///
411-
/// The duration is calculated from exact sample counts, providing sample-accurate
412-
/// timing information. This is more precise than duration estimates based on
413-
/// bitrate calculations used by lossy formats.
395+
/// Returns `None` only for malformed FLAC files missing sample count metadata.
414396
#[inline]
415397
fn total_duration(&self) -> Option<Duration> {
416398
self.total_duration
417399
}
418400

419401
/// Returns the bit depth of the audio samples.
420402
///
421-
/// FLAC is a lossless format that preserves the original bit depth:
422-
/// - 16-bit: Standard CD quality
423-
/// - 24-bit: Professional/high-resolution audio
424-
/// - 32-bit: Professional/studio quality
425-
/// - Other depths: 8, 12, and 20-bit are also supported
426-
///
427-
/// Always returns `Some(depth)` for valid FLAC streams.
428-
///
429403
/// # Implementation Note
430404
///
431-
/// The bit depth information is preserved from the original FLAC stream and
405+
/// Up to 24 bits of information is preserved from the original FLAC stream and
432406
/// used for proper sample scaling during conversion to Rodio's sample format.
407+
///
408+
/// # Returns
409+
///
410+
/// Always returns `Some(depth)` for valid FLAC streams.
433411
#[inline]
434412
fn bits_per_sample(&self) -> Option<u32> {
435413
Some(self.bits_per_sample)
@@ -476,12 +454,6 @@ where
476454
/// // Seek to 30 seconds into the track
477455
/// decoder.try_seek(Duration::from_secs(30)).unwrap();
478456
/// ```
479-
///
480-
/// # Implementation Details
481-
///
482-
/// The seeking implementation handles channel alignment to ensure that seeking
483-
/// to a specific time position results in the correct channel being returned
484-
/// for the first sample after the seek operation.
485457
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
486458
// Seeking should be "saturating", meaning: target positions beyond the end of the stream
487459
// are clamped to the end.
@@ -545,9 +517,9 @@ impl<R> Iterator for FlacDecoder<R>
545517
where
546518
R: Read + Seek,
547519
{
548-
/// The type of items yielded by the iterator.
520+
/// The type of samples yielded by the iterator.
549521
///
550-
/// Returns `Sample` (typically `f32`) values representing individual audio samples.
522+
/// Returns `Sample` values representing individual audio samples.
551523
/// Samples are interleaved across channels in the order: channel 0, channel 1, etc.
552524
type Item = Sample;
553525

@@ -587,7 +559,7 @@ where
587559
if self.current_block_off < self.current_block.len() {
588560
// Read from current block.
589561
let real_offset = (self.current_block_off % self.channels.get() as usize)
590-
* self.current_block_channel_len
562+
* self.current_block_samples_per_channel
591563
+ self.current_block_off / self.channels.get() as usize;
592564
let raw_val = self.current_block[real_offset];
593565
self.current_block_off += 1;
@@ -622,7 +594,8 @@ where
622594
.read_next_or_eof(buffer)
623595
{
624596
Ok(Some(block)) => {
625-
self.current_block_channel_len = (block.len() / block.channels()) as usize;
597+
self.current_block_samples_per_channel =
598+
(block.len() / block.channels()) as usize;
626599
self.current_block = block.into_buffer();
627600
}
628601
Ok(None) | Err(_) => {
@@ -634,7 +607,7 @@ where
634607
}
635608
}
636609

637-
/// Returns bounds on the remaining length of the iterator.
610+
/// Returns bounds on the remaining amount of samples.
638611
///
639612
/// Provides accurate size estimates based on FLAC metadata when available.
640613
/// This information can be used by consumers for buffer pre-allocation

src/decoder/mod.rs

Lines changed: 56 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ use crate::{
6363
};
6464

6565
pub mod builder;
66-
pub use builder::{DecoderBuilder, Settings};
66+
pub use builder::DecoderBuilder;
67+
use builder::Settings;
6768

6869
mod utils;
6970

@@ -148,6 +149,7 @@ enum DecoderImpl<R: Read + Seek> {
148149
enum Unreachable {}
149150

150151
impl<R: Read + Seek> DecoderImpl<R> {
152+
/// Advances the decoder and returns the next sample.
151153
#[inline]
152154
fn next(&mut self) -> Option<Sample> {
153155
match self {
@@ -165,6 +167,7 @@ impl<R: Read + Seek> DecoderImpl<R> {
165167
}
166168
}
167169

170+
/// Returns the bounds on the remaining amount of samples.
168171
#[inline]
169172
fn size_hint(&self) -> (usize, Option<usize>) {
170173
match self {
@@ -182,6 +185,7 @@ impl<R: Read + Seek> DecoderImpl<R> {
182185
}
183186
}
184187

188+
/// Returns the number of samples before the current span ends.
185189
#[inline]
186190
fn current_span_len(&self) -> Option<usize> {
187191
match self {
@@ -199,6 +203,7 @@ impl<R: Read + Seek> DecoderImpl<R> {
199203
}
200204
}
201205

206+
/// Returns the number of audio channels.
202207
#[inline]
203208
fn channels(&self) -> ChannelCount {
204209
match self {
@@ -216,6 +221,7 @@ impl<R: Read + Seek> DecoderImpl<R> {
216221
}
217222
}
218223

224+
/// Returns the sample rate in Hz.
219225
#[inline]
220226
fn sample_rate(&self) -> SampleRate {
221227
match self {
@@ -258,7 +264,10 @@ impl<R: Read + Seek> DecoderImpl<R> {
258264

259265
/// Returns the bits per sample of this audio source.
260266
///
261-
/// For lossy formats this should always return `None`.
267+
/// # Format Support
268+
///
269+
/// For lossy formats this should always return `None` as bit depth is not a meaningful
270+
/// concept for compressed audio.
262271
#[inline]
263272
fn bits_per_sample(&self) -> Option<u32> {
264273
match self {
@@ -276,6 +285,7 @@ impl<R: Read + Seek> DecoderImpl<R> {
276285
}
277286
}
278287

288+
/// Attempts to seek to a given position in the current source.
279289
#[inline]
280290
fn try_seek(&mut self, pos: Duration) -> Result<(), SeekError> {
281291
match self {
@@ -554,7 +564,7 @@ impl TryFrom<bytes::Bytes> for Decoder<std::io::Cursor<bytes::Bytes>> {
554564
/// use rodio::Decoder;
555565
///
556566
/// // Embedded audio data (e.g., from include_bytes!)
557-
/// static AUDIO_DATA: &[u8] = include_bytes!("music.wav");
567+
/// static AUDIO_DATA: &[u8] = include_bytes!("../../assets/music.wav");
558568
/// let decoder = Decoder::try_from(AUDIO_DATA).unwrap();
559569
/// ```
560570
impl TryFrom<&'static [u8]> for Decoder<std::io::Cursor<&'static [u8]>> {
@@ -587,7 +597,7 @@ impl TryFrom<&'static [u8]> for Decoder<std::io::Cursor<&'static [u8]>> {
587597
/// Decoder::try_from(data)
588598
/// }
589599
///
590-
/// static EMBEDDED: &[u8] = include_bytes!("music.wav");
600+
/// static EMBEDDED: &[u8] = include_bytes!("../../assets/music.wav");
591601
/// let decoder1 = decode_audio(Cow::Borrowed(EMBEDDED)).unwrap();
592602
/// let owned_data = std::fs::read("music.wav").unwrap();
593603
/// let decoder2 = decode_audio(Cow::Owned(owned_data)).unwrap();
@@ -602,11 +612,11 @@ impl TryFrom<std::borrow::Cow<'static, [u8]>>
602612
}
603613
}
604614

605-
/// Converts a `PathBuf` into a `Decoder`.
615+
/// Converts a `&Path` into a `Decoder`.
606616
///
607617
/// This is a convenience method for loading audio files from filesystem paths. The file is opened
608-
/// and automatically configured with optimal settings including file size detection and seeking
609-
/// support.
618+
/// and automatically configured with optimal settings including file size detection, seeking
619+
/// support and format hint.
610620
///
611621
/// # Errors
612622
///
@@ -618,25 +628,24 @@ impl TryFrom<std::borrow::Cow<'static, [u8]>>
618628
/// # Examples
619629
/// ```no_run
620630
/// use rodio::Decoder;
621-
/// use std::path::PathBuf;
631+
/// use std::path::Path;
622632
///
623-
/// let path = PathBuf::from("music.mp3");
633+
/// let path = Path::new("music.mp3");
624634
/// let decoder = Decoder::try_from(path).unwrap();
625635
/// ```
626-
impl TryFrom<std::path::PathBuf> for Decoder<BufReader<std::fs::File>> {
636+
impl TryFrom<&std::path::Path> for Decoder<BufReader<std::fs::File>> {
627637
type Error = DecoderError;
628638

629-
fn try_from(path: std::path::PathBuf) -> Result<Self, Self::Error> {
630-
let file = std::fs::File::open(path).map_err(|e| Self::Error::IoError(e.to_string()))?;
631-
Self::try_from(file)
639+
fn try_from(path: &std::path::Path) -> Result<Self, Self::Error> {
640+
path.to_path_buf().try_into()
632641
}
633642
}
634643

635-
/// Converts a `&Path` into a `Decoder`.
644+
/// Converts a `PathBuf` into a `Decoder`.
636645
///
637646
/// This is a convenience method for loading audio files from filesystem paths. The file is opened
638-
/// and automatically configured with optimal settings including file size detection and seeking
639-
/// support.
647+
/// and automatically configured with optimal settings including file size detection, seeking
648+
/// support and format hint.
640649
///
641650
/// # Errors
642651
///
@@ -648,52 +657,43 @@ impl TryFrom<std::path::PathBuf> for Decoder<BufReader<std::fs::File>> {
648657
/// # Examples
649658
/// ```no_run
650659
/// use rodio::Decoder;
651-
/// use std::path::Path;
660+
/// use std::path::PathBuf;
652661
///
653-
/// let path = Path::new("music.mp3");
662+
/// let path = PathBuf::from("music.mp3");
654663
/// let decoder = Decoder::try_from(path).unwrap();
655664
/// ```
656-
impl TryFrom<&std::path::Path> for Decoder<BufReader<std::fs::File>> {
665+
impl TryFrom<std::path::PathBuf> for Decoder<BufReader<std::fs::File>> {
657666
type Error = DecoderError;
658667

659-
fn try_from(path: &std::path::Path) -> Result<Self, Self::Error> {
660-
let file = std::fs::File::open(path).map_err(|e| Self::Error::IoError(e.to_string()))?;
661-
Self::try_from(file)
662-
}
663-
}
668+
fn try_from(path: std::path::PathBuf) -> Result<Self, Self::Error> {
669+
let ext = path.extension().and_then(|e| e.to_str());
670+
let file = std::fs::File::open(&path).map_err(|e| DecoderError::IoError(e.to_string()))?;
664671

665-
impl Decoder<BufReader<std::fs::File>> {
666-
/// Creates a `Decoder` from any path-like type.
667-
///
668-
/// This is a convenience method that accepts anything that can be converted to a `Path`,
669-
/// including `&str`, `String`, `&Path`, `PathBuf`, etc. The file is opened and automatically
670-
/// configured with optimal settings including file size detection and seeking support.
671-
///
672-
/// # Errors
673-
///
674-
/// Returns `DecoderError::UnrecognizedFormat` if the audio format could not be determined
675-
/// or is not supported.
676-
///
677-
/// Returns `DecoderError::IoError` if the file cannot be opened or its metadata cannot be read.
678-
///
679-
/// # Examples
680-
/// ```no_run
681-
/// use rodio::Decoder;
682-
/// use std::path::Path;
683-
///
684-
/// // Works with &str
685-
/// let decoder = Decoder::from_path("music.mp3").unwrap();
686-
///
687-
/// // Works with String
688-
/// let path = String::from("music.mp3");
689-
/// let decoder = Decoder::from_path(path).unwrap();
690-
///
691-
/// // Works with Path and PathBuf
692-
/// let decoder = Decoder::from_path(Path::new("music.mp3")).unwrap();
693-
/// ```
694-
pub fn from_path(path: impl AsRef<std::path::Path>) -> Result<Self, DecoderError> {
695-
let file = std::fs::File::open(path).map_err(|e| DecoderError::IoError(e.to_string()))?;
696-
Self::try_from(file)
672+
let len = file
673+
.metadata()
674+
.map_err(|e| DecoderError::IoError(e.to_string()))?
675+
.len();
676+
677+
let mut builder = Self::builder()
678+
.with_data(BufReader::new(file))
679+
.with_byte_len(len)
680+
.with_seekable(true);
681+
682+
if let Some(ext) = ext {
683+
let hint = match ext {
684+
"adif" | "adts" => "aac",
685+
"caf" => "audio/x-caf",
686+
"m4a" | "m4b" | "m4p" | "m4r" | "mp4" => "audio/mp4",
687+
"bit" | "mpga" => "mp3",
688+
"mka" | "mkv" => "audio/matroska",
689+
"oga" | "ogm" | "ogv" | "ogx" | "spx" => "audio/ogg",
690+
"wave" => "wav",
691+
_ => ext,
692+
};
693+
builder = builder.with_hint(hint);
694+
}
695+
696+
builder.build()
697697
}
698698
}
699699

0 commit comments

Comments
 (0)