-
Notifications
You must be signed in to change notification settings - Fork 48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add multiple voice to wav option #249
Comments
Yeah this has been suggested before and could be great. It will just require some work. |
To give you an idea, got it to work for python with help from AI, I use it like this: parser = DemoParser(file)
voice_data = parser.parse_voice()
for steamid, segments in voice_data.items():
for segment in segments:
starting_tick = segment['start_tick']
audio_bytes = segment["audio"]
# etc with these modifications: let voice_segments = convert_voice_data_to_wav(output.voice_data).map_err(|e| PyValueError::new_err(format!("{e}")))?;
let mut out_hm = AHashMap::new();
for (steamid, segments) in voice_segments {
let py_segments = PyList::new_bound(py, segments.into_iter().map(|(start_tick, bytes)| {
let py_dict = PyDict::new_bound(py);
py_dict.set_item("start_tick", start_tick).unwrap();
py_dict.set_item("audio", PyBytes::new_bound(py, &bytes)).unwrap();
py_dict
}));
out_hm.insert(steamid, py_segments); instead of let out = convert_voice_data_to_wav(output.voice_data).unwrap();
let mut out_hm = AHashMap::default();
for (steamid, bytes) in out {
let py_bytes = PyBytes::new_bound(py, &bytes);
out_hm.insert(steamid, py_bytes); File: pub fn convert_voice_data_to_wav(voice_data: Vec<(i32, CSVCMsg_VoiceData)>) -> Result<Vec<(String, Vec<(i32, Vec<u8>)>)>, DemoParserError> {
// Group voice data by SteamID
let mut hm: AHashMap<u64, Vec<(i32, &CSVCMsg_VoiceData)>> = AHashMap::default();
for (tick, data) in &voice_data {
hm.entry(data.xuid()).or_insert_with(Vec::new).push((*tick, data));
}
// Process each player's data in parallel
let voice_data_wav: Vec<_> = hm.par_iter().map(|(xuid, data)| {
let mut decoder = Decoder::new(48000, opus::Channels::Mono).unwrap();
let mut segments = Vec::new();
let mut current_segment = Vec::new();
let mut segment_start_tick = None;
let mut last_tick = None;
const GAP_THRESHOLD: i32 = 32; // ~500ms at 64 ticks/sec
// Sort by tick to ensure chronological order (if not guaranteed by parser)
let mut sorted_data = data.to_vec();
sorted_data.sort_by_key(|&(tick, _)| tick);
for (tick, chunk) in sorted_data {
// Check for gap to start new segment
if let Some(lt) = last_tick {
if tick - lt > GAP_THRESHOLD {
if !current_segment.is_empty() {
segments.push((segment_start_tick.unwrap(), current_segment));
current_segment = Vec::new();
}
segment_start_tick = Some(tick);
}
} else {
segment_start_tick = Some(tick); // First chunk in segment
}
// Decode voice chunk
let pcm = match chunk.audio.format() {
csgoproto::netmessages::VoiceDataFormat_t::VOICEDATA_FORMAT_OPUS => {
parse_voice_chunk_new_format(chunk.audio.voice_data(), &mut decoder)?
}
csgoproto::netmessages::VoiceDataFormat_t::VOICEDATA_FORMAT_STEAM => {
parse_voice_chunk_old_format(chunk.audio.voice_data(), &mut decoder)?
}
_ => return Err(DemoParserError::UnkVoiceFormat),
};
// Convert PCM samples to bytes
let pcm_bytes = pcm.iter().flat_map(|x| x.to_le_bytes()).collect::<Vec<u8>>();
current_segment.extend(pcm_bytes);
last_tick = Some(tick);
}
// Push the last segment if it exists
if !current_segment.is_empty() {
segments.push((segment_start_tick.unwrap(), current_segment));
}
// Convert segments to WAV files
let wav_files: Vec<(i32, Vec<u8>)> = segments.into_iter().map(|(start_tick, bytes)| {
let mut out = Vec::new();
out.extend(generate_wav_header(1, 48000, 16, bytes.len() as u32)); // Mono, 48kHz, 16-bit
out.extend(bytes);
(start_tick, out)
}).collect();
Ok((xuid.to_string(), wav_files))
}).collect();
// Collect results, propagating errors
let mut ok_packets = Vec::new(); instead of 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
.par_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 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);
}
};
}
let mut out = vec![];
out.extend(generate_wav_header(1, 48000, 16, data_this_player.len() as u32));
out.extend(data_this_player);
Ok((xuid.to_string(), out))
})
.collect();
// Check for errors
let mut ok_packets = vec![]; File: pub voice_data: Vec<(i32, CSVCMsg_VoiceData)>, instead of pub voice_data: Vec<CSVCMsg_VoiceData>, File: pub voice_data: Vec<(i32, CSVCMsg_VoiceData)>, instead of pub voice_data: Vec<CSVCMsg_VoiceData>, and self.voice_data.push((self.tick, m)); instead of self.voice_data.push(m); |
Hi just wondering if you could add maybe better option for voice to wav.
IF u dont understand well let me know, english not my first language
thx much!
The text was updated successfully, but these errors were encountered: