Skip to content

Commit

Permalink
fix audio (#148)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaihoE authored Mar 15, 2024
1 parent e8c3da6 commit 03b3691
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ rayon = "1.7.0"
protobuf-support = "3.3.0"
proc-macro2 = "1.0.69"
rand = "0.8.5"
opus = "0.3.0"

[dependencies.csgoproto]
path = "../csgoproto"
Expand Down
2 changes: 2 additions & 0 deletions src/parser/src/first_pass/read_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,8 @@ pub enum DemoParserError {
IllegalPathOp,
VectorResizeFailure,
ImpossibleCmd,
UnkVoiceFormat,
MalformedVoicePacket,
}

impl std::error::Error for DemoParserError {}
Expand Down
1 change: 1 addition & 0 deletions src/parser/src/second_pass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod parser;
pub mod parser_settings;
pub mod path_ops;
pub mod variants;
pub mod voice_data;
117 changes: 117 additions & 0 deletions src/parser/src/second_pass/voice_data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
use crate::first_pass::read_bits::DemoParserError;
use ahash::AHashMap;
use csgoproto::netmessages::CSVCMsg_VoiceData;
use opus::Decoder;
use rayon::iter::IntoParallelRefIterator;
use rayon::iter::ParallelIterator;
use std::{i16, time::Instant};

#[derive(Debug)]
struct VoicePacket {
pub length: u16,
pub voice_type: u8,
}
const FRAME_SIZE: usize = 480;
const AVG_BYTES_PER_PACKET: usize = 1600;

pub fn parse_voice_chunk_old_format(bytes: &[u8], decoder: &mut Decoder) -> Result<Vec<i16>, DemoParserError> {
// based on https://github.com/DandrewsDev/CS2VoiceData
let mut decoded_bytes = vec![];
let packet = VoicePacket {
// sample_rate: u16::from_le_bytes(bytes[9..11].try_into().unwrap()),
voice_type: u8::from_le_bytes([bytes[11]].try_into().unwrap()),
length: u16::from_le_bytes(bytes[12..14].try_into().unwrap()),
};
if packet.voice_type == 6 {
let mut ptr = 14;

// read chunks until chunk_len == 65535
while ptr < packet.length as usize {
let mut output = vec![0; FRAME_SIZE];
let chunk_len = u16::from_le_bytes(bytes[ptr..ptr + 2].try_into().unwrap());
if chunk_len == 65535 {
break;
}
ptr += 4;
match decoder.decode(&bytes, &mut decoded_bytes, false) {
Ok(n) => decoded_bytes.extend(&output[..n]),
Err(_) => return Err(DemoParserError::MalformedVoicePacket),
};
ptr += chunk_len as usize;
}
}
Ok(decoded_bytes)
}
pub fn parse_voice_chunk_new_format(bytes: &[u8], decoder: &mut Decoder) -> Result<Vec<i16>, DemoParserError> {
let mut decoded_bytes = vec![0; 1024];
let n = match decoder.decode(&bytes, &mut decoded_bytes, false) {
Ok(n) => n,
Err(_) => return Err(DemoParserError::MalformedVoicePacket),
};
decoded_bytes.truncate(n);
Ok(decoded_bytes)
}

fn generate_wav_header(num_channels: u16, sample_rate: u32, bits_per_sample: u16, data_size: u32) -> Vec<u8> {
let mut header = Vec::new();
// RIFF header
header.extend_from_slice(b"RIFF");
header.extend_from_slice(&((36 + data_size) as u32).to_le_bytes());
header.extend_from_slice(b"WAVE");
// Format chunk
header.extend_from_slice(b"fmt ");
header.extend_from_slice(&(16 as u32).to_le_bytes());
header.extend_from_slice(&(1 as u16).to_le_bytes());
header.extend_from_slice(&num_channels.to_le_bytes());
header.extend_from_slice(&sample_rate.to_le_bytes());
header.extend_from_slice(&(sample_rate * num_channels as u32 * bits_per_sample as u32 / 8).to_le_bytes());
header.extend_from_slice(&(num_channels * bits_per_sample / 8).to_le_bytes());
header.extend_from_slice(&bits_per_sample.to_le_bytes());
// Data chunk
header.extend_from_slice(b"data");
header.extend_from_slice(&data_size.to_le_bytes());
header
}
pub fn convert_voice_data_to_wav(voice_data: Vec<CSVCMsg_VoiceData>) -> Result<Vec<(String, Vec<u8>)>, DemoParserError> {
// Group by steamid
let mut hm: AHashMap<u64, Vec<&CSVCMsg_VoiceData>> = AHashMap::default();
for data in &voice_data {
hm.entry(data.xuid()).or_insert(vec![]).push(data);
}
// Collect voice data per steamid
let voice_data_wav: Vec<Result<(String, Vec<u8>), DemoParserError>> = hm
.iter()
.map(|(xuid, data)| {
let mut decoder = Decoder::new(48000, opus::Channels::Mono).unwrap();
let mut data_this_player = Vec::with_capacity(AVG_BYTES_PER_PACKET * data.len());
// add header
data_this_player.extend(generate_wav_header(1, 48000, 16, data_this_player.len() as u32));
// add voice data
for chunk in data {
match chunk.audio.format() {
csgoproto::netmessages::VoiceDataFormat_t::VOICEDATA_FORMAT_OPUS => data_this_player.extend(
parse_voice_chunk_new_format(chunk.audio.voice_data(), &mut decoder)?
.iter()
.flat_map(|x| x.to_le_bytes()),
),
csgoproto::netmessages::VoiceDataFormat_t::VOICEDATA_FORMAT_STEAM => data_this_player.extend(
parse_voice_chunk_new_format(chunk.audio.voice_data(), &mut decoder)?
.iter()
.flat_map(|x| x.to_le_bytes()),
),
csgoproto::netmessages::VoiceDataFormat_t::VOICEDATA_FORMAT_ENGINE => {
return Err(DemoParserError::UnkVoiceFormat);
}
};
}
Ok((xuid.to_string(), data_this_player))
})
.collect();

// Check for errors
let mut ok_packets = vec![];
for data in voice_data_wav {
ok_packets.push(data?);
}
Ok(ok_packets)
}

0 comments on commit 03b3691

Please sign in to comment.