Browse Source

Add encoder params to VoiceClient.play

pull/10109/head
Imayhaveborkedit 2 years ago
committed by dolfies
parent
commit
cccd6107b6
  1. 62
      discord/opus.py
  2. 55
      discord/voice_client.py

62
discord/opus.py

@ -39,10 +39,17 @@ from .errors import DiscordException
if TYPE_CHECKING: if TYPE_CHECKING:
T = TypeVar('T') T = TypeVar('T')
APPLICATION_CTL = Literal['audio', 'voip', 'lowdelay']
BAND_CTL = Literal['narrow', 'medium', 'wide', 'superwide', 'full'] BAND_CTL = Literal['narrow', 'medium', 'wide', 'superwide', 'full']
SIGNAL_CTL = Literal['auto', 'voice', 'music'] SIGNAL_CTL = Literal['auto', 'voice', 'music']
class ApplicationCtl(TypedDict):
audio: int
voip: int
lowdelay: int
class BandCtl(TypedDict): class BandCtl(TypedDict):
narrow: int narrow: int
medium: int medium: int
@ -90,9 +97,10 @@ OK = 0
BAD_ARG = -1 BAD_ARG = -1
# Encoder CTLs # Encoder CTLs
APPLICATION_AUDIO = 2049 APPLICATION_AUDIO = 'audio'
APPLICATION_VOIP = 2048 APPLICATION_VOIP = 'voip'
APPLICATION_LOWDELAY = 2051 APPLICATION_LOWDELAY = 'lowdelay'
# These remain as strings for backwards compat
CTL_SET_BITRATE = 4002 CTL_SET_BITRATE = 4002
CTL_SET_BANDWIDTH = 4008 CTL_SET_BANDWIDTH = 4008
@ -105,6 +113,12 @@ CTL_SET_GAIN = 4034
CTL_LAST_PACKET_DURATION = 4039 CTL_LAST_PACKET_DURATION = 4039
# fmt: on # fmt: on
application_ctl: ApplicationCtl = {
'audio': 2049,
'voip': 2048,
'lowdelay': 2051,
}
band_ctl: BandCtl = { band_ctl: BandCtl = {
'narrow': 1101, 'narrow': 1101,
'medium': 1102, 'medium': 1102,
@ -319,16 +333,38 @@ class _OpusStruct:
class Encoder(_OpusStruct): class Encoder(_OpusStruct):
def __init__(self, application: int = APPLICATION_AUDIO): def __init__(
_OpusStruct.get_opus_version() self,
*,
self.application: int = application application: APPLICATION_CTL = 'audio',
bitrate: int = 128,
fec: bool = True,
expected_packet_loss: float = 0.15,
bandwidth: BAND_CTL = 'full',
signal_type: SIGNAL_CTL = 'auto',
):
if application not in application_ctl:
raise ValueError(f'{application} is not a valid application setting. Try one of: {"".join(application_ctl)}')
if not 16 <= bitrate <= 512:
raise ValueError(f'bitrate must be between 16 and 512, not {bitrate}')
if not 0 < expected_packet_loss <= 1.0:
raise ValueError(
f'expected_packet_loss must be a positive number less than or equal to 1, not {expected_packet_loss}'
)
_OpusStruct.get_opus_version() # lazy loads the opus library
self.application: int = application_ctl[application]
self._state: EncoderStruct = self._create_state() self._state: EncoderStruct = self._create_state()
self.set_bitrate(128)
self.set_fec(True) self.set_bitrate(bitrate)
self.set_expected_packet_loss_percent(0.15) self.set_fec(fec)
self.set_bandwidth('full') if fec:
self.set_signal_type('auto') self.set_expected_packet_loss_percent(expected_packet_loss)
self.set_bandwidth(bandwidth)
self.set_signal_type(signal_type)
def __del__(self) -> None: def __del__(self) -> None:
if hasattr(self, '_state'): if hasattr(self, '_state'):
@ -355,7 +391,7 @@ class Encoder(_OpusStruct):
def set_signal_type(self, req: SIGNAL_CTL) -> None: def set_signal_type(self, req: SIGNAL_CTL) -> None:
if req not in signal_ctl: if req not in signal_ctl:
raise KeyError(f'{req!r} is not a valid bandwidth setting. Try one of: {",".join(signal_ctl)}') raise KeyError(f'{req!r} is not a valid signal type setting. Try one of: {",".join(signal_ctl)}')
k = signal_ctl[req] k = signal_ctl[req]
_lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k) _lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k)

55
discord/voice_client.py

