-
Notifications
You must be signed in to change notification settings - Fork 42
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
121 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ pub mod parser; | |
pub mod parser_settings; | ||
pub mod path_ops; | ||
pub mod variants; | ||
pub mod voice_data; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |