Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
46cbdef
Add audio-normalizing, still not perfect
dafoxia May 10, 2014
f97efad
add recording ability, rewrite of audio decoding and mixing, bugfix o…
dafoxia May 18, 2014
0b46ee6
Revised audio mixing, stutter should not happen anymore, read out of …
dafoxia May 23, 2014
282a223
Add Copy Audio
dafoxia Jun 14, 2014
2b8023d
added jitterbuffer & copystream; reworked audio decoding
dafoxia Jun 14, 2014
df5e947
added 'mumble2mumble' X-over ability
dafoxia Jun 21, 2014
a5b987f
enable directcopy
Jun 28, 2014
5398c2b
send packets seqeuence numbers as recieved
Jun 29, 2014
9d37e62
some forgotten things
Jun 29, 2014
ba65a0d
fix disconnect function, connect after disconnect is possible w/o err…
dafoxia Jul 4, 2014
d57611b
add ability to set comment
dafoxia Jul 5, 2014
df740c4
add ready check flag
dafoxia Jul 12, 2014
f3cbfdc
Fix channel_id
dafoxia Jul 12, 2014
724168d
Merge branch 'refactor-models' of https://github.com/chendo/mumble-ru…
dafoxia Jul 12, 2014
2610512
saving
dafoxia Jul 12, 2014
b3df276
remove directcopy, rework de-/en- coding
dafoxia Jul 12, 2014
4c1d136
fix error in destroy handler
dafoxia Jul 12, 2014
e75c10f
preparation to downstream
dafoxia Jul 13, 2014
5f5c534
merge with newest master from perrym5
dafoxia Jul 13, 2014
d89d214
adjustmet of get_imgmsg(file)
dafoxia Jul 13, 2014
d6d68e9
stash
dafoxia Jul 14, 2014
f7ecb53
merge
dafoxia Jul 14, 2014
c25a5f6
use ringbuffer instead of queue
dafoxia Jul 18, 2014
e0c382c
improvements on audio-receive code
dafoxia Jul 18, 2014
1f7a652
avoid 'endless' memory consumption
dafoxia Jul 19, 2014
71bb04d
cosmetics
dafoxia Jul 19, 2014
f68ac56
Mumble2Mumble added basic CELT support too
Jul 27, 2014
be4a2e0
add forgotten file
Jul 27, 2014
78e6028
OPUS/CELT Codec switch for player
Jul 29, 2014
02d4e5a
+debug messages
Jul 29, 2014
8058d1d
pull from master
Jul 29, 2014
f74a6e8
complete mumble2mumble CELT/OPUS compatibility
Jul 30, 2014
8e0629f
complete mumble2mumble CELT/OPUS compatibility
Jul 30, 2014
a440d29
repair protocol and decoding
Aug 1, 2014
91c31b8
cleanup
Aug 1, 2014
2d8bae7
enabled codec-switching for audio-player
Aug 1, 2014
5b49830
add some handling if client kicked
Aug 5, 2014
614fd7c
fix message handler
Aug 9, 2014
0dd5beb
cleanup
Aug 10, 2014
a30cbb3
fix Bug (no sound) after reconnect user in mumble2mumble
Aug 13, 2014
d236c91
Masking Terminator Bit and add codec constantes
Jan 16, 2015
a90e080
removed debug messages
Jan 16, 2015
ed0d958
celt-ruby and opus-ruby is now handled properly, some performance imp…
loscoala Jan 17, 2015
12669e1
fix: missing argument
loscoala Jan 17, 2015
3489541
fix: missing argument
loscoala Jan 18, 2015
809314e
Merge pull request #2 from loscoala/master
dafoxia Jan 18, 2015
2f718ec
Correct OPUS audio decoding
Feb 1, 2015
da3e5e6
revert receive_stream_handler
Feb 1, 2015
a611f5c
update
dafoxia Oct 26, 2015
437fa41
add functionality to set the user comment
niko20010 Jul 23, 2014
822b945
Add functionality to register user
May 9, 2015
92db70b
fixed User#muted? and User#deafened? not returning correct values
nilsding Jul 16, 2015
5f8995d
some audio functionality
Nov 29, 2015
b878a68
Setting old standard value
Nov 29, 2015
a651208
update for better matching to enhanced opus-ruby
Nov 29, 2015
d98a2e2
send more info about operating-system
Dec 3, 2015
0e8f91b
Obey max bandwidth limit
Dec 3, 2015
30f93b9
fix error when message could not read from socket
Dec 5, 2015
19e041a
add set/get avatar images, get pingtime
Dec 31, 2015
4bf5a3c
now can send and recieve with portaudio.
Jan 6, 2016
27af4f7
add depends
Jan 6, 2016
7525fd1
fail safe if no portaudio installed
Jan 9, 2016
ddccb78
fix initbug
Jan 9, 2016
2b0530d
test patch for stereo transmission (works only via fifo pipe) other v…
dafoxia Jun 6, 2020
f0529e2
Update connection.rb
dafoxia Apr 9, 2022
d51245d
fix for playing WAV files. The files should have the propper format o…
dafoxia Jun 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified .gitignore
100644 → 100755
Empty file.
Empty file modified .ruby-gemset
100644 → 100755
Empty file.
Empty file modified .ruby-version
100644 → 100755
Empty file.
Empty file modified Gemfile
100644 → 100755
Empty file.
Empty file modified LICENSE.txt
100644 → 100755
Empty file.
7 changes: 6 additions & 1 deletion README.rdoc
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
mumble-ruby
http://www.github.com/perrym5/mumble-ruby
forked from: http://www.github.com/perrym5/mumble-ruby
and: https://github.com/qwertos/mumble-ruby

