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} -loglevel 0 -i \"#{file}\" #{options} -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
  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 = '')
  ret_io, writer = IO.pipe
  command = "#{ffmpeg_command} -loglevel 0 -i - #{options} -f s16le -ar 48000 -ac 2 #{filter_volume_argument} pipe:1"
  spawn(command, in: io, out: writer)
  ret_io
end

Private Instance Methods

ffmpeg_command() click to toggle source
# File lib/discordrb/voice/encoder.rb, line 98
def ffmpeg_command
  @use_avconv ? 'avconv' : 'ffmpeg'
end
filter_volume_argument() click to toggle source
# File lib/discordrb/voice/encoder.rb, line 102
def filter_volume_argument
  return '' if @filter_volume == 1

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