use std::collections::{HashMap, VecDeque};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, RecvTimeoutError};
use std::sync::Arc;
use std::thread::JoinHandle;
use std::time::Duration;
use wasapi::{
initialize_mta, AudioClient, DeviceEnumerator, Direction, Role, SampleType, SessionState, StreamMode,
WaveFormat,
};
use windows::Win32::Foundation::CloseHandle;
use windows::Win32::System::Diagnostics::ToolHelp::{
CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, TH32CS_SNAPPROCESS,
};
use crate::pcm::{self, SAMPLE_RATE};
use crate::source::{Capture, CaptureError};
const ZOOM_EXE: &str = "zoom.exe";
struct ProcessInfo {
name: String,
parent: u32,
}
fn processes() -> HashMap<u32, ProcessInfo> {
let mut out = HashMap::new();
unsafe {
let Ok(snap) = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0) else {
return out;
};
let mut entry = PROCESSENTRY32W {
dwSize: std::mem::size_of::<PROCESSENTRY32W>() as u32,
..Default::default()
};
if Process32FirstW(snap, &mut entry).is_ok() {
loop {
let len = entry
.szExeFile
.iter()
.position(|&c| c == 0)
.unwrap_or(entry.szExeFile.len());
out.insert(
entry.th32ProcessID,
ProcessInfo {
name: String::from_utf16_lossy(&entry.szExeFile[..len]).to_lowercase(),
parent: entry.th32ParentProcessID,
},
);
if Process32NextW(snap, &mut entry).is_err() {
break;
}
}
}
let _ = CloseHandle(snap);
}
out
}
fn zoom_root(pid: u32, procs: &HashMap<u32, ProcessInfo>) -> u32 {
let mut current = pid;
for _ in 0..16 {
match procs
.get(¤t)
.and_then(|p| procs.get(&p.parent).map(|parent| (p.parent, parent)))
{
Some((parent_pid, parent)) if parent.name == ZOOM_EXE => current = parent_pid,
_ => break,
}
}
current
}
pub fn active_zoom_process() -> Option<u32> {
let _ = initialize_mta();
let procs = processes();
if !procs.values().any(|p| p.name == ZOOM_EXE) {
return None;
}
let enumerator = DeviceEnumerator::new().ok()?;
let devices = enumerator.get_device_collection(&Direction::Render).ok()?;
for device in &devices {
let Ok(device) = device else { continue };
let Ok(manager) = device.get_iaudiosessionmanager() else {
continue;
};
let Ok(sessions) = manager.get_audiosessionenumerator() else {
continue;
};
let count = sessions.get_count().unwrap_or(0);
for i in 0..count {
let Ok(session) = sessions.get_session(i) else {
continue;
};
if !matches!(session.get_state(), Ok(SessionState::Active)) {
continue;
}
let Ok(pid) = session.get_process_id() else {
continue;
};
if procs.get(&pid).is_some_and(|p| p.name == ZOOM_EXE) {
return Some(zoom_root(pid, &procs));
}
}
}
None
}
struct Worker {
rx: Receiver<Result<Vec<i16>, String>>,
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
}
impl Worker {
fn spawn(open: impl FnOnce() -> Result<AudioClient, String> + Send + 'static) -> Result<Self, String> {
let (tx, rx) = mpsc::channel();
let (ready_tx, ready_rx) = mpsc::channel();
let stop = Arc::new(AtomicBool::new(false));
let stop_flag = stop.clone();
let thread = std::thread::Builder::new()
.name("meet-capture".into())
.spawn(move || {
let _ = initialize_mta();
let started = open().and_then(|mut client| start(&mut client).map(|fmt| (client, fmt)));
let (client, format) = match started {
Ok(v) => {
let _ = ready_tx.send(Ok(()));
v
}
Err(e) => {
let _ = ready_tx.send(Err(e));
return;
}
};
if let Err(e) = pump(&client, format, &tx, &stop_flag) {
let _ = tx.send(Err(e));
}
let _ = client.stop_stream();
})
.map_err(|e| e.to_string())?;
ready_rx
.recv()
.map_err(|_| "поток захвата не запустился".to_string())??;
Ok(Self {
rx,
stop,
thread: Some(thread),
})
}
fn read(&mut self) -> Result<Vec<i16>, String> {
let mut out = Vec::new();
match self.rx.recv_timeout(Duration::from_millis(50)) {
Ok(block) => out.extend(block?),
Err(RecvTimeoutError::Timeout) => return Ok(out),
Err(RecvTimeoutError::Disconnected) => return Err("поток захвата остановился".into()),
}
while let Ok(block) = self.rx.try_recv() {
out.extend(block?);
}
Ok(out)
}
}
impl Drop for Worker {
fn drop(&mut self) {
self.stop.store(true, Ordering::Relaxed);
if let Some(t) = self.thread.take() {
let _ = t.join();
}
}
}
#[derive(Clone, Copy)]
enum Format {
Native,
Float { rate: u32, channels: usize },
}
fn start(client: &mut AudioClient) -> Result<Format, String> {
let mode = StreamMode::EventsShared {
autoconvert: true,
buffer_duration_hns: 2_000_000,
};
let native = WaveFormat::new(16, 16, &SampleType::Int, SAMPLE_RATE as usize, 1, None);
let format = match client.initialize_client(&native, &Direction::Capture, &mode) {
Ok(()) => Format::Native,
Err(first) => {
let float = WaveFormat::new(32, 32, &SampleType::Float, 48_000, 2, None);
client
.initialize_client(&float, &Direction::Capture, &mode)
.map_err(|second| format!("{first}; запасной формат: {second}"))?;
Format::Float {
rate: 48_000,
channels: 2,
}
}
};
Ok(format)
}
fn pump(
client: &AudioClient,
format: Format,
tx: &mpsc::Sender<Result<Vec<i16>, String>>,
stop: &AtomicBool,
) -> Result<(), String> {
let event = client.set_get_eventhandle().map_err(|e| e.to_string())?;
let capture = client.get_audiocaptureclient().map_err(|e| e.to_string())?;
client.start_stream().map_err(|e| e.to_string())?;
let mut raw: VecDeque<u8> = VecDeque::new();
while !stop.load(Ordering::Relaxed) {
let _ = event.wait_for_event(100);
loop {
let frames = capture
.get_next_packet_size()
.map_err(|e| e.to_string())?
.unwrap_or(0);
if frames == 0 {
break;
}
let before = raw.len();
let info = capture
.read_from_device_to_deque(&mut raw)
.map_err(|e| e.to_string())?;
if info.flags.silent {
for b in raw.iter_mut().skip(before) {
*b = 0;
}
}
}
if raw.is_empty() {
continue;
}
let bytes: Vec<u8> = raw.drain(..).collect();
let samples = match format {
Format::Native => bytes
.as_chunks::<2>()
.0
.iter()
.map(|c| i16::from_le_bytes(*c))
.collect(),
Format::Float { rate, channels } => {
let floats: Vec<f32> = bytes
.as_chunks::<4>()
.0
.iter()
.map(|c| f32::from_le_bytes(*c))
.collect();
pcm::to_native(&floats, channels, rate)
}
};
if tx.send(Ok(samples)).is_err() {
break;
}
}
Ok(())
}
pub struct ProcessLoopback(Worker);
impl ProcessLoopback {
pub fn open(pid: u32) -> Result<Self, CaptureError> {
Worker::spawn(move || {
AudioClient::new_application_loopback_client(pid, true).map_err(|e| e.to_string())
})
.map(Self)
.map_err(CaptureError::Loopback)
}
}
impl Capture for ProcessLoopback {
fn read(&mut self) -> Result<Vec<i16>, CaptureError> {
self.0.read().map_err(CaptureError::Loopback)
}
}
pub struct Microphone(Worker);
impl Microphone {
pub fn open() -> Result<Self, CaptureError> {
Worker::spawn(|| {
let enumerator = DeviceEnumerator::new().map_err(|e| e.to_string())?;
let device = enumerator
.get_default_device_for_role(&Direction::Capture, &Role::Communications)
.or_else(|_| enumerator.get_default_device(&Direction::Capture))
.map_err(|e| e.to_string())?;
device.get_iaudioclient().map_err(|e| e.to_string())
})
.map(Self)
.map_err(CaptureError::Microphone)
}
}
impl Capture for Microphone {
fn read(&mut self) -> Result<Vec<i16>, CaptureError> {
self.0.read().map_err(CaptureError::Microphone)
}
}