mirror of
https://codeberg.org/demostf/parser.git
synced 2026-06-03 18:24:05 +02:00
wip
This commit is contained in:
parent
4bd97e4afc
commit
1fed4d8826
6 changed files with 247 additions and 25 deletions
57
Cargo.lock
generated
57
Cargo.lock
generated
|
|
@ -44,6 +44,17 @@ version = "1.0.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "audiopus_sys"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62314a1546a2064e033665d658e88c620a62904be945f8147e6b16c3db9f8651"
|
||||||
|
dependencies = [
|
||||||
|
"cmake",
|
||||||
|
"log",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
|
|
@ -122,9 +133,12 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.99"
|
version = "1.2.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695"
|
checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a"
|
||||||
|
dependencies = [
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
|
@ -184,6 +198,15 @@ version = "0.7.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cmake"
|
||||||
|
version = "0.1.54"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.15.8"
|
version = "0.15.8"
|
||||||
|
|
@ -373,6 +396,12 @@ version = "0.3.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hound"
|
||||||
|
version = "3.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iai"
|
name = "iai"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -695,6 +724,16 @@ version = "11.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opus"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6526409b274a7e98e55ff59d96aafd38e6cd34d46b7dbbc32ce126dffcd75e8e"
|
||||||
|
dependencies = [
|
||||||
|
"audiopus_sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -732,6 +771,12 @@ version = "0.2.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plotters"
|
name = "plotters"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
|
|
@ -1015,6 +1060,12 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "similar"
|
name = "similar"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
@ -1148,6 +1199,7 @@ dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"enumflags2",
|
"enumflags2",
|
||||||
"fnv",
|
"fnv",
|
||||||
|
"hound",
|
||||||
"iai",
|
"iai",
|
||||||
"insta",
|
"insta",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
|
|
@ -1157,6 +1209,7 @@ dependencies = [
|
||||||
"no-panic",
|
"no-panic",
|
||||||
"num-traits 0.2.19",
|
"num-traits 0.2.19",
|
||||||
"num_enum",
|
"num_enum",
|
||||||
|
"opus",
|
||||||
"parse-display",
|
"parse-display",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
|
|
|
||||||
10
Cargo.toml
10
Cargo.toml
|
|
@ -44,6 +44,11 @@ path = "src/bin/strings.rs"
|
||||||
name = "direct_hits"
|
name = "direct_hits"
|
||||||
path = "src/bin/direct_hits.rs"
|
path = "src/bin/direct_hits.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "voice"
|
||||||
|
path = "src/bin/voice.rs"
|
||||||
|
required-features = ["audio"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bitbuffer = { version = "0.11.0", features = ["serde"] }
|
bitbuffer = { version = "0.11.0", features = ["serde"] }
|
||||||
num_enum = "0.7.2"
|
num_enum = "0.7.2"
|
||||||
|
|
@ -77,10 +82,15 @@ tempfile = { version = "3", optional = true }
|
||||||
lazy_static = { version = "1", optional = true }
|
lazy_static = { version = "1", optional = true }
|
||||||
prettyplease = { version = "0.2", optional = true }
|
prettyplease = { version = "0.2", optional = true }
|
||||||
|
|
||||||
|
# audio
|
||||||
|
opus = { version = "0.3.0", optional = true }
|
||||||
|
hound = { version = "3.5.1", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
schema = ["schemars", "bitbuffer/schemars"]
|
schema = ["schemars", "bitbuffer/schemars"]
|
||||||
trace = ["tracing", "tracing-subscriber"]
|
trace = ["tracing", "tracing-subscriber"]
|
||||||
codegen = ["better-panic", "quote", "syn", "Inflector", "proc-macro2", "tempfile", "lazy_static", "prettyplease"]
|
codegen = ["better-panic", "quote", "syn", "Inflector", "proc-macro2", "tempfile", "lazy_static", "prettyplease"]
|
||||||
|
audio = ["dep:opus", "dep:hound"]
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
|
|
|
||||||
27
flake.lock
generated
27
flake.lock
generated
|
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"crane": {
|
"crane": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733688869,
|
"lastModified": 1742394900,
|
||||||
"narHash": "sha256-KrhxxFj1CjESDrL5+u/zsVH0K+Ik9tvoac/oFPoxSB8=",
|
"narHash": "sha256-vVOAp9ahvnU+fQoKd4SEXB2JG2wbENkpqcwlkIXgUC0=",
|
||||||
"owner": "ipetkov",
|
"owner": "ipetkov",
|
||||||
"repo": "crane",
|
"repo": "crane",
|
||||||
"rev": "604637106e420ad99907cae401e13ab6b452e7d9",
|
"rev": "70947c1908108c0c551ddfd73d4f750ff2ea67cd",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
@ -44,17 +44,14 @@
|
||||||
"rust-overlay": "rust-overlay"
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1735052218,
|
"lastModified": 1745880182,
|
||||||
"narHash": "sha256-I30wh6G8fSUO4EseexxiDXcxyUhXR6C8BvEeKn6xyfE=",
|
"narHash": "sha256-QEhBdGhuVArNIVUfblAbA115qXulEJWdR465gqIKF2A=",
|
||||||
"owner": "icewind1991",
|
"path": "/home/robin/Projects/mill-scale",
|
||||||
"repo": "mill-scale",
|
"type": "path"
|
||||||
"rev": "7e45bb598ff63a8416ee3c26743b20644563bd93",
|
|
||||||
"type": "github"
|
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "icewind1991",
|
"path": "/home/robin/Projects/mill-scale",
|
||||||
"repo": "mill-scale",
|
"type": "path"
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
|
|
@ -88,11 +85,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733884434,
|
"lastModified": 1742697269,
|
||||||
"narHash": "sha256-8GXR9kC07dyOIshAyfZhG11xfvBRSZzYghnZ2weOKJU=",
|
"narHash": "sha256-Lpp0XyAtIl1oGJzNmTiTGLhTkcUjwSkEb0gOiNzYFGM=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "d0483df44ddf0fd1985f564abccbe568e020ddf2",
|
"rev": "01973c84732f9275c50c5f075dd1f54cc04b3316",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
mill-scale = {
|
mill-scale = {
|
||||||
url = "github:icewind1991/mill-scale";
|
# url = "github:icewind1991/mill-scale";
|
||||||
|
url = "path:/home/robin/Projects/mill-scale";
|
||||||
inputs.flakelight.follows = "flakelight";
|
inputs.flakelight.follows = "flakelight";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
161
src/bin/voice.rs
Normal file
161
src/bin/voice.rs
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
use std::env;
|
||||||
|
use std::fs;
|
||||||
|
use bitbuffer::{BigEndian, BitRead, BitReadStream, LittleEndian};
|
||||||
|
use hound::{SampleFormat, WavSpec, WavWriter};
|
||||||
|
use main_error::MainError;
|
||||||
|
use opus::{Channels, Decoder};
|
||||||
|
use opus::packet::parse;
|
||||||
|
use steamid_ng::SteamID;
|
||||||
|
use tf_demo_parser::demo::parser::MessageHandler;
|
||||||
|
use tf_demo_parser::MessageType;
|
||||||
|
pub use tf_demo_parser::{Demo, DemoParser, Parse, ParserState};
|
||||||
|
use tf_demo_parser::demo::data::DemoTick;
|
||||||
|
use tf_demo_parser::demo::message::Message;
|
||||||
|
use tf_demo_parser::demo::message::voice::{VoiceInitMessage};
|
||||||
|
|
||||||
|
#[cfg(feature = "jemallocator")]
|
||||||
|
#[global_allocator]
|
||||||
|
static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
||||||
|
|
||||||
|
fn main() -> Result<(), MainError> {
|
||||||
|
#[cfg(feature = "better-panic")]
|
||||||
|
better_panic::install();
|
||||||
|
|
||||||
|
#[cfg(feature = "trace")]
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let args: Vec<_> = env::args().collect();
|
||||||
|
if args.len() < 2 {
|
||||||
|
println!("1 argument required");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let path = args[1].clone();
|
||||||
|
let file = fs::read(path)?;
|
||||||
|
let demo = Demo::new(&file);
|
||||||
|
let parser = DemoParser::new_with_analyser(demo.get_stream(), Voice::default());
|
||||||
|
let (_header, samples) = parser.parse()?;
|
||||||
|
let mut sample_buf = vec![0; 16 * 1024];
|
||||||
|
let spec = WavSpec {
|
||||||
|
channels: 1,
|
||||||
|
sample_rate: 44100,
|
||||||
|
bits_per_sample: 16,
|
||||||
|
sample_format: SampleFormat::Int,
|
||||||
|
};
|
||||||
|
let mut writer = WavWriter::create("out.wav", spec).unwrap();
|
||||||
|
for sample in samples {
|
||||||
|
let packet = decode_steam_voice_sample(&sample.data);
|
||||||
|
let mut decoder = Decoder::new(packet.sample_rate as u32, Channels::Mono).unwrap();
|
||||||
|
dbg!(&packet.data[0..8]);
|
||||||
|
dbg!(parse(&packet.data));
|
||||||
|
let samples = decoder.decode(&packet.data, &mut sample_buf, false).unwrap();
|
||||||
|
dbg!(samples);
|
||||||
|
for sample in &sample_buf[0..samples] {
|
||||||
|
writer.write_sample(*sample).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct Voice {
|
||||||
|
pub samples: Vec<VoiceSample>,
|
||||||
|
pub last_init: Option<VoiceInitMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VoiceCodec {
|
||||||
|
Steam,
|
||||||
|
Speex,
|
||||||
|
Celt,
|
||||||
|
CeltHigh
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct VoiceSample {
|
||||||
|
tick: DemoTick,
|
||||||
|
codec: VoiceCodec,
|
||||||
|
sampling_rate: u16,
|
||||||
|
client: u8,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageHandler for Voice {
|
||||||
|
type Output = Vec<VoiceSample>;
|
||||||
|
|
||||||
|
fn does_handle(message_type: MessageType) -> bool {
|
||||||
|
matches!(
|
||||||
|
message_type,
|
||||||
|
MessageType::VoiceInit | MessageType::VoiceData
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_message(&mut self, message: &Message, tick: DemoTick, _parser_state: &ParserState) {
|
||||||
|
match message {
|
||||||
|
Message::VoiceInit(init) => {
|
||||||
|
self.last_init = Some(init.clone());
|
||||||
|
}
|
||||||
|
Message::VoiceData(data) => {
|
||||||
|
if let Some(init) = self.last_init.as_ref() {
|
||||||
|
let codec = match init.codec.as_str() {
|
||||||
|
"steam" => VoiceCodec::Steam,
|
||||||
|
"vaudio_speex" => VoiceCodec::Speex,
|
||||||
|
"vaudio_celt" => VoiceCodec::Celt,
|
||||||
|
"vaudio_celt_high" => VoiceCodec::CeltHigh,
|
||||||
|
_ => {
|
||||||
|
eprintln!("unknown voice codex: {}", init.codec);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.samples.push(VoiceSample {
|
||||||
|
tick,
|
||||||
|
codec,
|
||||||
|
sampling_rate: init.sampling_rate,
|
||||||
|
data: data.data.clone().read_sized::<Vec<u8>>(data.length as usize / 8).unwrap(),
|
||||||
|
client: data.client,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
eprintln!("Voice data without init");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn into_output(self, _state: &ParserState) -> Self::Output {
|
||||||
|
self.samples
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(BitRead, Debug)]
|
||||||
|
struct SteamVoiceHeader {
|
||||||
|
steam_id: u64,
|
||||||
|
ty: u8,
|
||||||
|
sample_rate: u16,
|
||||||
|
payload_type: u8,
|
||||||
|
length: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct SteamVoicePacket {
|
||||||
|
steam_id: SteamID,
|
||||||
|
sample_rate: u16,
|
||||||
|
data: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_steam_voice_sample(data: &[u8]) -> SteamVoicePacket {
|
||||||
|
let mut reader = BitReadStream::<LittleEndian>::from(data);
|
||||||
|
let header = reader.read::<SteamVoiceHeader>().unwrap();
|
||||||
|
assert_eq!(header.ty, 11);
|
||||||
|
let data: Vec<u8> = if header.payload_type == 6 {
|
||||||
|
reader.read_sized(header.length as usize).unwrap()
|
||||||
|
} else {
|
||||||
|
dbg!(header.length, data.len());
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
SteamVoicePacket {
|
||||||
|
steam_id: SteamID::from(header.steam_id),
|
||||||
|
sample_rate: header.sample_rate,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,9 +6,9 @@ use crate::{ReadResult, Stream};
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct VoiceInitMessage {
|
pub struct VoiceInitMessage {
|
||||||
codec: String,
|
pub codec: String,
|
||||||
quality: u8,
|
pub quality: u8,
|
||||||
sampling_rate: u16,
|
pub sampling_rate: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BitRead<'_, LittleEndian> for VoiceInitMessage {
|
impl BitRead<'_, LittleEndian> for VoiceInitMessage {
|
||||||
|
|
@ -67,11 +67,11 @@ fn test_voice_init_roundtrip() {
|
||||||
#[endianness = "LittleEndian"]
|
#[endianness = "LittleEndian"]
|
||||||
#[serde(bound(deserialize = "'a: 'static"))]
|
#[serde(bound(deserialize = "'a: 'static"))]
|
||||||
pub struct VoiceDataMessage<'a> {
|
pub struct VoiceDataMessage<'a> {
|
||||||
client: u8,
|
pub client: u8,
|
||||||
proximity: u8,
|
pub proximity: u8,
|
||||||
length: u16,
|
pub length: u16,
|
||||||
#[size = "length"]
|
#[size = "length"]
|
||||||
data: Stream<'a>,
|
pub data: Stream<'a>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue