def test_encoding():
    """
    This generates some PCM, encodes it with opus, encrypts it, and then decrypts it.

    This serves as a test that the interface to opus and nacl work properly.
    """

    from discord.opus import Decoder, Encoder

    encoder = Encoder()

    # We need to generate some PCM for testing
    pcm_data = b''

    # Time that passes per PCM frame
    time_per_frame = encoder.FRAME_LENGTH / 1000
    # Frames per second
    frames_per_second = int(1 / time_per_frame)
    # Time that passes per PCM sample
    time_per_sample = time_per_frame / encoder.SAMPLES_PER_FRAME
    # Maximum magnitude within PCM data type
    magnitude = (2**15) - 1
    # Generate a 'Middle C' tone
    frequency = 261.625

    # Generate 1 second of PCM data
    for sample in range(encoder.SAMPLES_PER_FRAME * frames_per_second):
        sample_time = sample * time_per_sample

        value = magnitude * math.sin(2 * math.pi * sample_time * frequency)

        # Duplicate PCM value per channel
        pcm_data += struct.pack('h', int(value)) * encoder.CHANNELS

    # Ensure data generated is of correct form
    assert len(pcm_data) == (encoder.FRAME_SIZE * frames_per_second)

    # Encode the data
    opus_packets = []

    for index in range(0, len(pcm_data), encoder.FRAME_SIZE):
        encoded = encoder.encode(pcm_data[index:index + encoder.FRAME_SIZE],
                                 encoder.SAMPLES_PER_FRAME)
        opus_packets.append(encoded)

    # Prepare to encrypt the data
    import nacl.secret
    import nacl.utils

    # Generate a random secret key and create an encryption box
    secret_key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
    box = nacl.secret.SecretBox(secret_key)

    # Encrypt the data
    encrypted_packets = []

    for packet in opus_packets:
        encrypted_data = box.encrypt(packet)

        # Check message length
        assert len(
            encrypted_data) == len(packet) + box.NONCE_SIZE + box.MACBYTES

        encrypted_packets.append(encrypted_data)

    # Decrypt the data
    decrypted_packets = []

    for packet, original_packet in zip(encrypted_packets, opus_packets):
        decrypted_data = box.decrypt(packet)

        # Ensure data matches
        assert decrypted_data == original_packet

        decrypted_packets.append(decrypted_data)

    # Create decoder
    decoder = Decoder()
    decoder.set_volume(1.0)

    # Decode the data
    decoded_pcm = b''

    for packet in decrypted_packets:
        decoded_pcm += decoder.decode(packet)

    assert len(decoded_pcm) == len(pcm_data)
Ejemplo n.º 2
0
class Encoder:
    def __init__(self, bot):
        super().__init__()
        self.bot = bot

        self.filename = None
        self.converter = None
        self.opus_data = []

        self.encoder = OpusEncoder(48000, 2)
        self.delay = self.encoder.frame_length / 1000.0
        self.encoder.frame_length = 0
        self._connected = Event()
        self._connected.set()

        self.to_encode = asyncio.Queue()
        self.encode_task = bot.loop.create_task(self.queue_encoder())

    def __unload(self):
        try:
            self.encode_task.cancel()
        except:
            pass

    def create_ffmpeg_player(self,
                             filename,
                             *,
                             use_avconv=False,
                             pipe=False,
                             options=None,
                             before_options=None,
                             headers=None,
                             after=None):
        """
        Stolen from Rapptz/Danny, thanks!
        """
        command = 'ffmpeg' if not use_avconv else 'avconv'
        input_name = '-' if pipe else shlex.quote(filename)
        before_args = ""
        if isinstance(headers, dict):
            for key, value in headers.items():
                before_args += "{}: {}\r\n".format(key, value)
            before_args = ' -headers ' + shlex.quote(before_args)

        if isinstance(before_options, str):
            before_args += ' ' + before_options

        cmd = command + '{} -i {} -f s16le -ar {} -ac {} -loglevel warning'
        cmd = cmd.format(before_args, input_name, self.encoder.sampling_rate,
                         self.encoder.channels)

        if isinstance(options, str):
            cmd = cmd + ' ' + options

        cmd += ' pipe:1'

        stdin = None if not pipe else filename
        args = shlex.split(cmd)
        try:
            p = subprocess.Popen(args, stdin=stdin, stdout=subprocess.PIPE)
            return ProcessPlayer(p, self, after)
        except FileNotFoundError as e:
            raise ClientException('ffmpeg/avconv was not found in your PATH'
                                  ' environment variable') from e
        except subprocess.SubprocessError as e:
            raise ClientException(
                'Popen failed: {0.__name__} {1}'.format(type(e), str(e))) \
                from e

    @commands.command()
    async def encode(self, filename: AudioCacheFileConverter):
        """Encodes a song to pickled pcm data"""
        if self.converter is not None:
            return

        self.converter = \
            self.create_ffmpeg_player(filename, after=self._reset_converter)
        self.filename = filename
        self.converter.start()
        log.debug('encoding {}'.format(filename))

    @commands.command(pass_context=True)
    async def fakeplay(self, ctx, filename: EncodedCacheFile):
        audio = self.bot.get_cog('Audio')
        with open(filename, 'rb') as f:
            esong = pickle.load(f)
        if not isinstance(esong, EncodedSong):
            await self.bot.say("Bad file")
            return
        vc = audio.voice_client(ctx.message.server)
        vc.audio_player = ShittyPlayer(esong, audio, vc._connected,
                                       vc.play_audio)
        vc.audio_player.start()

    def play_audio(self, data_frame):
        # opus_frame = self.encoder.encode(data_frame,
        #                                  self.encoder.samples_per_frame)
        # self.opus_data.append(opus_frame)
        self.to_encode.put_nowait(data_frame)

    async def queue_encoder(self):
        while True:
            pcm = await self.to_encode.get()
            opus = self.encoder.encode(pcm, self.encoder.samples_per_frame)
            self.opus_data.append(opus)

    def _reset_converter(self):
        self.save_opus_data()
        self.converter = None
        self.filename = None

    def save_opus_data(self):
        enc = EncodedSong(self.filename + "-encoded", self.delay,
                          self.opus_data)
        with open(enc.filename, 'wb') as f:
            pickle.dump(enc, f)
