Files
songbird/src/input/dca.rs
Kyle Simpson 7e4392ae68 Voice Rework -- Events, Track Queues (#806)
This implements a proof-of-concept for an improved audio frontend. The largest change is the introduction of events and event handling: both by time elapsed and by track events, such as ending or looping. Following on from this, the library now includes a basic, event-driven track queue system (which people seem to ask for unusually often). A new sample, `examples/13_voice_events`, demonstrates both the `TrackQueue` system and some basic events via the `~queue` and `~play_fade` commands.

Locks are removed from around the control of `Audio` objects, which should allow the backend to be moved to a more granular futures-based backend solution in a cleaner way.
2020-10-31 12:19:07 +01:00

138 lines
3.5 KiB
Rust

use super::{codec::OpusDecoderState, error::DcaError, Codec, Container, Input, Metadata, Reader};
use serde::Deserialize;
use std::{ffi::OsStr, io::BufReader, mem};
use tokio::{fs::File as TokioFile, io::AsyncReadExt};
/// Creates a streamed audio source from a DCA file.
/// Currently only accepts the [DCA1 format](https://github.com/bwmarrin/dca).
pub async fn dca<P: AsRef<OsStr>>(path: P) -> Result<Input, DcaError> {
_dca(path.as_ref()).await
}
async fn _dca(path: &OsStr) -> Result<Input, DcaError> {
let mut reader = TokioFile::open(path).await.map_err(DcaError::IoError)?;
let mut header = [0u8; 4];
// Read in the magic number to verify it's a DCA file.
reader
.read_exact(&mut header)
.await
.map_err(DcaError::IoError)?;
if header != b"DCA1"[..] {
return Err(DcaError::InvalidHeader);
}
let size = reader
.read_i32_le()
.await
.map_err(|_| DcaError::InvalidHeader)?;
// Sanity check
if size < 2 {
return Err(DcaError::InvalidSize(size));
}
let mut raw_json = Vec::with_capacity(size as usize);
let mut json_reader = reader.take(size as u64);
json_reader
.read_to_end(&mut raw_json)
.await
.map_err(DcaError::IoError)?;
let reader = BufReader::new(json_reader.into_inner().into_std().await);
let metadata: Metadata = serde_json::from_slice::<DcaMetadata>(raw_json.as_slice())
.map_err(DcaError::InvalidMetadata)?
.into();
let stereo = metadata.channels == Some(2);
Ok(Input::new(
stereo,
Reader::File(reader),
Codec::Opus(OpusDecoderState::new().map_err(DcaError::Opus)?),
Container::Dca {
first_frame: (size as usize) + mem::size_of::<i32>() + header.len(),
},
Some(metadata),
))
}
#[derive(Debug, Deserialize)]
pub(crate) struct DcaMetadata {
pub(crate) dca: Dca,
pub(crate) opus: Opus,
pub(crate) info: Option<Info>,
pub(crate) origin: Option<Origin>,
pub(crate) extra: Option<serde_json::Value>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Dca {
pub(crate) version: u64,
pub(crate) tool: Tool,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Tool {
pub(crate) name: String,
pub(crate) version: String,
pub(crate) url: String,
pub(crate) author: String,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Opus {
pub(crate) mode: String,
pub(crate) sample_rate: u32,
pub(crate) frame_size: u64,
pub(crate) abr: u64,
pub(crate) vbr: u64,
pub(crate) channels: u8,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Info {
pub(crate) title: Option<String>,
pub(crate) artist: Option<String>,
pub(crate) album: Option<String>,
pub(crate) genre: Option<String>,
pub(crate) cover: Option<String>,
}
#[derive(Debug, Deserialize)]
pub(crate) struct Origin {
pub(crate) source: Option<String>,
pub(crate) abr: Option<u64>,
pub(crate) channels: Option<u8>,
pub(crate) encoding: Option<String>,
pub(crate) url: Option<String>,
}
impl From<DcaMetadata> for Metadata {
fn from(mut d: DcaMetadata) -> Self {
let (title, artist) = d
.info
.take()
.map(|mut m| (m.title.take(), m.artist.take()))
.unwrap_or_else(|| (None, None));
let channels = Some(d.opus.channels);
let sample_rate = Some(d.opus.sample_rate);
Self {
title,
artist,
channels,
sample_rate,
..Default::default()
}
}
}