// Symphonia // Copyright (c) 2019-2022 The Project Symphonia Developers. // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use std::mem; use std::vec::Vec; use symphonia_core::audio::{AudioBuffer, Signal}; use symphonia_core::checksum::Md5; use symphonia_core::io::Monitor; /// `Validator` computes the MD5 checksum of an audio stream taking into account the peculiarities /// of FLAC's MD5 validation scheme. #[derive(Default)] pub struct Validator { state: Md5, buf: Vec, } impl Validator { /// Processes the audio buffer and updates the state of the validator. pub fn update(&mut self, buf: &AudioBuffer, bps: u32) { // The MD5 checksum is calculated on a buffer containing interleaved audio samples of the // correct sample width. While FLAC can encode and decode samples of arbitrary bit widths, // the samples in the buffer must be a multiple of 8-bits. // // Additionally, Symphonia's AudioBuffer's are in planar format, and the FLAC decoder works // internally on signed 32-bit samples exclusively. // // Therefore, to compute the checksum, the audio buffer samples must truncated to the // correct bit-width, interlaced, and converted to a little-endian byte buffer. The byte // buffer can then be passed to the MD5 algorithm for hashing. // Round the sample bit width up to the nearest byte. let bytes_per_sample = match bps { 0 => return, 1..=8 => 1, 9..=16 => 2, 17..=24 => 3, 25..=32 => 4, _ => unreachable!(), }; let n_channels = buf.spec().channels.count(); let n_frames = buf.frames(); // Calculate the total size of all the samples in bytes. let buf_len = n_channels * n_frames * bytes_per_sample; // Ensure the byte buffer length can accomodate all the samples. if self.buf.len() < buf_len { self.buf.resize(buf_len, 0u8); } // Populate the hash buffer with samples truncated to the correct width. A &[u8] slice of // all the samples in hash buffer will be returned. let buf_slice = match bytes_per_sample { 1 => copy_as_i8(buf, &mut self.buf, n_channels, n_frames), 2 => copy_as_i16(buf, &mut self.buf, n_channels, n_frames), 3 => copy_as_i24(buf, &mut self.buf, n_channels, n_frames), 4 => copy_as_i32(buf, &mut self.buf, n_channels, n_frames), _ => unreachable!(), }; // Update the MD5 state. self.state.process_buf_bytes(buf_slice); } /// Get the checksum. pub fn md5(&mut self) -> [u8; 16] { self.state.md5() } } fn copy_as_i24<'a>( samples: &AudioBuffer, buf: &'a mut [u8], n_channels: usize, n_frames: usize, ) -> &'a [u8] { const SIZE_OF_I24: usize = 24 / 8; for ch in 0..n_channels { for (out, sample) in buf.chunks_exact_mut(SIZE_OF_I24).skip(ch).step_by(n_channels).zip(samples.chan(ch)) { out.copy_from_slice(&sample.to_le_bytes()[0..SIZE_OF_I24]); } } &buf[..n_channels * n_frames * SIZE_OF_I24] } macro_rules! copy_as { ($name:ident, $type:ty) => { fn $name<'a>( samples: &AudioBuffer, buf: &'a mut [u8], n_channels: usize, n_frames: usize, ) -> &'a [u8] { for ch in 0..n_channels { for (out, sample) in buf .chunks_exact_mut(mem::size_of::<$type>()) .skip(ch) .step_by(n_channels) .zip(samples.chan(ch)) { out.copy_from_slice(&(*sample as $type).to_le_bytes()); } } &buf[..n_channels * n_frames * mem::size_of::<$type>()] } }; } copy_as!(copy_as_i8, i8); copy_as!(copy_as_i16, i16); copy_as!(copy_as_i32, i32);