@ -58,7 +58,7 @@ if TYPE_CHECKING:
from .guild import Guild from .guild import Guild
from .state import ConnectionState from .state import ConnectionState
from .user import ClientUser from .user import ClientUser
from .opus import Encoder from .opus import Encoder, APPLICATION_CTL, BAND_CTL, SIGNAL_CTL
from . import abc from . import abc
from .types.gateway import VoiceStateUpdateEvent as VoiceStateUpdatePayload from .types.gateway import VoiceStateUpdateEvent as VoiceStateUpdatePayload
@ -594,7 +594,18 @@ class VoiceClient(VoiceProtocol):
return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4] return header + box.encrypt(bytes(data), bytes(nonce)).ciphertext + nonce[:4]
def play(self, source: AudioSource, *, after: Optional[Callable[[Optional[Exception]], Any]] = None) -> None: def play(
self,
source: AudioSource,
*,
after: Optional[Callable[[Optional[Exception]], Any]] = None,
application: APPLICATION_CTL = 'audio',
bitrate: int = 128,
fec: bool = True,
expected_packet_loss: float = 0.15,
bandwidth: BAND_CTL = 'full',
signal_type: SIGNAL_CTL = 'auto',
) -> None:
"""Plays an :class:`AudioSource`. """Plays an :class:`AudioSource`.
The finalizer, ``after`` is called after the source has been exhausted The finalizer, ``after`` is called after the source has been exhausted
@ -604,9 +615,15 @@ class VoiceClient(VoiceProtocol):
caught and the audio player is then stopped. If no after callback is caught and the audio player is then stopped. If no after callback is
passed, any caught exception will be logged using the library logger. passed, any caught exception will be logged using the library logger.
Extra parameters may be passed to the internal opus encoder if a PCM based
source is used. Otherwise, they are ignored.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
Instead of writing to ``sys.stderr``, the library's logger is used. Instead of writing to ``sys.stderr``, the library's logger is used.
.. versionchanged:: 2.4
Added encoder parameters as keyword arguments.
Parameters Parameters
----------- -----------
source: :class:`AudioSource` source: :class:`AudioSource`
@ -615,6 +632,27 @@ class VoiceClient(VoiceProtocol):
The finalizer that is called after the stream is exhausted. The finalizer that is called after the stream is exhausted.
This function must have a single parameter, ``error``, that This function must have a single parameter, ``error``, that
denotes an optional exception that was raised during playing. denotes an optional exception that was raised during playing.
application: :class:`str`
Configures the encoder's intended application. Can be one of:
``'audio'``, ``'voip'``, ``'lowdelay'``.
Defaults to ``'audio'``.
bitrate: :class:`int`
Configures the bitrate in the encoder. Can be between ``16`` and ``512``.
Defaults to ``128``.
fec: :class:`bool`
Configures the encoder's use of inband forward error correction.
Defaults to ``True``.
expected_packet_loss: :class:`float`
Configures the encoder's expected packet loss percentage. Requires FEC.
Defaults to ``0.15``.
bandwidth: :class:`str`
Configures the encoder's bandpass. Can be one of:
``'narrow'``, ``'medium'``, ``'wide'``, ``'superwide'``, ``'full'``.
Defaults to ``'full'``.
signal_type: :class:`str`
Configures the type of signal being encoded. Can be one of:
``'auto'``, ``'voice'``, ``'music'``.
Defaults to ``'auto'``.
Raises Raises
------- -------
@ -624,6 +662,8 @@ class VoiceClient(VoiceProtocol):
Source is not a :class:`AudioSource` or after is not a callable. Source is not a :class:`AudioSource` or after is not a callable.
OpusNotLoaded OpusNotLoaded
Source is not opus encoded and opus is not loaded. Source is not opus encoded and opus is not loaded.
ValueError
An improper value was passed as an encoder parameter.
""" """
if not self.is_connected(): if not self.is_connected():
@ -635,8 +675,15 @@ class VoiceClient(VoiceProtocol):
if not isinstance(source, AudioSource): if not isinstance(source, AudioSource):
raise TypeError(f'source must be an AudioSource not {source.__class__.__name__}') raise TypeError(f'source must be an AudioSource not {source.__class__.__name__}')
if not self.encoder and not source.is_opus(): if not source.is_opus():
self.encoder = opus.Encoder() self.encoder = opus.Encoder(
application=application,
bitrate=bitrate,
fec=fec,
expected_packet_loss=expected_packet_loss,
bandwidth=bandwidth,
signal_type=signal_type,
)
self._player = AudioPlayer(source, self, after=after) self._player = AudioPlayer(source, self, after=after)
self._player.start() self._player.start()

Loading…
Cancel
Save