symphonia-wem 0.1.0

Symphonia demuxer for Wwise Encoded Media files
Documentation
//! Symphonia adapter for Wwise Encoded Media files.

pub(crate) mod bits;
mod chunk;
pub(crate) mod codebook;
mod endianness;
pub(crate) mod header;
pub(crate) mod math;
mod packet;

use symphonia_core::{
    codecs::{
        CodecParameters,
        audio::{AudioCodecParameters, well_known::CODEC_ID_VORBIS},
    },
    common::FourCc,
    errors::{Error, Result, SeekErrorKind},
    formats::{
        FormatId, FormatInfo, FormatOptions, FormatReader, MediaInfo, SeekMode, SeekTo, SeekedTo,
        Track,
        probe::{ProbeableFormat, Score, Scoreable},
    },
    io::{MediaSourceStream, ReadBytes as _, ScopedStream},
    meta::{Metadata, MetadataLog},
    packet::Packet,
};

use crate::{
    chunk::{Chunk, ChunkReader},
    header::{IdentHeader, SetupHeader},
    packet::{PacketInfo, PacketReader},
};

/// Format information.
const FORMAT_INFO: FormatInfo = FormatInfo {
    format: FormatId::new(FourCc::new(*b"wem ")),
    short_name: "wem",
    long_name: "Wwise Encoded Media",
};

/// Demuxer for a Wwise Encoded Media file.
pub struct WemReader<'s> {
    /// Read the data from any source.
    mss: MediaSourceStream<'s>,
    /// Information about the media.
    media_info: MediaInfo,
    /// Time ordered metadata.
    metadata: MetadataLog,
    /// Different codec bitstreams.
    tracks: Vec<Track>,
    /// Reader of packets.
    packet_reader: PacketReader,
}

impl<'s> WemReader<'s> {
    /// Try reading from a stream.
    ///
    /// # Errors
    ///
    /// - When reading the bytestream fails.
    pub fn try_new(mut mss: MediaSourceStream<'s>) -> Result<Self> {
        // Read the first 4 bytes of the stream, should be RIFF or RIFX, denotes the endianness
        let riff_marker = mss.read_quad_bytes()?;
        let endianness = match &riff_marker {
            b"RIFF" => endianness::Endianness::Little,
            b"RIFX" => endianness::Endianness::Big,
            _ => {
                return symphonia_core::errors::unsupported_error(
                    "wem: missing RIFF stream marker",
                );
            }
        };

        // Length of the next RIFF block
        let _riff_size = endianness.u32(&mut mss)?;

        // RIFF form
        let riff_form = mss.read_quad_bytes()?;
        if riff_form != *b"WAVE" {
            // We only support WAVE
            return symphonia_core::errors::unsupported_error("wem: RIFF form is not WAVE");
        }
        let mut format = None;

        // Read each chunk
        loop {
            let chunk = ChunkReader::read(endianness, &mut mss)?;

            match chunk {
                Chunk::Fmt(fmt) => {
                    // Store
                    format = Some(fmt);
                }
                Chunk::Data(last_offset) => {
                    // Ensure the format chunk is read
                    let Some(format) = format.as_ref() else {
                        return symphonia_core::errors::decode_error("wem: missing format chunk");
                    };

                    // Convert channels to vorbis channels
                    let channels =
                        symphonia_common::xiph::audio::vorbis::vorbis_channels_to_channels(
                            u8::try_from(format.channels.get())
                                .map_err(|_| Error::LimitError("wem: too many channels"))?,
                        )
                        .ok_or(Error::LimitError("wem: too many channels"))?;

                    // Create the fake header packets
                    let ident_packet = IdentHeader::new(format)?;
                    let setup_packet = SetupHeader::new(format, &mut mss)?;

                    // Set the packet info
                    let packet_info = format.packet_info(&setup_packet, last_offset);

                    // Create the extra data
                    let mut extra_data = ident_packet.into_inner().to_vec();
                    extra_data.append(&mut setup_packet.into_inner());

                    // Set the codec parameters for the track
                    let mut codec_params = AudioCodecParameters::new();
                    codec_params
                        .for_codec(CODEC_ID_VORBIS)
                        .with_sample_rate(format.sample_rate.get())
                        .with_channels(channels)
                        // Send the headers' packet data to vorbis
                        .with_extra_data(extra_data.into_boxed_slice());

                    // Create a track
                    let mut track = Track::new(0);
                    track.with_codec_params(CodecParameters::Audio(codec_params));
                    let tracks = vec![track];

                    // Create the media info
                    let media_info = MediaInfo::new();
                    let metadata = MetadataLog::default();

                    // Setup packet reader
                    let mut packet_reader = PacketReader::new(packet_info);
                    // Read the first packet, required for parsing the consecutive ones
                    packet_reader.read_first(&mut mss)?;

                    // This chunk is always last, if it's not found something went wrong with reading and an error will probably be thrown reading the next chunk
                    return Ok(Self {
                        mss,
                        media_info,
                        metadata,
                        tracks,
                        packet_reader,
                    });
                }
                Chunk::Hash => {
                    return symphonia_core::errors::unsupported_error(
                        "wem: can't handle hash chunks",
                    );
                }
            }
        }
    }
}

impl ProbeableFormat<'_> for WemReader<'_> {
    fn try_probe_new(
        mss: MediaSourceStream<'_>,
        _opts: FormatOptions,
    ) -> Result<Box<dyn FormatReader + '_>> {
        Ok(Box::new(WemReader::try_new(mss)?))
    }

    fn probe_data() -> &'static [symphonia_core::formats::probe::ProbeFormatData] {
        &[symphonia_core::support_format!(
            FORMAT_INFO,
            &["wem"],
            &["application/octet-stream"],
            &[b"RIFF", b"RIFX"]
        )]
    }
}

impl Scoreable for WemReader<'_> {
    fn score(mut src: ScopedStream<&mut MediaSourceStream<'_>>) -> Result<Score> {
        // Take the riff marker
        let riff_marker = src.read_quad_bytes()?;
        if riff_marker != *b"RIFF" && riff_marker != *b"RIFX" {
            return Ok(Score::Unsupported);
        }

        // Ignore the size of the riff block
        src.ignore_bytes(4)?;

        // Take the riff form
        let riff_form = src.read_quad_bytes()?;
        if riff_form != *b"WAVE" {
            return Ok(Score::Unsupported);
        }

        Ok(Score::Supported(255))
    }
}

impl FormatReader for WemReader<'_> {
    fn format_info(&self) -> &FormatInfo {
        &FORMAT_INFO
    }

    fn media_info(&self) -> &MediaInfo {
        &self.media_info
    }

    fn metadata(&mut self) -> Metadata<'_> {
        self.metadata.metadata()
    }

    fn seek(&mut self, _mode: SeekMode, _to: SeekTo) -> Result<SeekedTo> {
        // No tracks so nothing to seek
        if self.tracks.is_empty() {
            return symphonia_core::errors::seek_error(SeekErrorKind::Unseekable);
        }

        todo!()
    }

    fn tracks(&self) -> &[Track] {
        &self.tracks
    }

    fn next_packet(&mut self) -> Result<Option<Packet>> {
        // Read the next packet from the stream
        self.packet_reader.read(&mut self.mss, self.tracks[0].id)
    }

    fn into_inner<'s>(self: Box<Self>) -> MediaSourceStream<'s>
    where
        Self: 's,
    {
        self.mss
    }
}