ruby-alsa 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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.3"
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
 
@@ -0,0 +1,12 @@
1
+ module ALSA
2
+ def self.logger
3
+ unless @logger
4
+ @logger = Logger.new(STDERR)
5
+ @logger.level = Logger::WARN
6
+ end
7
+
8
+ @logger
9
+ end
10
+
11
+ def self.logger=(logger); @logger = logger; end
12
+ end
@@ -0,0 +1,12 @@
1
+ module ALSA
2
+ module Native
3
+ extend FFI::Library
4
+ ffi_lib "libasound.so.2"
5
+
6
+ attach_function :strerror, :snd_strerror, [:int], :string
7
+
8
+ def self.error_code?(response)
9
+ response and response < 0
10
+ end
11
+ end
12
+ end
@@ -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