class Discordrb::Voice::Encoder

This class conveniently abstracts opus and ffmpeg/avconv, for easy implementation of voice sending. It’s not very useful for most users, but I guess it can be useful sometimes.

Constants

OPUS_SILENCE

One frame of complete silence Opus encoded

Attributes

filter_volume[RW]

@see VoiceBot#filter_volume= @return [Integer] the volume used as a filter to ffmpeg/avconv.

use_avconv[RW]

Whether or not avconv should be used instead of ffmpeg. If possible, it is recommended to use ffmpeg instead, as it is better supported. @return [true, false] whether avconv should be used instead of ffmpeg.

Public Class Methods

new() click to toggle source

Create a new encoder

# File lib/discordrb/voice/encoder.rb, line 26
def initialize
  sample_rate = 48_000
  frame_size = 960
  channels = 2
  @filter_volume = 1

  raise LoadError, 'Opus unavailable - voice not supported! Please install opus for voice support to work.' unless OPUS_AVAILABLE

  @opus = Opus::Encoder.new(sample_rate, frame_size, channels)
end

Public Instance Methods

adjust_volume(buf, mult) click to toggle source

Adjusts the volume of a given buffer of s16le PCM data. @param buf [String] An unencoded PCM (s16le) buffer. @param mult [Float] The volume multiplier, 1 for same volume. @return [String] The buffer with adjusted volume, s16le again

# File lib/discordrb/voice/encoder.rb, line 57
def adjust_volume(buf, mult)
  # We don't need to adjust anything if the buf is nil so just return in that case
  return unless buf

  # buf is s16le so use 's<' for signed, 16 bit, LE
  result = buf.unpack('s<*').map do |sample|
    sample *= mult

    # clamp to s16 range
    [32_767, [-32_768, sample].max].min
  end

  # After modification, make it s16le again
  result.pack('s<*')
end
bitrate=(value) click to toggle source

Set the opus encoding bitrate @param value [Integer] The new bitrate to use, in bits per second (so 64000 if you want 64 kbps)

# File lib/discordrb/voice/encoder.rb, line 39
def bitrate=(value)
  @opus.bitrate = value
end
encode(buffer) click to toggle source

Encodes the given buffer using opus. @param buffer [String] An unencoded PCM (s16le) buffer. @return [String] A buffer encoded using opus.

# File lib/discordrb/voice/encoder.rb, line 46
def encode(buffer)
  @opus.encode(buffer, 1920)
end
encode_file(file, options = '') click to toggle source

Encodes a given file (or rather, decodes it) using ffmpeg. This accepts pretty much any format, even videos with an audio track. For a list of supported formats, see ffmpeg.org/general.html#Audio-Codecs. It even accepts URLs, though encoding them is pretty slow - I recommend to make a stream of it and then use {#encode_io} instead. @param file [String] The path or URL to encode. @param options [String] ffmpeg options to pass after the -i flag @return [IO] the audio, encoded as s16le PCM

# File lib/discordrb/voice/encoder.rb, line 79
def encode_file(file, options = '')
  command = ffmpeg_command(input: file, options: options)
  IO.popen(command)
end
encode_io(io, options = '') click to toggle source

Encodes an arbitrary IO audio stream using ffmpeg. Accepts pretty much any media format, even videos with audio tracks. For a list of supported audio formats, see ffmpeg.org/general.html#Audio-Codecs. @param io [IO] The stream to encode. @param options [String] ffmpeg options to pass after the -i flag @return [IO] the audio, encoded as s16le PCM

# File lib/discordrb/voice/encoder.rb, line 89
def encode_io(io, options = '')
  command = ffmpeg_command(options: options)
  IO.popen(command, in: io)
end

Private Instance Methods

ffmpeg_command(input: '-', options: null) click to toggle source
# File lib/discordrb/voice/encoder.rb, line 96
def ffmpeg_command(input: '-', options: null)
  [
    @use_avconv ? 'avconv' : 'ffmpeg',
    '-loglevel', '0',
    '-i', input,
    '-f', 's16le',
    '-ar', '48000',
    '-ac', '2',
    'pipe:1',
    filter_volume_argument
  ].concat(options.split).reject { |segment| segment.nil? || segment == '' }
end
filter_volume_argument() click to toggle source
# File lib/discordrb/voice/encoder.rb, line 109
def filter_volume_argument
  return '' if @filter_volume == 1

  @use_avconv ? "-vol #{(@filter_volume * 256).ceil}" : "-af volume=#{@filter_volume}"
end