Add encoder params to VoiceClient.play

This commit is contained in:
Imayhaveborkedit 2023-08-23 21:04:15 -04:00 committed by GitHub
parent b276f3f5a2
commit 8b8ce55378
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 99 additions and 16 deletions

View File

@ -39,10 +39,17 @@ from .errors import DiscordException
if TYPE_CHECKING:
T = TypeVar('T')
APPLICATION_CTL = Literal['audio', 'voip', 'lowdelay']
BAND_CTL = Literal['narrow', 'medium', 'wide', 'superwide', 'full']
SIGNAL_CTL = Literal['auto', 'voice', 'music']
class ApplicationCtl(TypedDict):
audio: int
voip: int
lowdelay: int
class BandCtl(TypedDict):
narrow: int
medium: int
@ -90,9 +97,10 @@ OK = 0
BAD_ARG = -1
# Encoder CTLs
APPLICATION_AUDIO = 2049
APPLICATION_VOIP = 2048
APPLICATION_LOWDELAY = 2051
APPLICATION_AUDIO = 'audio'
APPLICATION_VOIP = 'voip'
APPLICATION_LOWDELAY = 'lowdelay'
# These remain as strings for backwards compat
CTL_SET_BITRATE = 4002
CTL_SET_BANDWIDTH = 4008
@ -105,6 +113,12 @@ CTL_SET_GAIN = 4034
CTL_LAST_PACKET_DURATION = 4039
# fmt: on
application_ctl: ApplicationCtl = {
'audio': 2049,
'voip': 2048,
'lowdelay': 2051,
}
band_ctl: BandCtl = {
'narrow': 1101,
'medium': 1102,
@ -319,16 +333,38 @@ class _OpusStruct:
class Encoder(_OpusStruct):
def __init__(self, application: int = APPLICATION_AUDIO):
_OpusStruct.get_opus_version()
def __init__(
self,
*,
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)}')
self.application: int = application
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.set_bitrate(128)
self.set_fec(True)
self.set_expected_packet_loss_percent(0.15)
self.set_bandwidth('full')
self.set_signal_type('auto')
self.set_bitrate(bitrate)
self.set_fec(fec)
if fec:
self.set_expected_packet_loss_percent(expected_packet_loss)
self.set_bandwidth(bandwidth)
self.set_signal_type(signal_type)
def __del__(self) -> None:
if hasattr(self, '_state'):
@ -355,7 +391,7 @@ class Encoder(_OpusStruct):
def set_signal_type(self, req: SIGNAL_CTL) -> None:
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]
_lib.opus_encoder_ctl(self._state, CTL_SET_SIGNAL, k)

View File

@ -58,7 +58,7 @@ if TYPE_CHECKING:
from .guild import Guild
from .state import ConnectionState
from .user import ClientUser
from .opus import Encoder
from .opus import Encoder, APPLICATION_CTL, BAND_CTL, SIGNAL_CTL
from .channel import StageChannel, VoiceChannel
from . import abc
@ -569,7 +569,18 @@ class VoiceClient(VoiceProtocol):
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`.
The finalizer, ``after`` is called after the source has been exhausted
@ -579,9 +590,15 @@ class VoiceClient(VoiceProtocol):
caught and the audio player is then stopped. If no after callback is
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
Instead of writing to ``sys.stderr``, the library's logger is used.
.. versionchanged:: 2.4
Added encoder parameters as keyword arguments.
Parameters
-----------
source: :class:`AudioSource`
@ -590,6 +607,27 @@ class VoiceClient(VoiceProtocol):
The finalizer that is called after the stream is exhausted.
This function must have a single parameter, ``error``, that
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
-------
@ -599,6 +637,8 @@ class VoiceClient(VoiceProtocol):
Source is not a :class:`AudioSource` or after is not a callable.
OpusNotLoaded
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():
@ -610,8 +650,15 @@ class VoiceClient(VoiceProtocol):
if not isinstance(source, AudioSource):
raise TypeError(f'source must be an AudioSource not {source.__class__.__name__}')
if not self.encoder and not source.is_opus():
self.encoder = opus.Encoder()
if not source.is_opus():
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.start()