Driver: Split receive into its own feature (#141)
Adds the "receive" feature, which is disabled by default. When this is disabled, the UDP receive task is not compiled and not run, and as an optimisation the UDP receive buffer size is set to 0. All related events are also removed. This also removes the UDP Tx task, and moves packet and keepalive sends back into the mixer thread. This allows us to entirely remove channels and various allocations between the mixer and an async task created only for sending data (i.e., fewer memcopies). If "receive" is enabled, UDP sends are now non-blocking due to technical constraints -- failure to send is non-fatal, but *will* drop affected packets. Given that blocking on a UDP send indicates that the OS cannot clear send buffers fast enough, this should alleviate OS load. Closes #131.
This commit is contained in:
@@ -3,10 +3,16 @@
|
||||
//! [`EventContext`]: super::EventContext
|
||||
mod connect;
|
||||
mod disconnect;
|
||||
#[cfg(feature = "receive")]
|
||||
mod rtcp;
|
||||
#[cfg(feature = "receive")]
|
||||
mod speaking;
|
||||
#[cfg(feature = "receive")]
|
||||
mod voice;
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
||||
|
||||
pub use self::{connect::*, disconnect::*, rtcp::*, speaking::*, voice::*};
|
||||
pub use self::{connect::*, disconnect::*};
|
||||
#[cfg(feature = "receive")]
|
||||
pub use self::{rtcp::*, speaking::*, voice::*};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use super::context_data::*;
|
||||
use crate::ConnectionInfo;
|
||||
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct InternalConnect {
|
||||
@@ -15,27 +14,6 @@ pub struct InternalDisconnect {
|
||||
pub info: ConnectionInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct InternalSpeakingUpdate {
|
||||
pub ssrc: u32,
|
||||
pub speaking: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InternalVoicePacket {
|
||||
pub audio: Option<Vec<i16>>,
|
||||
pub packet: Rtp,
|
||||
pub payload_offset: usize,
|
||||
pub payload_end_pad: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InternalRtcpPacket {
|
||||
pub packet: Rtcp,
|
||||
pub payload_offset: usize,
|
||||
pub payload_end_pad: usize,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalConnect> for ConnectData<'a> {
|
||||
fn from(val: &'a InternalConnect) -> Self {
|
||||
Self {
|
||||
@@ -60,32 +38,62 @@ impl<'a> From<&'a InternalDisconnect> for DisconnectData<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalSpeakingUpdate> for SpeakingUpdateData {
|
||||
fn from(val: &'a InternalSpeakingUpdate) -> Self {
|
||||
Self {
|
||||
speaking: val.speaking,
|
||||
ssrc: val.ssrc,
|
||||
#[cfg(feature = "receive")]
|
||||
mod receive {
|
||||
use super::*;
|
||||
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct InternalSpeakingUpdate {
|
||||
pub ssrc: u32,
|
||||
pub speaking: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InternalVoicePacket {
|
||||
pub audio: Option<Vec<i16>>,
|
||||
pub packet: Rtp,
|
||||
pub payload_offset: usize,
|
||||
pub payload_end_pad: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct InternalRtcpPacket {
|
||||
pub packet: Rtcp,
|
||||
pub payload_offset: usize,
|
||||
pub payload_end_pad: usize,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalSpeakingUpdate> for SpeakingUpdateData {
|
||||
fn from(val: &'a InternalSpeakingUpdate) -> Self {
|
||||
Self {
|
||||
speaking: val.speaking,
|
||||
ssrc: val.ssrc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalVoicePacket> for VoiceData<'a> {
|
||||
fn from(val: &'a InternalVoicePacket) -> Self {
|
||||
Self {
|
||||
audio: &val.audio,
|
||||
packet: &val.packet,
|
||||
payload_offset: val.payload_offset,
|
||||
payload_end_pad: val.payload_end_pad,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalRtcpPacket> for RtcpData<'a> {
|
||||
fn from(val: &'a InternalRtcpPacket) -> Self {
|
||||
Self {
|
||||
packet: &val.packet,
|
||||
payload_offset: val.payload_offset,
|
||||
payload_end_pad: val.payload_end_pad,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalVoicePacket> for VoiceData<'a> {
|
||||
fn from(val: &'a InternalVoicePacket) -> Self {
|
||||
Self {
|
||||
audio: &val.audio,
|
||||
packet: &val.packet,
|
||||
payload_offset: val.payload_offset,
|
||||
payload_end_pad: val.payload_end_pad,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a InternalRtcpPacket> for RtcpData<'a> {
|
||||
fn from(val: &'a InternalRtcpPacket) -> Self {
|
||||
Self {
|
||||
packet: &val.packet,
|
||||
payload_offset: val.payload_offset,
|
||||
payload_end_pad: val.payload_end_pad,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(feature = "receive")]
|
||||
pub use receive::*;
|
||||
|
||||
@@ -26,24 +26,35 @@ pub enum EventContext<'a> {
|
||||
/// [`EventStore::add_event`]: EventStore::add_event
|
||||
/// [`TrackHandle::add_event`]: TrackHandle::add_event
|
||||
Track(&'a [(&'a TrackState, &'a TrackHandle)]),
|
||||
|
||||
/// Speaking state update, typically describing how another voice
|
||||
/// user is transmitting audio data. Clients must send at least one such
|
||||
/// packet to allow SSRC/UserID matching.
|
||||
SpeakingStateUpdate(Speaking),
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Speaking state transition, describing whether a given source has started/stopped
|
||||
/// transmitting. This fires in response to a silent burst, or the first packet
|
||||
/// breaking such a burst.
|
||||
SpeakingUpdate(SpeakingUpdateData),
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Opus audio packet, received from another stream.
|
||||
VoicePacket(VoiceData<'a>),
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Telemetry/statistics packet, received from another stream.
|
||||
RtcpPacket(RtcpData<'a>),
|
||||
|
||||
/// Fired whenever a client disconnects.
|
||||
ClientDisconnect(ClientDisconnect),
|
||||
|
||||
/// Fires when this driver successfully connects to a voice channel.
|
||||
DriverConnect(ConnectData<'a>),
|
||||
|
||||
/// Fires when this driver successfully reconnects after a network error.
|
||||
DriverReconnect(ConnectData<'a>),
|
||||
|
||||
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
||||
DriverDisconnect(DisconnectData<'a>),
|
||||
}
|
||||
@@ -51,8 +62,11 @@ pub enum EventContext<'a> {
|
||||
#[derive(Debug)]
|
||||
pub enum CoreContext {
|
||||
SpeakingStateUpdate(Speaking),
|
||||
#[cfg(feature = "receive")]
|
||||
SpeakingUpdate(InternalSpeakingUpdate),
|
||||
#[cfg(feature = "receive")]
|
||||
VoicePacket(InternalVoicePacket),
|
||||
#[cfg(feature = "receive")]
|
||||
RtcpPacket(InternalRtcpPacket),
|
||||
ClientDisconnect(ClientDisconnect),
|
||||
DriverConnect(InternalConnect),
|
||||
@@ -64,9 +78,12 @@ impl<'a> CoreContext {
|
||||
pub(crate) fn to_user_context(&'a self) -> EventContext<'a> {
|
||||
match self {
|
||||
Self::SpeakingStateUpdate(evt) => EventContext::SpeakingStateUpdate(*evt),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::SpeakingUpdate(evt) =>
|
||||
EventContext::SpeakingUpdate(SpeakingUpdateData::from(evt)),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::VoicePacket(evt) => EventContext::VoicePacket(VoiceData::from(evt)),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::RtcpPacket(evt) => EventContext::RtcpPacket(RtcpData::from(evt)),
|
||||
Self::ClientDisconnect(evt) => EventContext::ClientDisconnect(*evt),
|
||||
Self::DriverConnect(evt) => EventContext::DriverConnect(ConnectData::from(evt)),
|
||||
@@ -84,8 +101,11 @@ impl EventContext<'_> {
|
||||
pub fn to_core_event(&self) -> Option<CoreEvent> {
|
||||
match self {
|
||||
Self::SpeakingStateUpdate(_) => Some(CoreEvent::SpeakingStateUpdate),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::SpeakingUpdate(_) => Some(CoreEvent::SpeakingUpdate),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::VoicePacket(_) => Some(CoreEvent::VoicePacket),
|
||||
#[cfg(feature = "receive")]
|
||||
Self::RtcpPacket(_) => Some(CoreEvent::RtcpPacket),
|
||||
Self::ClientDisconnect(_) => Some(CoreEvent::ClientDisconnect),
|
||||
Self::DriverConnect(_) => Some(CoreEvent::DriverConnect),
|
||||
|
||||
@@ -6,8 +6,26 @@
|
||||
///
|
||||
/// ## Events from other users
|
||||
/// Songbird can observe when a user *speaks for the first time* ([`SpeakingStateUpdate`]),
|
||||
/// when a client leaves the session ([`ClientDisconnect`]), voice packets ([`VoicePacket`]), and
|
||||
/// telemetry data ([`RtcpPacket`]). The format of voice packets is described by [`VoiceData`].
|
||||
/// when a client leaves the session ([`ClientDisconnect`]).
|
||||
///
|
||||
/// When the `"receive"` feature is enabled, songbird can also handle voice packets
|
||||
#[cfg_attr(feature = "receive", doc = "([`VoicePacket`](Self::VoicePacket)),")]
|
||||
#[cfg_attr(not(feature = "receive"), doc = "(`VoicePacket`),")]
|
||||
/// detect speech starting/stopping
|
||||
#[cfg_attr(
|
||||
feature = "receive",
|
||||
doc = "([`SpeakingUpdate`](Self::SpeakingUpdate)),"
|
||||
)]
|
||||
#[cfg_attr(not(feature = "receive"), doc = "(`SpeakingUpdate`),")]
|
||||
/// and handle telemetry data
|
||||
#[cfg_attr(feature = "receive", doc = "([`RtcpPacket`](Self::RtcpPacket)).")]
|
||||
#[cfg_attr(not(feature = "receive"), doc = "(`RtcpPacket`).")]
|
||||
/// The format of voice packets is described by
|
||||
#[cfg_attr(
|
||||
feature = "receive",
|
||||
doc = "[`VoiceData`](super::context::data::VoiceData)."
|
||||
)]
|
||||
#[cfg_attr(not(feature = "receive"), doc = "`VoiceData`.")]
|
||||
///
|
||||
/// To detect when a user connects, you must correlate gateway (e.g., `VoiceStateUpdate`) events
|
||||
/// from the main part of your bot.
|
||||
@@ -15,15 +33,12 @@
|
||||
/// To obtain a user's SSRC, you must use [`SpeakingStateUpdate`] events.
|
||||
///
|
||||
/// [`EventData`]: super::EventData
|
||||
/// [`VoiceData`]: super::context::data::VoiceData
|
||||
/// [`SpeakingStateUpdate`]: Self::SpeakingStateUpdate
|
||||
/// [`ClientDisconnect`]: Self::ClientDisconnect
|
||||
/// [`VoicePacket`]: Self::VoicePacket
|
||||
/// [`RtcpPacket`]: Self::RtcpPacket
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum CoreEvent {
|
||||
/// Speaking state update, typically describing how another voice
|
||||
/// Speaking state update from the WS gateway, typically describing how another voice
|
||||
/// user is transmitting audio data. Clients must send at least one such
|
||||
/// packet to allow SSRC/UserID matching.
|
||||
///
|
||||
@@ -32,24 +47,34 @@ pub enum CoreEvent {
|
||||
/// Note: this will fire when a user starts speaking for the first time,
|
||||
/// or changes their capabilities.
|
||||
SpeakingStateUpdate,
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Fires when a source starts speaking, or stops speaking
|
||||
/// (*i.e.*, 5 consecutive silent frames).
|
||||
SpeakingUpdate,
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Fires on receipt of a voice packet from another stream in the voice call.
|
||||
///
|
||||
/// As RTP packets do not map to Discord's notion of users, SSRCs must be mapped
|
||||
/// back using the user IDs seen through client connection, disconnection,
|
||||
/// or speaking state update.
|
||||
VoicePacket,
|
||||
|
||||
#[cfg(feature = "receive")]
|
||||
/// Fires on receipt of an RTCP packet, containing various call stats
|
||||
/// such as latency reports.
|
||||
RtcpPacket,
|
||||
|
||||
/// Fires whenever a user disconnects from the same stream as the bot.
|
||||
ClientDisconnect,
|
||||
|
||||
/// Fires when this driver successfully connects to a voice channel.
|
||||
DriverConnect,
|
||||
|
||||
/// Fires when this driver successfully reconnects after a network error.
|
||||
DriverReconnect,
|
||||
|
||||
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
||||
DriverDisconnect,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user