== ADDED Dependencies (06.01.2016):
ruby-portaudio
https://github.com/dafoxia/ruby-portaudio

== DESCRIPTION:

Expand Down
Empty file modified Rakefile
100644 → 100755
Empty file.
2 changes: 2 additions & 0 deletions lib/mumble-ruby.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'opus-ruby'
require 'celt-ruby'
require 'active_support/inflector'
require 'mumble-ruby/version'
require 'mumble-ruby/thread_tools'
Expand All @@ -13,6 +14,7 @@
require 'mumble-ruby/img_reader'
require 'mumble-ruby/cert_manager'
require 'mumble-ruby/audio_recorder'
require 'mumble-ruby/mumble2mumble.rb'
require 'hashie'

module Mumble
Expand Down
177 changes: 150 additions & 27 deletions lib/mumble-ruby/audio_player.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,31 @@
module Mumble
class AudioPlayer
include ThreadTools
COMPRESSED_SIZE = 960
COMPRESSED_SIZE = 48

def initialize(type, connection, sample_rate, bitrate)
@packet_header = (type << 5).chr
@conn = connection
@pds = PacketDataStream.new
@queue = Queue.new
@wav_format = WaveFile::Format.new :mono, :pcm_16, sample_rate

@queue = SizedQueue.new 500
@wav_format = WaveFile::Format.new :stereo, :pcm_16, sample_rate
@type = type
@bitrate = bitrate
@sample_rate = sample_rate
@framesize = COMPRESSED_SIZE * 10
create_encoder sample_rate, bitrate
end

def set_codec(type)
@type = type
@packet_header = (type << 5).chr
create_encoder @sample_rate, @bitrate
end

def destroy
kill_threads
end

def volume
@volume ||= 100
end
Expand All @@ -38,65 +51,175 @@ def play_file(file)
def stream_named_pipe(pipe)
unless playing?
@file = File.open(pipe, 'rb')
spawn_threads :produce, :consume
spawn_threads :produce
@playing = true
end
end

def stream_portaudio
begin
require 'ruby-portaudio'
PortAudio.init
unless playing?
@portaudio = PortAudio::Stream.open( :sample_rate => sample_rate, :frames => 8192, :input => { :device => PortAudio::Device.default_input, :channels => 1, :sample_format => :int16, :suggested_latency => 0.05 })
@audiobuffer = PortAudio::SampleBuffer.new( :format => :float32, :channels => 1, :frames => @framesize)
@portaudio.start
spawn_threads :portaudio
@playing = true
end
true
rescue
# no portaudio installed - no streaming possible
false
end
end

def stop
if playing?
kill_threads
@encoder.reset
@file.close unless @file.closed?
#@portaudio.stop unless @portaudio.stopped?
@playing = false
end
end

def set_bitrate bitrate
if !(@type == CODEC_ALPHA || @type == CODEC_BETA)
begin
@encoder.bitrate = bitrate
@bitrate = bitrate
rescue
end
end
end

def get_bitrate
@bitrate
end

