ruby-alsa 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +7 -0
- data/COPYING +674 -0
- data/COPYRIGHT +14 -0
- data/Manifest.txt +24 -0
- data/Rakefile +6 -0
- data/lib/alsa.rb +3 -2
- data/lib/alsa/logger.rb +12 -0
- data/lib/alsa/native.rb +12 -0
- data/lib/alsa/pcm/capture.rb +46 -0
- data/lib/alsa/pcm/hw_parameters.rb +124 -0
- data/lib/alsa/pcm/native.rb +52 -0
- data/lib/alsa/pcm/playback.rb +48 -0
- data/lib/alsa/pcm/stream.rb +76 -0
- data/log/test.log +2948 -0
- data/ruby-alsa.gemspec +39 -0
- data/script/console +2 -2
- data/script/play +20 -0
- data/script/record +13 -0
- data/spec.html +244 -0
- data/spec/alsa/logger_spec.rb +9 -0
- data/spec/alsa/native_spec.rb +30 -0
- data/spec/alsa/pcm/capture_spec.rb +102 -0
- data/spec/alsa/pcm/native_spec.rb +9 -0
- data/spec/alsa/pcm/playback_spec.rb +31 -0
- data/spec/alsa/pcm/stream_spec.rb +17 -0
- data/spec/alsa_spec.rb +23 -0
- data/spec/spec_helper.rb +2 -0
- data/spec/support/logger.rb +6 -0
- data/tasks/buildbot.rake +1 -0
- metadata +27 -3
data/COPYRIGHT
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Copyright © 2010 Alban Peignier, Florent Peyraud
|
2
|
+
|
3
|
+
This program is free software: you can redistribute it and/or modify
|
4
|
+
it under the terms of the GNU General public License as published by
|
5
|
+
the Free Software Foundation, either version 3 of the License, or
|
6
|
+
(at your option) any later version.
|
7
|
+
|
8
|
+
This program is distributed in the hope that it will be useful,
|
9
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
10
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
11
|
+
GNU General public License for more details.
|
12
|
+
|
13
|
+
You should have received a copy of the GNU General public License
|
14
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
data/Manifest.txt
CHANGED
@@ -1,13 +1,37 @@
|
|
1
|
+
.autotest
|
2
|
+
COPYING
|
3
|
+
COPYRIGHT
|
1
4
|
Gemfile
|
2
5
|
History.txt
|
3
6
|
Manifest.txt
|
4
7
|
README.rdoc
|
5
8
|
Rakefile
|
6
9
|
lib/alsa.rb
|
10
|
+
lib/alsa/logger.rb
|
11
|
+
lib/alsa/native.rb
|
12
|
+
lib/alsa/pcm/capture.rb
|
13
|
+
lib/alsa/pcm/hw_parameters.rb
|
14
|
+
lib/alsa/pcm/native.rb
|
15
|
+
lib/alsa/pcm/playback.rb
|
16
|
+
lib/alsa/pcm/stream.rb
|
17
|
+
log/test.log
|
18
|
+
ruby-alsa.gemspec
|
7
19
|
script/console
|
8
20
|
script/destroy
|
9
21
|
script/generate
|
22
|
+
script/play
|
23
|
+
script/record
|
24
|
+
spec.html
|
25
|
+
spec/alsa/logger_spec.rb
|
26
|
+
spec/alsa/native_spec.rb
|
27
|
+
spec/alsa/pcm/capture_spec.rb
|
28
|
+
spec/alsa/pcm/native_spec.rb
|
29
|
+
spec/alsa/pcm/playback_spec.rb
|
30
|
+
spec/alsa/pcm/stream_spec.rb
|
10
31
|
spec/alsa/pcm_spec.rb
|
32
|
+
spec/alsa_spec.rb
|
11
33
|
spec/spec.opts
|
12
34
|
spec/spec_helper.rb
|
35
|
+
spec/support/logger.rb
|
36
|
+
tasks/buildbot.rake
|
13
37
|
tasks/rspec.rake
|
data/Rakefile
CHANGED
@@ -4,6 +4,12 @@ require 'hoe'
|
|
4
4
|
require 'fileutils'
|
5
5
|
require 'lib/alsa.rb'
|
6
6
|
|
7
|
+
rubyforge_user_config = File.expand_path("~/.rubyforge/user-config.yml")
|
8
|
+
unless File.exists?(rubyforge_user_config)
|
9
|
+
mkdir_p File.dirname(rubyforge_user_config)
|
10
|
+
touch rubyforge_user_config
|
11
|
+
end
|
12
|
+
|
7
13
|
Hoe.plugin :newgem
|
8
14
|
# Hoe.plugin :website
|
9
15
|
# Hoe.plugin :cucumberfeatures
|
data/lib/alsa.rb
CHANGED
@@ -2,11 +2,10 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
2
2
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
4
|
require 'ffi'
|
5
|
-
require 'ffi_ext'
|
6
5
|
|
7
6
|
module ALSA
|
8
7
|
|
9
|
-
VERSION = "0.0.
|
8
|
+
VERSION = "0.0.4"
|
10
9
|
|
11
10
|
end
|
12
11
|
|
@@ -15,7 +14,9 @@ require 'alsa/logger'
|
|
15
14
|
require 'alsa/native'
|
16
15
|
require 'alsa/pcm/native'
|
17
16
|
require 'alsa/pcm/hw_parameters'
|
17
|
+
require 'alsa/pcm/stream'
|
18
18
|
require 'alsa/pcm/capture'
|
19
|
+
require 'alsa/pcm/playback'
|
19
20
|
|
20
21
|
module ALSA
|
21
22
|
|
data/lib/alsa/logger.rb
ADDED
data/lib/alsa/native.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module ALSA::PCM
|
2
|
+
class Capture < Stream
|
3
|
+
|
4
|
+
def native_constant
|
5
|
+
ALSA::PCM::Native::STREAM_CAPTURE
|
6
|
+
end
|
7
|
+
|
8
|
+
def read
|
9
|
+
check_handle!
|
10
|
+
|
11
|
+
ALSA.logger.debug { "start read with #{hw_params.sample_rate}, #{hw_params.channels} channels"}
|
12
|
+
|
13
|
+
# use an 500ms buffer
|
14
|
+
frame_count = hw_params.sample_rate / 2
|
15
|
+
ALSA.logger.debug { "allocate #{hw_params.buffer_size_for(frame_count)} bytes for #{frame_count} frames" }
|
16
|
+
FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
|
17
|
+
begin
|
18
|
+
read_buffer buffer, frame_count
|
19
|
+
end while yield buffer, frame_count
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_buffer(buffer, frame_count)
|
24
|
+
check_handle!
|
25
|
+
|
26
|
+
read_count = ALSA::try_to "read from audio interface" do
|
27
|
+
response = ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
|
28
|
+
if ALSA::Native::error_code?(response)
|
29
|
+
ALSA.logger.warn { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
|
30
|
+
ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
|
31
|
+
else
|
32
|
+
response
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
missing_frame_count = frame_count - read_count
|
37
|
+
if missing_frame_count > 0
|
38
|
+
ALSA.logger.debug { "re-read missing frame count: #{missing_frame_count}"}
|
39
|
+
read_buffer_size = hw_params.buffer_size_for(read_count)
|
40
|
+
# buffer[read_buffer_size] doesn't return a MemoryPointer
|
41
|
+
read_buffer(buffer + read_buffer_size, missing_frame_count)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module ALSA::PCM
|
2
|
+
class HwParameters
|
3
|
+
|
4
|
+
attr_accessor :handle, :device
|
5
|
+
|
6
|
+
def initialize(device = nil)
|
7
|
+
hw_params_pointer = FFI::MemoryPointer.new :pointer
|
8
|
+
|
9
|
+
ALSA::PCM::Native::hw_params_malloc hw_params_pointer
|
10
|
+
self.handle = hw_params_pointer.read_pointer
|
11
|
+
|
12
|
+
self.device = device if device
|
13
|
+
end
|
14
|
+
|
15
|
+
def update_attributes(attributes)
|
16
|
+
attributes.each_pair { |name, value| send("#{name}=", value) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def default_for_device
|
20
|
+
ALSA::try_to "initialize hardware parameter structure" do
|
21
|
+
ALSA::PCM::Native::hw_params_any device.handle, self.handle
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def current_for_device
|
27
|
+
ALSA::try_to "retrieve current hardware parameters" do
|
28
|
+
ALSA::PCM::Native::hw_params_current device.handle, self.handle
|
29
|
+
end
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def access=(access)
|
34
|
+
ALSA::try_to "set access type" do
|
35
|
+
ALSA::PCM::Native::hw_params_set_access self.device.handle, self.handle, ALSA::PCM::Native::Access.const_get(access.to_s.upcase)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def channels=(channels)
|
40
|
+
ALSA::try_to "set channel count : #{channels}" do
|
41
|
+
ALSA::PCM::Native::hw_params_set_channels self.device.handle, self.handle, channels
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def sample_rate=(sample_rate)
|
46
|
+
ALSA::try_to "set sample rate" do
|
47
|
+
rate = FFI::MemoryPointer.new(:int)
|
48
|
+
rate.write_int(sample_rate)
|
49
|
+
|
50
|
+
dir = FFI::MemoryPointer.new(:int)
|
51
|
+
dir.write_int(0)
|
52
|
+
|
53
|
+
error_code = ALSA::PCM::Native::hw_params_set_rate_near self.device.handle, self.handle, rate, dir
|
54
|
+
|
55
|
+
rate.free
|
56
|
+
dir.free
|
57
|
+
|
58
|
+
error_code
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def sample_rate
|
63
|
+
rate = nil
|
64
|
+
ALSA::try_to "get sample rate" do
|
65
|
+
rate_pointer = FFI::MemoryPointer.new(:int)
|
66
|
+
dir_pointer = FFI::MemoryPointer.new(:int)
|
67
|
+
dir_pointer.write_int(0)
|
68
|
+
|
69
|
+
error_code = ALSA::PCM::Native::hw_params_get_rate self.handle, rate_pointer, dir_pointer
|
70
|
+
|
71
|
+
rate = rate_pointer.read_int
|
72
|
+
|
73
|
+
rate_pointer.free
|
74
|
+
dir_pointer.free
|
75
|
+
|
76
|
+
error_code
|
77
|
+
end
|
78
|
+
rate
|
79
|
+
end
|
80
|
+
|
81
|
+
def sample_format=(sample_format)
|
82
|
+
ALSA::try_to "set sample format" do
|
83
|
+
ALSA::PCM::Native::hw_params_set_format self.device.handle, self.handle, ALSA::PCM::Native::Format.const_get(sample_format.to_s.upcase)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def sample_format
|
88
|
+
format = nil
|
89
|
+
FFI::MemoryPointer.new(:int) do |format_pointer|
|
90
|
+
ALSA::try_to "get sample format" do
|
91
|
+
ALSA::PCM::Native::hw_params_get_format self.handle, format_pointer
|
92
|
+
end
|
93
|
+
format = format_pointer.read_int
|
94
|
+
end
|
95
|
+
format
|
96
|
+
end
|
97
|
+
|
98
|
+
def channels
|
99
|
+
channels = nil
|
100
|
+
FFI::MemoryPointer.new(:int) do |channels_pointer|
|
101
|
+
ALSA::try_to "get channels" do
|
102
|
+
ALSA::PCM::Native::hw_params_get_channels self.handle, channels_pointer
|
103
|
+
end
|
104
|
+
channels = channels_pointer.read_int
|
105
|
+
end
|
106
|
+
channels
|
107
|
+
end
|
108
|
+
|
109
|
+
def buffer_size_for(frame_count)
|
110
|
+
ALSA::PCM::Native::format_size(self.sample_format, frame_count) * self.channels
|
111
|
+
end
|
112
|
+
|
113
|
+
def frame_count_for(byte_count)
|
114
|
+
ALSA::PCM::Native::bytes_to_frames(self.device.handle, byte_count)
|
115
|
+
end
|
116
|
+
|
117
|
+
def free
|
118
|
+
ALSA::try_to "unallocate hw_params" do
|
119
|
+
ALSA::PCM::Native::hw_params_free self.handle
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module ALSA::PCM
|
2
|
+
|
3
|
+
module Native
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib "libasound.so.2"
|
6
|
+
|
7
|
+
STREAM_PLAYBACK = 0
|
8
|
+
STREAM_CAPTURE = 1
|
9
|
+
|
10
|
+
BLOCK = 0
|
11
|
+
attach_function :open, :snd_pcm_open, [:pointer, :string, :int, :int], :int
|
12
|
+
attach_function :prepare, :snd_pcm_prepare, [ :pointer ], :int
|
13
|
+
attach_function :close, :snd_pcm_close, [:pointer], :int
|
14
|
+
|
15
|
+
attach_function :readi, :snd_pcm_readi, [ :pointer, :pointer, :ulong ], :long
|
16
|
+
attach_function :writei, :snd_pcm_writei, [ :pointer, :pointer, :ulong ], :long
|
17
|
+
|
18
|
+
attach_function :pcm_recover, :snd_pcm_recover, [ :pointer, :int, :int ], :int
|
19
|
+
|
20
|
+
attach_function :hw_params_malloc, :snd_pcm_hw_params_malloc, [:pointer], :int
|
21
|
+
attach_function :hw_params_free, :snd_pcm_hw_params_free, [:pointer], :int
|
22
|
+
|
23
|
+
attach_function :hw_params, :snd_pcm_hw_params, [ :pointer, :pointer ], :int
|
24
|
+
attach_function :hw_params_any, :snd_pcm_hw_params_any, [:pointer, :pointer], :int
|
25
|
+
attach_function :hw_params_current, :snd_pcm_hw_params_current, [ :pointer, :pointer ], :int
|
26
|
+
|
27
|
+
module Access
|
28
|
+
MMAP_INTERLEAVED = 0
|
29
|
+
MMAP_NONINTERLEAVED = 1
|
30
|
+
MMAP_COMPLEX = 2
|
31
|
+
RW_INTERLEAVED = 3
|
32
|
+
RW_NONINTERLEAVED = 4
|
33
|
+
end
|
34
|
+
|
35
|
+
attach_function :hw_params_set_access, :snd_pcm_hw_params_set_access, [ :pointer, :pointer, :int ], :int
|
36
|
+
|
37
|
+
module Format
|
38
|
+
S16_LE = 2
|
39
|
+
end
|
40
|
+
|
41
|
+
attach_function :hw_params_set_format, :snd_pcm_hw_params_set_format, [ :pointer, :pointer, :int ], :int
|
42
|
+
attach_function :hw_params_get_format, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
|
43
|
+
attach_function :hw_params_get_rate, :snd_pcm_hw_params_get_rate, [ :pointer, :pointer, :pointer ], :int
|
44
|
+
attach_function :hw_params_set_rate_near, :snd_pcm_hw_params_set_rate_near, [ :pointer, :pointer, :pointer, :pointer ], :int
|
45
|
+
attach_function :hw_params_set_channels, :snd_pcm_hw_params_set_channels, [ :pointer, :pointer, :uint ], :int
|
46
|
+
attach_function :hw_params_get_channels, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
|
47
|
+
attach_function :hw_params_set_periods, :snd_pcm_hw_params_set_periods, [ :pointer, :pointer, :uint, :int ], :int
|
48
|
+
|
49
|
+
attach_function :format_size, :snd_pcm_format_size, [ :int, :uint ], :int
|
50
|
+
attach_function :bytes_to_frames, :snd_pcm_bytes_to_frames, [ :pointer, :int ], :int
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module ALSA::PCM
|
2
|
+
class Playback < Stream
|
3
|
+
|
4
|
+
def native_constant
|
5
|
+
ALSA::PCM::Native::STREAM_PLAYBACK
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_buffer(buffer, frame_count)
|
9
|
+
check_handle!
|
10
|
+
|
11
|
+
write_count = ALSA::try_to "write in audio interface" do
|
12
|
+
response = ALSA::PCM::Native::writei(self.handle, buffer, frame_count)
|
13
|
+
if ALSA::Native::error_code?(response)
|
14
|
+
ALSA.logger.warn { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
|
15
|
+
ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
|
16
|
+
else
|
17
|
+
response
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
missing_frame_count = frame_count - write_count
|
22
|
+
if missing_frame_count > 0
|
23
|
+
ALSA.logger.debug { "missing wroted frame count: #{missing_frame_count}"}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def write
|
28
|
+
check_handle!
|
29
|
+
|
30
|
+
frame_count = hw_params.sample_rate / 2
|
31
|
+
FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
|
32
|
+
while audio_content = yield(buffer.size)
|
33
|
+
buffer.write_string audio_content
|
34
|
+
|
35
|
+
read_frame_count =
|
36
|
+
if audio_content.size == buffer.size
|
37
|
+
frame_count
|
38
|
+
else
|
39
|
+
hw_params.frame_count_for(audio_content.size)
|
40
|
+
end
|
41
|
+
|
42
|
+
write_buffer buffer, read_frame_count
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module ALSA::PCM
|
2
|
+
class Stream
|
3
|
+
|
4
|
+
attr_accessor :handle
|
5
|
+
|
6
|
+
def self.open(device = "default", hardware_attributes = {}, &block)
|
7
|
+
new.open(device, hardware_attributes, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def open(device = "default", hardware_attributes = {}, &block)
|
11
|
+
handle_pointer = FFI::MemoryPointer.new :pointer
|
12
|
+
ALSA::try_to "open audio device #{device}" do
|
13
|
+
ALSA::PCM::Native::open handle_pointer, device, native_constant, ALSA::PCM::Native::BLOCK
|
14
|
+
end
|
15
|
+
self.handle = handle_pointer.read_pointer
|
16
|
+
|
17
|
+
self.hardware_parameters = hardware_attributes
|
18
|
+
|
19
|
+
if block_given?
|
20
|
+
begin
|
21
|
+
yield self
|
22
|
+
ensure
|
23
|
+
self.close
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def change_hardware_parameters
|
29
|
+
hw_params = ALSA::PCM::HwParameters.new(self).default_for_device
|
30
|
+
|
31
|
+
begin
|
32
|
+
yield hw_params
|
33
|
+
|
34
|
+
ALSA::try_to "set hw parameters" do
|
35
|
+
ALSA::PCM::Native::hw_params self.handle, hw_params.handle
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
hw_params.free
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def hardware_parameters
|
43
|
+
ALSA::PCM::HwParameters.new(self).current_for_device
|
44
|
+
end
|
45
|
+
alias_method :hw_params, :hardware_parameters
|
46
|
+
|
47
|
+
def hardware_parameters=(attributes= {})
|
48
|
+
attributes = {
|
49
|
+
:access => :rw_interleaved,
|
50
|
+
:channels => 2,
|
51
|
+
:sample_format => :s16_le,
|
52
|
+
:sample_rate => 44100
|
53
|
+
}.update(attributes)
|
54
|
+
|
55
|
+
change_hardware_parameters do |hw_params|
|
56
|
+
hw_params.update_attributes(attributes)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def opened?
|
61
|
+
not self.handle.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def check_handle!
|
65
|
+
raise "Stream isn't opened" unless opened?
|
66
|
+
end
|
67
|
+
|
68
|
+
def close
|
69
|
+
ALSA::try_to "close audio device" do
|
70
|
+
ALSA::PCM::Native::close self.handle
|
71
|
+
self.handle = nil
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
end
|