diff options
Diffstat (limited to 'discord/opus.py')
| -rw-r--r-- | discord/opus.py | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/discord/opus.py b/discord/opus.py new file mode 100644 index 00000000..a49d9341 --- /dev/null +++ b/discord/opus.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- + +""" +The MIT License (MIT) + +Copyright (c) 2015 Rapptz + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" + +import ctypes +import ctypes.util +import array +from .errors import DiscordException +import logging + +log = logging.getLogger(__name__) +c_int_ptr = ctypes.POINTER(ctypes.c_int) +c_int16_ptr = ctypes.POINTER(ctypes.c_int16) +c_float_ptr = ctypes.POINTER(ctypes.c_float) + +class EncoderStruct(ctypes.Structure): + pass + +EncoderStructPtr = ctypes.POINTER(EncoderStruct) + +# A list of exported functions. +# The first argument is obviously the name. +# The second one are the types of arguments it takes. +# The third is the result type. +exported_functions = [ + ('opus_strerror', [ctypes.c_int], ctypes.c_char_p), + ('opus_encoder_get_size', [ctypes.c_int], ctypes.c_int), + ('opus_encoder_create', [ctypes.c_int, ctypes.c_int, ctypes.c_int, c_int_ptr], EncoderStructPtr), + ('opus_encode', [EncoderStructPtr, c_int16_ptr, ctypes.c_int, ctypes.c_char_p, ctypes.c_int32], ctypes.c_int32), + ('opus_encoder_destroy', [EncoderStructPtr], None) +] + +def libopus_loader(name): + # create the library... + lib = ctypes.cdll.LoadLibrary(name) + + # register the functions... + for item in exported_functions: + try: + func = getattr(lib, item[0]) + except Exception as e: + raise e + + try: + func.argtypes = item[1] + func.restype = item[2] + except KeyError: + pass + + return lib + +try: + _lib = libopus_loader(ctypes.util.find_library('opus')) +except: + _lib = None + +def load_opus(name): + """Loads the libopus shared library for use with voice. + + If this function is not called then the library uses the function + ``ctypes.util.find_library`` and then loads that one if available. + + Not loading a library leads to voice not working. + + This function propagates the exceptions thrown. + + .. warning:: + + The bitness of the library must match the bitness of your python + interpreter. If the library is 64-bit then your python interpreter + must be 64-bit as well. Usually if there's a mismatch in bitness then + the load will throw an exception. + + .. note:: + + On Windows, the .dll extension is not necessary. However, on Linux + the full extension is required to load the library, e.g. ``libopus.so.1``. + + :param name: The filename of the shared library. + """ + global _lib + _lib = libopus_loader(name) + +class OpusError(DiscordException): + """An exception that is thrown for libopus related errors.""" + def __init__(self, code): + self.code = code + msg = _lib.opus_strerror(self.code).decode('utf-8') + log.info('"{}" has happened'.format(msg)) + super(DiscordException, self).__init__(msg) + + +# Some constants... +OK = 0 +APPLICATION_AUDIO = 2049 +APPLICATION_VOIP = 2048 +APPLICATION_LOWDELAY = 2051 + +class Encoder: + def __init__(self, sampling, channels, application=APPLICATION_AUDIO): + self.sampling_rate = sampling + self.channels = channels + self.application = application + + self.frame_length = 20 + self.sample_size = 2 * self.channels # (bit_rate / 8) but bit_rate == 16 + self.samples_per_frame = int(self.sampling_rate / 1000 * self.frame_length) + self.frame_size = self.samples_per_frame * self.sample_size + + self._state = self._create_state() + + def __del__(self): + if hasattr(self, '_state'): + _lib.opus_encoder_destroy(self._state) + self._state = None + + def _create_state(self): + ret = ctypes.c_int() + result = _lib.opus_encoder_create(self.sampling_rate, self.channels, self.application, ctypes.byref(ret)) + + if ret.value != 0: + log.info('error has happened in state creation') + raise OpusError(ret.value) + + return result + + def encode(self, pcm, frame_size): + max_data_bytes = len(pcm) + pcm = ctypes.cast(pcm, c_int16_ptr) + data = (ctypes.c_char * max_data_bytes)() + + ret = _lib.opus_encode(self._state, pcm, frame_size, data, max_data_bytes) + if ret < 0: + log.info('error has happened in encode') + raise OpusError(ret) + + return array.array('b', data[:ret]).tobytes() |