def set_framelength miliseconds
case miliseconds
when 1..4
framelength = 2.5
when 5..14
framelength = 10
when 15..30
framelength = 20
when 31..45
framelength = 40
else
framelength = 60
end
@framesize= COMPRESSED_SIZE * framelength
begin
@encoder.set_frame_size @framesize
@audiobuffer = PortAudio::SampleBuffer.new( :format => :float32, :channels => 1, :frames => @framesize) if !@portaudio.stopped?
rescue
end
end

def get_frame_length
begin
(@encoder.frame_size / COMPRESSED_SIZE).to_i
rescue
puts $1
end
end

def get_framelength
@framesize / COMPRESSED_SIZE
end
private
def create_encoder(sample_rate, bitrate)
@encoder = Opus::Encoder.new sample_rate, sample_rate / 100, 1
@encoder.vbr_rate = 0 # CBR
@encoder.bitrate = bitrate
kill_threads
@encoder.destroy if @encoder != nil

if @type == CODEC_ALPHA || @type == CODEC_BETA
@encoder = Celt::Encoder.new sample_rate, sample_rate / 100, 1, [bitrate / 800, 127].min
@encoder.vbr_rate = bitrate
@encoder.prediction_request = 0
else
@encoder = Opus::Encoder.new sample_rate, @framesize, 2, 7200
@encoder.bitrate = bitrate
@encoder.opus_set_signal Opus::Constants::OPUS_SIGNAL_MUSIC # alternative OPUS_SIGNAL_VOICE but then constrainded vbr not work.
begin
@encoder.opus_set_vbr 1
@encoder.opus_set_vbr_constraint 1 # 1 constrainted VBR , 0 unconstrainded VBR
@encoder.opus_set_packet_loss_perc 10 # calculate with 10 percent packet loss
@encoder.opus_set_dtx 1
rescue
puts "[Warning] Some OPUS functions not aviable, use dafoxia's opus-ruby!"
puts $!
end
begin
@encoder.packet_loss_perc= 10
rescue
puts "[Warning] Packet Loss Resistance could not setted"
end
end
end

# TODO: call native functions
def change_volume(pcm_data)
pcm_data.unpack('s*').map { |s| s * (volume / 100.0) }.pack('s*')
end

def bounded_produce
frametime = (@encoder.frame_size / COMPRESSED_SIZE).to_f / 1000 #milliseconds
frame_counter = 0
timestart = Time.now.to_f
@file.each_buffer(@encoder.frame_size) do |buffer|
encode_sample buffer.samples.pack('s*')
encode_sample buffer.samples.flatten.pack('s*')
consume
frame_counter +=1
wait = timestart - Time.now.to_f + frame_counter * frametime
sleep(wait) if wait > 0
end

stop
end

def produce
encode_sample @file.read(@encoder.frame_size * 2)
encode_sample @file.read(@encoder.frame_size*4)
consume
end

def portaudio
begin
@portaudio.read(@audiobuffer)
@queue << @encoder.encode_ptr(@audiobuffer.to_ptr)
consume
rescue
sleep 0.2
end
end

def encode_sample(sample)
pcm_data = change_volume sample
@queue << @encoder.encode(pcm_data, COMPRESSED_SIZE)
if volume < 100
@queue << @encoder.encode(change_volume(sample))
else
@queue << @encoder.encode(sample)
end
end

def consume
@seq ||= 0
@seq %= 1000000 # Keep sequence number reasonable for long runs

@pds.rewind
@seq += 1
@pds.put_int @seq

frame = @queue.pop
@pds.put_int frame.size
@pds.append_block frame

size = @pds.size
@pds.rewind
data = [@packet_header, @pds.get_block(size)].flatten.join
@conn.send_udp_packet data
frame = @queue.pop
if frame != nil # prevent reading accidently from an empty queue
@seq ||= 0
@seq %= 1000000 # Keep sequence number reasonable for long runs
@pds.rewind
@seq += 1
@pds.put_int @seq
#frame = @queue.pop
@pds.put_int frame.size
@pds.append_block frame

size = @pds.size
@pds.rewind
data = [@packet_header, @pds.get_block(size)].flatten.join
@conn.send_udp_packet data
end
end
end
end
81 changes: 73 additions & 8 deletions lib/mumble-ruby/audio_recorder.rb
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,24 @@
module Mumble
class AudioRecorder
include ThreadTools
CODEC_ALPHA = 0
CODEC_BETA = 3
CODEC_OPUS = 4

