2021-05-11 18:34:06 +08:00
|
|
|
use std::{collections::HashMap, sync::Arc, time::Duration};
|
2021-05-10 06:27:18 +08:00
|
|
|
use serde::Deserialize;
|
|
|
|
use tsclientlib::{ClientId, Connection, DisconnectOptions, Identity, StreamItem};
|
|
|
|
use tsproto_packets::packets::{AudioData, CodecType, OutAudio, OutPacket};
|
|
|
|
use audiopus::coder::Encoder;
|
2021-05-11 00:28:57 +08:00
|
|
|
use futures::{lock::Mutex, prelude::*};
|
2021-05-11 18:34:06 +08:00
|
|
|
use slog::{debug, o, Drain, Logger};
|
|
|
|
use tokio::{task};
|
2021-05-10 06:27:18 +08:00
|
|
|
use tokio::task::LocalSet;
|
|
|
|
use anyhow::*;
|
|
|
|
mod ts_voice;
|
|
|
|
mod discord;
|
2021-05-11 00:28:57 +08:00
|
|
|
|
2021-05-10 06:27:18 +08:00
|
|
|
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
|
|
|
struct ConnectionId(u64);
|
|
|
|
|
|
|
|
// This trait adds the `register_songbird` and `register_songbird_with` methods
|
|
|
|
// to the client builder below, making it easy to install this voice client.
|
|
|
|
// The voice client can be retrieved in any command using `songbird::get(ctx).await`.
|
2021-05-10 20:01:35 +08:00
|
|
|
use songbird::{SerenityInit, Songbird};
|
|
|
|
use songbird::driver::{Config as DriverConfig, DecodeMode};
|
2021-05-10 06:27:18 +08:00
|
|
|
|
|
|
|
// Import the `Context` to handle commands.
|
2021-05-10 20:01:35 +08:00
|
|
|
use serenity::{client::Context, prelude::{RwLock, TypeMapKey}};
|
2021-05-10 06:27:18 +08:00
|
|
|
|
|
|
|
use serenity::{
|
|
|
|
async_trait,
|
|
|
|
client::{Client, EventHandler},
|
|
|
|
framework::{
|
|
|
|
StandardFramework,
|
|
|
|
standard::{
|
|
|
|
Args, CommandResult,
|
|
|
|
macros::{command, group},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
model::{channel::Message, gateway::Ready},
|
|
|
|
Result as SerenityResult,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug,Deserialize)]
|
|
|
|
struct Config {
|
|
|
|
discord_token: String,
|
|
|
|
teamspeak_server: String,
|
|
|
|
teamspeak_identity: String,
|
|
|
|
teamspeak_channel: i32,
|
|
|
|
/// default 0
|
|
|
|
verbose: i32,
|
|
|
|
/// default 1.0
|
|
|
|
volume: f32,
|
|
|
|
}
|
|
|
|
|
2021-05-10 20:01:35 +08:00
|
|
|
struct ListenerHolder;
|
|
|
|
|
2021-05-11 00:28:57 +08:00
|
|
|
//TODO: stop shooting myself in the knee with a mutex
|
|
|
|
type AudioBufferDiscord = Arc<Mutex<HashMap<u32,Vec<i16>>>>;
|
|
|
|
|
2021-05-10 20:01:35 +08:00
|
|
|
impl TypeMapKey for ListenerHolder {
|
2021-05-11 00:28:57 +08:00
|
|
|
type Value = AudioBufferDiscord;
|
2021-05-10 20:01:35 +08:00
|
|
|
}
|
|
|
|
|
2021-05-11 07:40:44 +08:00
|
|
|
const TICK_TIME: u64 = 15;
|
2021-05-11 04:44:15 +08:00
|
|
|
const FRAME_SIZE_MS: usize = 20;
|
|
|
|
const STEREO_20MS: usize = 48000 * 2 * FRAME_SIZE_MS / 1000;
|
2021-05-11 18:34:06 +08:00
|
|
|
/// The maximum size of an opus frame is 1275 as from RFC6716.
|
|
|
|
const MAX_OPUS_FRAME_SIZE: usize = 1275;
|
2021-05-10 06:27:18 +08:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<()> {
|
|
|
|
tracing_subscriber::fmt::init();
|
|
|
|
|
|
|
|
let config: Config = toml::from_str(&std::fs::read_to_string(".credentials.toml").unwrap()).unwrap();
|
|
|
|
let logger = {
|
|
|
|
let decorator = slog_term::TermDecorator::new().build();
|
|
|
|
let drain = slog_term::CompactFormat::new(decorator).build().fuse();
|
|
|
|
let drain = slog_envlogger::new(drain).fuse();
|
|
|
|
let drain = slog_async::Async::new(drain).build().fuse();
|
|
|
|
|
|
|
|
Logger::root(drain, o!())
|
|
|
|
};
|
|
|
|
|
|
|
|
let framework = StandardFramework::new()
|
|
|
|
.configure(|c| c
|
|
|
|
.prefix("~"))
|
|
|
|
.group(&discord::GENERAL_GROUP);
|
|
|
|
|
2021-05-10 20:01:35 +08:00
|
|
|
// Here, we need to configure Songbird to decode all incoming voice packets.
|
|
|
|
// If you want, you can do this on a per-call basis---here, we need it to
|
|
|
|
// read the audio data that other people are sending us!
|
|
|
|
let songbird = Songbird::serenity();
|
|
|
|
songbird.set_config(
|
|
|
|
DriverConfig::default()
|
|
|
|
.decode_mode(DecodeMode::Decode)
|
|
|
|
);
|
|
|
|
|
|
|
|
|
2021-05-10 06:27:18 +08:00
|
|
|
let mut client = Client::builder(&config.discord_token)
|
|
|
|
.event_handler(discord::Handler)
|
|
|
|
.framework(framework)
|
2021-05-10 20:01:35 +08:00
|
|
|
.register_songbird_with(songbird.into())
|
2021-05-10 06:27:18 +08:00
|
|
|
.await
|
|
|
|
.expect("Err creating client");
|
|
|
|
|
2021-05-11 00:28:57 +08:00
|
|
|
let map = HashMap::new();
|
|
|
|
let voice_buffer: AudioBufferDiscord = Arc::new(Mutex::new(map));
|
2021-05-10 20:01:35 +08:00
|
|
|
{
|
|
|
|
// Open the data lock in write mode, so keys can be inserted to it.
|
|
|
|
let mut data = client.data.write().await;
|
|
|
|
|
|
|
|
// The CommandCounter Value has the following type:
|
|
|
|
// Arc<RwLock<HashMap<String, u64>>>
|
|
|
|
// So, we have to insert the same type to it.
|
2021-05-11 00:28:57 +08:00
|
|
|
data.insert::<ListenerHolder>(voice_buffer.clone());
|
2021-05-10 20:01:35 +08:00
|
|
|
}
|
|
|
|
|
2021-05-10 06:27:18 +08:00
|
|
|
tokio::spawn(async move {
|
|
|
|
let _ = client.start().await.map_err(|why| println!("Client ended: {:?}", why));
|
|
|
|
});
|
|
|
|
|
|
|
|
let con_id = ConnectionId(0);
|
|
|
|
let local_set = LocalSet::new();
|
|
|
|
let audiodata = ts_voice::start(logger.clone(), &local_set)?;
|
|
|
|
|
|
|
|
let con_config = Connection::build(config.teamspeak_server)
|
|
|
|
.log_commands(config.verbose >= 1)
|
|
|
|
.log_packets(config.verbose >= 2)
|
|
|
|
.log_udp_packets(config.verbose >= 3);
|
|
|
|
|
|
|
|
// Optionally set the key of this client, otherwise a new key is generated.
|
|
|
|
let id = Identity::new_from_str(&config.teamspeak_identity).expect("Can't load identity!");
|
|
|
|
let con_config = con_config.identity(id);
|
|
|
|
|
|
|
|
// Connect
|
|
|
|
let mut con = con_config.connect()?;
|
|
|
|
|
|
|
|
let r = con
|
|
|
|
.events()
|
|
|
|
.try_filter(|e| future::ready(matches!(e, StreamItem::BookEvents(_))))
|
|
|
|
.next()
|
|
|
|
.await;
|
|
|
|
if let Some(r) = r {
|
|
|
|
r?;
|
|
|
|
}
|
|
|
|
|
2021-05-10 20:01:35 +08:00
|
|
|
// let (send, mut recv) = mpsc::channel(5);
|
|
|
|
// {
|
|
|
|
// let mut a2t = audiodata.a2ts.lock().unwrap();
|
|
|
|
// a2t.set_listener(send);
|
|
|
|
// a2t.set_volume(config.volume);
|
|
|
|
// a2t.set_playing(true);
|
|
|
|
// }
|
2021-05-11 06:17:26 +08:00
|
|
|
let encoder = audiopus::coder::Encoder::new(
|
|
|
|
audiopus::SampleRate::Hz48000,
|
|
|
|
audiopus::Channels::Stereo,
|
|
|
|
audiopus::Application::Voip)
|
|
|
|
.expect("Can't construct encoder!");
|
|
|
|
let encoder = Arc::new(Mutex::new(encoder));
|
2021-05-11 04:44:15 +08:00
|
|
|
let mut interval = tokio::time::interval(Duration::from_millis(TICK_TIME));
|
2021-05-11 00:28:57 +08:00
|
|
|
|
|
|
|
// tokio::spawn(async {
|
|
|
|
// loop {
|
|
|
|
// interval.tick().await;
|
|
|
|
// if let Err(e) = con.send_audio() {
|
|
|
|
// println!("Failed to send audio to teamspeak: {}",e);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// });
|
2021-05-11 04:44:15 +08:00
|
|
|
|
2021-05-10 06:27:18 +08:00
|
|
|
loop {
|
|
|
|
let t2a = audiodata.ts2a.clone();
|
|
|
|
let events = con.events().try_for_each(|e| async {
|
2021-05-11 07:40:44 +08:00
|
|
|
if let StreamItem::Audio(packet) = e {
|
|
|
|
let from = ClientId(match packet.data().data() {
|
|
|
|
AudioData::S2C { from, .. } => *from,
|
|
|
|
AudioData::S2CWhisper { from, .. } => *from,
|
|
|
|
_ => panic!("Can only handle S2C packets but got a C2S packet"),
|
|
|
|
});
|
2021-05-12 06:44:57 +08:00
|
|
|
|
|
|
|
// let mut t2a = t2a.lock().unwrap();
|
|
|
|
// if let Err(e) = t2a.play_packet((con_id, from), packet) {
|
|
|
|
// debug!(logger, "Failed to play packet"; "error" => %e);
|
|
|
|
// }
|
2021-05-11 07:40:44 +08:00
|
|
|
}
|
2021-05-10 06:27:18 +08:00
|
|
|
Ok(())
|
|
|
|
});
|
2021-05-11 07:40:44 +08:00
|
|
|
// let start = std::time::Instant::now();
|
2021-05-10 06:27:18 +08:00
|
|
|
// Wait for ctrl + c
|
|
|
|
tokio::select! {
|
2021-05-10 20:01:35 +08:00
|
|
|
// send_audio = recv.recv() => {
|
|
|
|
// if let Some(packet) = send_audio {
|
|
|
|
// con.send_audio(packet)?;
|
|
|
|
// } else {
|
|
|
|
// info!(logger, "Audio sending stream was canceled");
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// }
|
2021-05-11 00:28:57 +08:00
|
|
|
// send_audio = rx.recv() => {
|
|
|
|
// tokio::time::
|
|
|
|
// if let Some(packet) = send_audio {
|
|
|
|
// con.send_audio(packet)?;
|
|
|
|
// } else {
|
|
|
|
// info!(logger, "Audio sending stream was canceled");
|
|
|
|
// break;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
_send = interval.tick() => {
|
2021-05-11 07:40:44 +08:00
|
|
|
// let dur = start.elapsed();
|
|
|
|
// if dur.as_millis() > TICK_TIME as u128 {
|
|
|
|
// eprintln!("Tick took {}ms",dur.as_millis());
|
|
|
|
// }
|
2021-05-11 04:44:15 +08:00
|
|
|
let start = std::time::Instant::now();
|
2021-05-11 06:17:26 +08:00
|
|
|
if let Some(processed) = process_audio(&voice_buffer,&encoder).await {
|
2021-05-11 00:28:57 +08:00
|
|
|
con.send_audio(processed)?;
|
2021-05-11 04:44:15 +08:00
|
|
|
let dur = start.elapsed();
|
|
|
|
if dur >= Duration::from_millis(1) {
|
|
|
|
eprintln!("Audio pipeline took {}ms",dur.as_millis());
|
|
|
|
}
|
2021-05-10 06:27:18 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
_ = tokio::signal::ctrl_c() => { break; }
|
|
|
|
r = events => {
|
|
|
|
r?;
|
|
|
|
bail!("Disconnected");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2021-05-10 20:01:35 +08:00
|
|
|
println!("Disconnecting");
|
2021-05-10 06:27:18 +08:00
|
|
|
// Disconnect
|
|
|
|
con.disconnect(DisconnectOptions::new())?;
|
|
|
|
con.events().for_each(|_| future::ready(())).await;
|
2021-05-10 20:01:35 +08:00
|
|
|
println!("Disconnected");
|
2021-05-10 06:27:18 +08:00
|
|
|
Ok(())
|
2021-05-11 00:28:57 +08:00
|
|
|
}
|
|
|
|
|
2021-05-11 04:44:15 +08:00
|
|
|
|
2021-05-11 00:42:52 +08:00
|
|
|
|
2021-05-11 06:17:26 +08:00
|
|
|
async fn process_audio(voice_buffer: &AudioBufferDiscord, encoder: &Arc<Mutex<Encoder>>) -> Option<OutPacket> {
|
2021-05-11 00:42:52 +08:00
|
|
|
let mut buffer_map;
|
2021-05-11 00:28:57 +08:00
|
|
|
{
|
|
|
|
let mut lock = voice_buffer.lock().await;
|
|
|
|
buffer_map = std::mem::replace(&mut *lock, HashMap::new());
|
|
|
|
}
|
|
|
|
if buffer_map.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2021-05-11 00:42:52 +08:00
|
|
|
let mut encoded = [0; 1024];
|
2021-05-11 06:17:26 +08:00
|
|
|
let encoder_c = encoder.clone();
|
2021-05-11 00:28:57 +08:00
|
|
|
let res = task::spawn_blocking(move || {
|
|
|
|
let start = std::time::Instant::now();
|
2021-05-11 00:42:52 +08:00
|
|
|
let mut data: Vec<i16> = Vec::with_capacity(STEREO_20MS);
|
|
|
|
for buffer in buffer_map.values_mut() {
|
|
|
|
//buffer.truncate(STEREO_20MS);
|
2021-05-11 00:28:57 +08:00
|
|
|
for i in 0..buffer.len() {
|
|
|
|
if let Some(v) = data.get_mut(i) {
|
|
|
|
*v = *v + buffer[i];
|
|
|
|
} else {
|
2021-05-11 00:42:52 +08:00
|
|
|
data.extend(&buffer[i..]);
|
|
|
|
break;
|
2021-05-11 00:28:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-11 00:42:52 +08:00
|
|
|
|
2021-05-11 00:28:57 +08:00
|
|
|
|
2021-05-11 06:17:26 +08:00
|
|
|
let lock = encoder_c.try_lock().expect("Can't reach encoder!");
|
|
|
|
let length = match lock.encode(&data, &mut encoded) {
|
2021-05-11 00:28:57 +08:00
|
|
|
Err(e) => {eprintln!("Failed to encode voice: {}",e); return None;},
|
|
|
|
Ok(size) => size,
|
|
|
|
};
|
2021-05-11 04:44:15 +08:00
|
|
|
//println!("Data size: {}/{} enc-length: {}",data.len(),STEREO_20MS,length);
|
2021-05-11 00:28:57 +08:00
|
|
|
//println!("length size: {}",length);
|
|
|
|
let duration = start.elapsed().as_millis();
|
|
|
|
if duration > 15 {
|
|
|
|
eprintln!("Took too {}ms for processing audio!",duration);
|
|
|
|
}
|
|
|
|
|
|
|
|
Some(OutAudio::new(&AudioData::C2S { id: 0, codec: CodecType::OpusMusic, data: &encoded[..length] }))
|
|
|
|
}).await.expect("Join error for audio processing thread!");
|
|
|
|
res
|
2021-05-10 06:27:18 +08:00
|
|
|
}
|