Ejemplo n.º 3
0
class Encoder:
    def __init__(self, bot):
        super().__init__()
        self.bot = bot

        self.filename = None
        self.converter = None
        self.opus_data = []

        self.encoder = OpusEncoder(48000, 2)
        self.delay = self.encoder.frame_length / 1000.0
        self.encoder.frame_length = 0
        self._connected = Event()
        self._connected.set()

        self.to_encode = asyncio.Queue()
        self.encode_task = bot.loop.create_task(self.queue_encoder())

    def __unload(self):
        try:
            self.encode_task.cancel()
        except:
            pass

    def create_ffmpeg_player(self, filename, *, use_avconv=False, pipe=False,
                             options=None, before_options=None, headers=None,
                             after=None):
        """
        Stolen from Rapptz/Danny, thanks!
        """
        command = 'ffmpeg' if not use_avconv else 'avconv'
        input_name = '-' if pipe else shlex.quote(filename)
        before_args = ""
        if isinstance(headers, dict):
            for key, value in headers.items():
                before_args += "{}: {}\r\n".format(key, value)
            before_args = ' -headers ' + shlex.quote(before_args)

        if isinstance(before_options, str):
            before_args += ' ' + before_options

        cmd = command + '{} -i {} -f s16le -ar {} -ac {} -loglevel warning'
        cmd = cmd.format(before_args, input_name, self.encoder.sampling_rate,
                         self.encoder.channels)

        if isinstance(options, str):
            cmd = cmd + ' ' + options

        cmd += ' pipe:1'

        stdin = None if not pipe else filename
        args = shlex.split(cmd)
        try:
            p = subprocess.Popen(args, stdin=stdin, stdout=subprocess.PIPE)
            return ProcessPlayer(p, self, after)
        except FileNotFoundError as e:
            raise ClientException('ffmpeg/avconv was not found in your PATH'
                                  ' environment variable') from e
        except subprocess.SubprocessError as e:
            raise ClientException(
                'Popen failed: {0.__name__} {1}'.format(type(e), str(e))) \
                from e

    @commands.command()
    async def encode(self, filename: AudioCacheFileConverter):
        """Encodes a song to pickled pcm data"""
        if self.converter is not None:
            return

        self.converter = \
            self.create_ffmpeg_player(filename, after=self._reset_converter)
        self.filename = filename
        self.converter.start()
        log.debug('encoding {}'.format(filename))

    @commands.command(pass_context=True)
    async def fakeplay(self, ctx, filename: EncodedCacheFile):
        audio = self.bot.get_cog('Audio')
        with open(filename, 'rb') as f:
            esong = pickle.load(f)
        if not isinstance(esong, EncodedSong):
            await self.bot.say("Bad file")
            return
        vc = audio.voice_client(ctx.message.server)
        vc.audio_player = ShittyPlayer(esong, audio, vc._connected,
                                       vc.play_audio)
        vc.audio_player.start()

    def play_audio(self, data_frame):
        # opus_frame = self.encoder.encode(data_frame,
        #                                  self.encoder.samples_per_frame)
        # self.opus_data.append(opus_frame)
        self.to_encode.put_nowait(data_frame)

    async def queue_encoder(self):
        while True:
            pcm = await self.to_encode.get()
            opus = self.encoder.encode(pcm,
                                       self.encoder.samples_per_frame)
            self.opus_data.append(opus)

    def _reset_converter(self):
        self.save_opus_data()
        self.converter = None
        self.filename = None

    def save_opus_data(self):
        enc = EncodedSong(self.filename + "-encoded",
                          self.delay, self.opus_data)
        with open(enc.filename, 'wb') as f:
            pickle.dump(enc, f)