def initialize(client, sample_rate)
@client = client
@wav_format = WaveFile::Format.new(:mono, :pcm_16, sample_rate)
@pds = PacketDataStream.new
@pds_lock = Mutex.new

@decoders = Hash.new do |h, k|
@opus_decoders = Hash.new do |h, k|
h[k] = Opus::Decoder.new sample_rate, sample_rate / 100, 1
end

@celt_decoders = Hash.new do |h, k|
h[k] = Celt::Decoder.new sample_rate, sample_rate / 100, 1
end

@queues = Hash.new do |h, k|
h[k] = Queue.new
end
Expand All @@ -24,6 +31,24 @@ def recording?
@recording ||= false
end

def stream_portaudio
begin
PortAudio.init
unless !recording?
@portaudio = PortAudio::Stream.open( :sample_rate => 48000, :frames => 8192, :output => { :device => PortAudio::Device.default_output, :channels => 1, :sample_format => :int16, :suggested_latency => 0.25 })
@audiobuffer = PortAudio::SampleBuffer.new(:format => :int16, :channels => 1, :frames => 8192)
@portaudio.start
@callback = @client.on_udp_tunnel { |msg| process_udp_tunnel msg }
spawn_thread :portaudio
@recording = true
end
true
rescue
# no portaudio gem installed - no streaming possible.
false
end
end

def start(file)
unless recording?
@file = WaveFile::Writer.new(file, @wav_format)
Expand All @@ -37,8 +62,8 @@ def stop
if recording?
@client.remove_callback :udp_tunnel, @callback
kill_threads
@decoders.values.each &:destroy
@decoders.clear
@opus_decoders.values.each &:destroy
@opus_decoders.clear
@queues.clear
@file.close
@recording = false
Expand All @@ -49,15 +74,37 @@ def stop
def process_udp_tunnel(message)
@pds_lock.synchronize do
@pds.rewind
@pds.append_block message.packet[1..-1]
@pds.append_block message.packet#[1..-1] # we need packet type info

@pds.rewind
packet_type = @pds.get_next
source = @pds.get_int
seq = @pds.get_int
len = @pds.get_int
opus = @pds.get_block len

@queues[source] << @decoders[source].decode(opus.join)
case ( packet_type >> 5 )
when CODEC_ALPHA
len = @pds.get_next
alpha = @pds.get_block ( len & 0x7f )
@queues[source] << @celt_decoders[source].decode(alpha.join)
while ( len 0x80 ) != 0
len = @pds.get_next
alpha = @pds.get_block ( len & 0x7f )
@queues[source] << @celt_decoders[source].decode(alpha.join)
end
when CODEC_BETA
len = @pds.get_next
beta = @pds.get_block ( len & 0x7f )
@queues[source] << @celt_decoders[source].decode(beta.join)
while ( len 0x80 ) != 0
len = @pds.get_next
beta = @pds.get_block ( len & 0x7f )
@queues[source] << @celt_decoders[source].decode(beta.join)
end
when CODEC_OPUS
len = @pds.get_int
opus = @pds.get_block ( len & 0x1FFF )
@queues[source] << @opus_decoders[source].decode(opus.join)
@opus_decoders[source].reset if ( len & 0x2000 ) == 0x2000
end
end
end

Expand All @@ -75,5 +122,23 @@ def write_audio
@file.write WaveFile::Buffer.new(samples, @wav_format)
end
end

def portaudio
pcms = @queues.values
.reject { |q| q.empty? } # Remove empty queues
.map { |q| q.pop.unpack 's*' } # Grab the top element of each queue and expand
head, *tail = pcms
if head
samples = head.zip(*tail)
.map { |pcms| pcms.reduce(:+) / pcms.size } # Average together all the columns of the matrix (merge audio streams)
.flatten # Flatten the resulting 1d matrix
@audiobuffer = PortAudio::SampleBuffer.new( :format => :int16, :channels => 1, :frames => send.size / 2 +1)
@audiobuffer.add(sample.pack 's*')
begin
@portaudio << @audiobuffer
rescue
end
end
end
end
end
Empty file modified lib/mumble-ruby/cert_manager.rb
100644 → 100755
Empty file.
Empty file modified lib/mumble-ruby/channel.rb
100644 → 100755
Empty file.
Loading