alsa-backup 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +4 -0
- data/History.txt +32 -0
- data/Manifest.txt +32 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +106 -0
- data/Rakefile +65 -0
- data/TODO +3 -0
- data/alsa-backup.gemspec +47 -0
- data/bin/alsa-backup +9 -0
- data/config.sample +91 -0
- data/lib/alsa.rb +299 -0
- data/lib/alsa_backup.rb +37 -0
- data/lib/alsa_backup/cli.rb +56 -0
- data/lib/alsa_backup/core_ext.rb +34 -0
- data/lib/alsa_backup/length_controller.rb +21 -0
- data/lib/alsa_backup/recorder.rb +91 -0
- data/lib/alsa_backup/writer.rb +124 -0
- data/lib/sndfile.rb +138 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/alsa/pcm_spec.rb +7 -0
- data/spec/alsa_backup/cli_spec.rb +78 -0
- data/spec/alsa_backup/core_ext_spec.rb +42 -0
- data/spec/alsa_backup/recorder_spec.rb +145 -0
- data/spec/alsa_backup/writer_spec.rb +130 -0
- data/spec/alsa_backup_spec.rb +11 -0
- data/spec/fixtures/config_test.rb +3 -0
- data/spec/sndfile/info_spec.rb +38 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +30 -0
- data/tasks/rspec.rake +35 -0
- metadata +139 -0
data/.autotest
ADDED
data/History.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
== 0.0.7
|
2
|
+
|
3
|
+
* Make configurable channels, sample rate and alsa device
|
4
|
+
|
5
|
+
== 0.0.6
|
6
|
+
|
7
|
+
* Fix file writing problem
|
8
|
+
|
9
|
+
== 0.0.5
|
10
|
+
|
11
|
+
* Fix half buffer writing in recording file
|
12
|
+
|
13
|
+
== 0.0.4
|
14
|
+
|
15
|
+
* Replace :int by :off_t in Sndfile#write_int prototype
|
16
|
+
* Add logger in Sndfile
|
17
|
+
* Use a 500ms buffer in ALSA::Capture#read
|
18
|
+
* Add daemons in gem dependencies
|
19
|
+
|
20
|
+
== 0.0.3
|
21
|
+
|
22
|
+
* Support --background to daemonize process
|
23
|
+
* Support --pidfile option
|
24
|
+
* Rename existing file
|
25
|
+
|
26
|
+
== 0.0.2 2009-05-19
|
27
|
+
|
28
|
+
* Improve capture by support partial reads and recovering errors
|
29
|
+
|
30
|
+
== 0.0.1 2009-05-14
|
31
|
+
|
32
|
+
* Initial release
|
data/Manifest.txt
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
PostInstall.txt
|
5
|
+
README.rdoc
|
6
|
+
Rakefile
|
7
|
+
TODO
|
8
|
+
alsa-backup.gemspec
|
9
|
+
bin/alsa-backup
|
10
|
+
config.sample
|
11
|
+
lib/alsa.rb
|
12
|
+
lib/alsa_backup.rb
|
13
|
+
lib/alsa_backup/cli.rb
|
14
|
+
lib/alsa_backup/core_ext.rb
|
15
|
+
lib/alsa_backup/length_controller.rb
|
16
|
+
lib/alsa_backup/recorder.rb
|
17
|
+
lib/alsa_backup/writer.rb
|
18
|
+
lib/sndfile.rb
|
19
|
+
script/console
|
20
|
+
script/destroy
|
21
|
+
script/generate
|
22
|
+
spec/alsa/pcm_spec.rb
|
23
|
+
spec/alsa_backup/cli_spec.rb
|
24
|
+
spec/alsa_backup/core_ext_spec.rb
|
25
|
+
spec/alsa_backup/recorder_spec.rb
|
26
|
+
spec/alsa_backup/writer_spec.rb
|
27
|
+
spec/alsa_backup_spec.rb
|
28
|
+
spec/fixtures/config_test.rb
|
29
|
+
spec/sndfile/info_spec.rb
|
30
|
+
spec/spec.opts
|
31
|
+
spec/spec_helper.rb
|
32
|
+
tasks/rspec.rake
|
data/PostInstall.txt
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
= AlsaBackup
|
2
|
+
|
3
|
+
* http://github.com/alban.peignier/alsa-backup
|
4
|
+
|
5
|
+
== DESCRIPTION:
|
6
|
+
|
7
|
+
ALSA client to perform continuous recording
|
8
|
+
|
9
|
+
== FEATURES/PROBLEMS:
|
10
|
+
|
11
|
+
* configurable file name strategy
|
12
|
+
|
13
|
+
== SYNOPSIS:
|
14
|
+
|
15
|
+
By using the default settings :
|
16
|
+
|
17
|
+
alsa-backup
|
18
|
+
|
19
|
+
will record in record.wav the default alsa device
|
20
|
+
|
21
|
+
By using command line arguments :
|
22
|
+
|
23
|
+
alsa-backup --directory=/tmp --file=test.wav --length=2
|
24
|
+
|
25
|
+
will record 2 seconds in /tmp/test.wav
|
26
|
+
|
27
|
+
By loading a configuration :
|
28
|
+
|
29
|
+
alsa-backup --config=/path/to/config
|
30
|
+
|
31
|
+
will load the specified configuration
|
32
|
+
|
33
|
+
== CONFIGURATION:
|
34
|
+
|
35
|
+
The configuration file is a Ruby file. This piece of code can
|
36
|
+
configurate the AlsaBacup recorder :
|
37
|
+
|
38
|
+
AlsaBackup.config do |recorder|
|
39
|
+
# configure recorder here :
|
40
|
+
recorder.file = "record.wav"
|
41
|
+
end
|
42
|
+
|
43
|
+
=== Recorder File
|
44
|
+
|
45
|
+
The recorder file can be specified as a simple string :
|
46
|
+
|
47
|
+
recorder.file = "record.wav"
|
48
|
+
|
49
|
+
The recorder file can be specified by a Proc which returns the string :
|
50
|
+
|
51
|
+
recorder.file = Proc.new {
|
52
|
+
Time.now.strftime("%Y/%m-%b/%d-%a/%Hh.wav")
|
53
|
+
}
|
54
|
+
|
55
|
+
will use the current time to create file names like these :
|
56
|
+
|
57
|
+
2009/05-May/17-Sun/19h.wav
|
58
|
+
2009/05-May/17-Sun/20h.wav
|
59
|
+
|
60
|
+
To use different files every 15 minutes :
|
61
|
+
|
62
|
+
recorder.file = Proc.new {
|
63
|
+
Time.now.floor(:min, 15).strftime("%Y/%m-%b/%d-%a/%Hh%M.wav")
|
64
|
+
}
|
65
|
+
|
66
|
+
will create files like these :
|
67
|
+
|
68
|
+
2009/05-May/17-Sun/19h00.wav
|
69
|
+
2009/05-May/17-Sun/19h15.wav
|
70
|
+
|
71
|
+
See config.sample.
|
72
|
+
|
73
|
+
== REQUIREMENTS:
|
74
|
+
|
75
|
+
* alsa library (http://www.alsa-project.org/)
|
76
|
+
* libsndfile library (http://www.mega-nerd.com/libsndfile/)
|
77
|
+
|
78
|
+
== INSTALL:
|
79
|
+
|
80
|
+
sudo apt-get install libasound2 libsndfile1
|
81
|
+
sudo gem install --source http://gems.github.com albanpeignier-alsa-backup
|
82
|
+
|
83
|
+
== LICENSE:
|
84
|
+
|
85
|
+
(The MIT License)
|
86
|
+
|
87
|
+
Copyright (c) 2009 Alban Peignier
|
88
|
+
|
89
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
90
|
+
a copy of this software and associated documentation files (the
|
91
|
+
'Software'), to deal in the Software without restriction, including
|
92
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
93
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
94
|
+
permit persons to whom the Software is furnished to do so, subject to
|
95
|
+
the following conditions:
|
96
|
+
|
97
|
+
The above copyright notice and this permission notice shall be
|
98
|
+
included in all copies or substantial portions of the Software.
|
99
|
+
|
100
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
101
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
102
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
103
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
104
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
105
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
106
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'rubygems' unless ENV['NO_RUBYGEMS']
|
2
|
+
%w[rake rake/clean fileutils newgem hoe rubigen].each { |f| require f }
|
3
|
+
require File.dirname(__FILE__) + '/lib/alsa_backup'
|
4
|
+
|
5
|
+
# Generate all the Rake tasks
|
6
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
7
|
+
$hoe = Hoe.spec('alsa-backup') do |p|
|
8
|
+
p.version = AlsaBackup::VERSION
|
9
|
+
p.readme_file = 'README.rdoc'
|
10
|
+
p.developer('Alban Peignier', 'alban@tryphon.eu')
|
11
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
12
|
+
p.rubyforge_name = p.name # TODO this is default value
|
13
|
+
p.extra_deps = [
|
14
|
+
['ffi','>= 0.3.5'], [ 'newgem', ">= #{::Newgem::VERSION}" ], [ 'daemons', '>= 1.0.10' ]
|
15
|
+
]
|
16
|
+
p.extra_dev_deps = [
|
17
|
+
['newgem', ">= #{::Newgem::VERSION}"]
|
18
|
+
]
|
19
|
+
p.url = 'http://projects.tryphon.eu/alsa-backup'
|
20
|
+
|
21
|
+
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
22
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
23
|
+
p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
|
24
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Recreate Manifest.txt to include ALL files'
|
28
|
+
task :manifest do
|
29
|
+
`rake check_manifest | patch -p0 > Manifest.txt`
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Generate a #{$hoe.name}.gemspec file"
|
33
|
+
task :gemspec do
|
34
|
+
File.open("#{$hoe.name}.gemspec", "w") do |file|
|
35
|
+
file.puts $hoe.spec.to_ruby
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
require 'newgem/tasks' # load /tasks/*.rake
|
40
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
41
|
+
|
42
|
+
task :default => :spec
|
43
|
+
|
44
|
+
namespace :gems do
|
45
|
+
task :install do
|
46
|
+
gems = %w{active_support ffi rspec daemons}
|
47
|
+
sh "sudo gem install #{gems.join(' ')}"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
require 'debian/build'
|
52
|
+
|
53
|
+
include Debian::Build
|
54
|
+
require 'debian/build/config'
|
55
|
+
|
56
|
+
namespace "package" do
|
57
|
+
Package.new(:"alsa-backup") do |t|
|
58
|
+
t.version = '0.0.8'
|
59
|
+
t.debian_increment = 1
|
60
|
+
|
61
|
+
t.source_provider = GitExportProvider.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
require 'debian/build/tasks'
|
data/TODO
ADDED
data/alsa-backup.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{alsa-backup}
|
5
|
+
s.version = "0.0.8"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Alban Peignier"]
|
9
|
+
s.date = %q{2010-01-23}
|
10
|
+
s.default_executable = %q{alsa-backup}
|
11
|
+
s.description = %q{ALSA client to perform continuous recording}
|
12
|
+
s.email = ["alban@tryphon.eu"]
|
13
|
+
s.executables = ["alsa-backup"]
|
14
|
+
s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt"]
|
15
|
+
s.files = [".autotest", "History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "TODO", "alsa-backup.gemspec", "bin/alsa-backup", "config.sample", "lib/alsa.rb", "lib/alsa_backup.rb", "lib/alsa_backup/cli.rb", "lib/alsa_backup/core_ext.rb", "lib/alsa_backup/length_controller.rb", "lib/alsa_backup/recorder.rb", "lib/alsa_backup/writer.rb", "lib/sndfile.rb", "script/console", "script/destroy", "script/generate", "spec/alsa/pcm_spec.rb", "spec/alsa_backup/cli_spec.rb", "spec/alsa_backup/core_ext_spec.rb", "spec/alsa_backup/recorder_spec.rb", "spec/alsa_backup/writer_spec.rb", "spec/alsa_backup_spec.rb", "spec/fixtures/config_test.rb", "spec/sndfile/info_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "tasks/rspec.rake"]
|
16
|
+
s.homepage = %q{http://projects.tryphon.eu/alsa-backup}
|
17
|
+
s.rdoc_options = ["--main", "README.rdoc"]
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
s.rubyforge_project = %q{alsa-backup}
|
20
|
+
s.rubygems_version = %q{1.3.5}
|
21
|
+
s.summary = %q{ALSA client to perform continuous recording}
|
22
|
+
|
23
|
+
if s.respond_to? :specification_version then
|
24
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
25
|
+
s.specification_version = 3
|
26
|
+
|
27
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
28
|
+
s.add_runtime_dependency(%q<ffi>, [">= 0.3.5"])
|
29
|
+
s.add_runtime_dependency(%q<newgem>, [">= 1.5.2"])
|
30
|
+
s.add_runtime_dependency(%q<daemons>, [">= 1.0.10"])
|
31
|
+
s.add_development_dependency(%q<newgem>, [">= 1.5.2"])
|
32
|
+
s.add_development_dependency(%q<hoe>, [">= 2.5.0"])
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
35
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
36
|
+
s.add_dependency(%q<daemons>, [">= 1.0.10"])
|
37
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
38
|
+
s.add_dependency(%q<hoe>, [">= 2.5.0"])
|
39
|
+
end
|
40
|
+
else
|
41
|
+
s.add_dependency(%q<ffi>, [">= 0.3.5"])
|
42
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
43
|
+
s.add_dependency(%q<daemons>, [">= 1.0.10"])
|
44
|
+
s.add_dependency(%q<newgem>, [">= 1.5.2"])
|
45
|
+
s.add_dependency(%q<hoe>, [">= 2.5.0"])
|
46
|
+
end
|
47
|
+
end
|
data/bin/alsa-backup
ADDED
data/config.sample
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#
|
2
|
+
# Config sample for Alsa.Backup
|
3
|
+
#
|
4
|
+
# Usage:
|
5
|
+
# alsa-backup --config /path/to/this/file
|
6
|
+
#
|
7
|
+
|
8
|
+
#
|
9
|
+
# To use syslog
|
10
|
+
#
|
11
|
+
#require 'syslog_logger'
|
12
|
+
#AlsaBackup.logger = SyslogLogger.new('alsa-backup').tap do |logger|
|
13
|
+
# logger.level = Logger::INFO
|
14
|
+
#end
|
15
|
+
|
16
|
+
#
|
17
|
+
# To debug
|
18
|
+
#
|
19
|
+
#Sndfile::logger = ALSA::logger = AlsaBackup.logger = Logger.new(STDOUT)
|
20
|
+
|
21
|
+
AlsaBackup.logger.info("load config #{__FILE__}")
|
22
|
+
|
23
|
+
AlsaBackup.config do |recorder|
|
24
|
+
|
25
|
+
#
|
26
|
+
# To specify the alsa device to be used
|
27
|
+
#
|
28
|
+
#recorder.device = "plughw:0"
|
29
|
+
|
30
|
+
#
|
31
|
+
# To specify the channel count to be used
|
32
|
+
#
|
33
|
+
#recorder.channels = 1
|
34
|
+
#recorder.channels = 4
|
35
|
+
|
36
|
+
#
|
37
|
+
# To specify the sample_rate to be used
|
38
|
+
#
|
39
|
+
#recorder.sample_rate = 48000
|
40
|
+
|
41
|
+
#
|
42
|
+
# To use files named like this :
|
43
|
+
# - 2009/05-May/17-Sun/19h.wav
|
44
|
+
# - 2009/05-May/17-Sun/20h.wav
|
45
|
+
#
|
46
|
+
#recorder.file = Proc.new {
|
47
|
+
# Time.now.strftime("%Y/%m-%b/%d-%a/%Hh.wav")
|
48
|
+
#}
|
49
|
+
|
50
|
+
#
|
51
|
+
# To use files named like this :
|
52
|
+
# - 2009/05-May/17-Sun/19h00.wav
|
53
|
+
# - 2009/05-May/17-Sun/19h15.wav
|
54
|
+
#
|
55
|
+
#recorder.file = Proc.new {
|
56
|
+
# Time.now.floor(:min, 15).strftime("%Y/%m-%b/%d-%a/%Hh%M.wav")
|
57
|
+
#}
|
58
|
+
|
59
|
+
#
|
60
|
+
# Define where the files are created
|
61
|
+
#
|
62
|
+
#recorder.directory = "/var/lib/alsa-backup"
|
63
|
+
|
64
|
+
#
|
65
|
+
# Customize error handler during record
|
66
|
+
#
|
67
|
+
|
68
|
+
# To sleep 30 seconds on error
|
69
|
+
#recorder.error_handler = Proc.new do |exception|
|
70
|
+
# 30
|
71
|
+
#end
|
72
|
+
|
73
|
+
# To fail on any error
|
74
|
+
#recorder.error_handler = Proc.new do |exception|
|
75
|
+
# false
|
76
|
+
#end
|
77
|
+
|
78
|
+
# To sleep the default time
|
79
|
+
#recorder.error_handler = Proc.new do |exception|
|
80
|
+
# true
|
81
|
+
#end
|
82
|
+
|
83
|
+
# To process created files
|
84
|
+
# WARN: don't forget the & in the command line or fork
|
85
|
+
#recorder.on_close do |file|
|
86
|
+
# AlsaBackup.logger.info "encode in ogg : #{file}"
|
87
|
+
# system "oggenc -Q -q7 #{file} &"
|
88
|
+
#end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
data/lib/alsa.rb
ADDED
@@ -0,0 +1,299 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module ALSA
|
6
|
+
|
7
|
+
def self.logger
|
8
|
+
unless @logger
|
9
|
+
@logger = Logger.new(STDERR)
|
10
|
+
@logger.level = Logger::WARN
|
11
|
+
end
|
12
|
+
|
13
|
+
@logger
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.logger=(logger); @logger = logger; end
|
17
|
+
|
18
|
+
def self.try_to(message, &block)
|
19
|
+
logger.debug { message }
|
20
|
+
if ALSA::Native::error_code?(response = yield)
|
21
|
+
raise "cannot #{message} (#{ALSA::Native::strerror(response)})"
|
22
|
+
else
|
23
|
+
response
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
module Native
|
28
|
+
extend FFI::Library
|
29
|
+
ffi_lib "libasound.so"
|
30
|
+
|
31
|
+
attach_function :strerror, :snd_strerror, [:int], :string
|
32
|
+
|
33
|
+
def self.error_code?(response)
|
34
|
+
response < 0
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module PCM
|
39
|
+
|
40
|
+
class Capture
|
41
|
+
|
42
|
+
attr_accessor :handle
|
43
|
+
|
44
|
+
def self.open(device, hardware_attributes = {}, &block)
|
45
|
+
Capture.new.open(device, hardware_attributes, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def open(device, hardware_attributes = {}, &block)
|
49
|
+
capture_handle = FFI::MemoryPointer.new :pointer
|
50
|
+
ALSA::try_to "open audio device #{device}" do
|
51
|
+
ALSA::PCM::Native::open capture_handle, device, ALSA::PCM::Native::STREAM_CAPTURE, ALSA::PCM::Native::BLOCK
|
52
|
+
end
|
53
|
+
self.handle = capture_handle.read_pointer
|
54
|
+
|
55
|
+
self.hardware_parameters=hardware_attributes
|
56
|
+
|
57
|
+
if block_given?
|
58
|
+
begin
|
59
|
+
yield self
|
60
|
+
ensure
|
61
|
+
self.close
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def change_hardware_parameters
|
67
|
+
hw_params = HwParameters.new(self).default_for_device
|
68
|
+
|
69
|
+
begin
|
70
|
+
yield hw_params
|
71
|
+
|
72
|
+
ALSA::try_to "set hw parameters" do
|
73
|
+
ALSA::PCM::Native::hw_params self.handle, hw_params.handle
|
74
|
+
end
|
75
|
+
ensure
|
76
|
+
hw_params.free
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def hardware_parameters
|
81
|
+
HwParameters.new(self).current_for_device
|
82
|
+
end
|
83
|
+
alias_method :hw_params, :hardware_parameters
|
84
|
+
|
85
|
+
def hardware_parameters=(attributes= {})
|
86
|
+
attributes = {:access => :rw_interleaved}.update(attributes)
|
87
|
+
change_hardware_parameters do |hw_params|
|
88
|
+
hw_params.update_attributes(attributes)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def read
|
93
|
+
ALSA.logger.debug { "start read with #{hw_params.sample_rate}, #{hw_params.channels} channels"}
|
94
|
+
|
95
|
+
# use an 500ms buffer
|
96
|
+
frame_count = hw_params.sample_rate / 2
|
97
|
+
ALSA.logger.debug { "allocate #{hw_params.buffer_size_for(frame_count)} bytes for #{frame_count} frames" }
|
98
|
+
FFI::MemoryPointer.new(:char, hw_params.buffer_size_for(frame_count)) do |buffer|
|
99
|
+
begin
|
100
|
+
read_buffer buffer, frame_count
|
101
|
+
end while yield buffer, frame_count
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def read_buffer(buffer, frame_count)
|
106
|
+
read_count = ALSA::try_to "read from audio interface" do
|
107
|
+
response = ALSA::PCM::Native::readi(self.handle, buffer, frame_count)
|
108
|
+
if ALSA::Native::error_code?(response)
|
109
|
+
ALSA.logger.warn { "try to recover '#{ALSA::Native::strerror(response)}' on read"}
|
110
|
+
ALSA::PCM::Native::pcm_recover(self.handle, response, 1)
|
111
|
+
else
|
112
|
+
response
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
missing_frame_count = frame_count - read_count
|
117
|
+
if missing_frame_count > 0
|
118
|
+
ALSA.logger.debug { "re-read missing frame count: #{missing_frame_count}"}
|
119
|
+
read_buffer_size = hw_params.buffer_size_for(read_count)
|
120
|
+
# buffer[read_buffer_size] doesn't return a MemoryPointer
|
121
|
+
read_buffer(buffer + read_buffer_size, missing_frame_count)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def close
|
126
|
+
ALSA::try_to "close audio device" do
|
127
|
+
ALSA::PCM::Native::close self.handle
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class HwParameters
|
132
|
+
|
133
|
+
attr_accessor :handle, :device
|
134
|
+
|
135
|
+
def initialize(device = nil)
|
136
|
+
hw_params_pointer = FFI::MemoryPointer.new :pointer
|
137
|
+
|
138
|
+
ALSA::PCM::Native::hw_params_malloc hw_params_pointer
|
139
|
+
self.handle = hw_params_pointer.read_pointer
|
140
|
+
|
141
|
+
self.device = device if device
|
142
|
+
end
|
143
|
+
|
144
|
+
def update_attributes(attributes)
|
145
|
+
attributes.each_pair { |name, value| send("#{name}=", value) }
|
146
|
+
end
|
147
|
+
|
148
|
+
def default_for_device
|
149
|
+
ALSA::try_to "initialize hardware parameter structure" do
|
150
|
+
ALSA::PCM::Native::hw_params_any device.handle, self.handle
|
151
|
+
end
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
def current_for_device
|
156
|
+
ALSA::try_to "retrieve current hardware parameters" do
|
157
|
+
ALSA::PCM::Native::hw_params_current device.handle, self.handle
|
158
|
+
end
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
def access=(access)
|
163
|
+
ALSA::try_to "set access type" do
|
164
|
+
ALSA::PCM::Native::hw_params_set_access self.device.handle, self.handle, ALSA::PCM::Native::Access.const_get(access.to_s.upcase)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def channels=(channels)
|
169
|
+
ALSA::try_to "set channel count : #{channels}" do
|
170
|
+
ALSA::PCM::Native::hw_params_set_channels self.device.handle, self.handle, channels
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def sample_rate=(sample_rate)
|
175
|
+
ALSA::try_to "set sample rate" do
|
176
|
+
rate = FFI::MemoryPointer.new(:int)
|
177
|
+
rate.write_int(sample_rate)
|
178
|
+
|
179
|
+
dir = FFI::MemoryPointer.new(:int)
|
180
|
+
dir.write_int(0)
|
181
|
+
|
182
|
+
error_code = ALSA::PCM::Native::hw_params_set_rate_near self.device.handle, self.handle, rate, dir
|
183
|
+
|
184
|
+
rate.free
|
185
|
+
dir.free
|
186
|
+
|
187
|
+
error_code
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def sample_rate
|
192
|
+
rate = nil
|
193
|
+
ALSA::try_to "get sample rate" do
|
194
|
+
rate_pointer = FFI::MemoryPointer.new(:int)
|
195
|
+
dir_pointer = FFI::MemoryPointer.new(:int)
|
196
|
+
dir_pointer.write_int(0)
|
197
|
+
|
198
|
+
error_code = ALSA::PCM::Native::hw_params_get_rate self.handle, rate_pointer, dir_pointer
|
199
|
+
|
200
|
+
rate = rate_pointer.read_int
|
201
|
+
|
202
|
+
rate_pointer.free
|
203
|
+
dir_pointer.free
|
204
|
+
|
205
|
+
error_code
|
206
|
+
end
|
207
|
+
rate
|
208
|
+
end
|
209
|
+
|
210
|
+
def sample_format=(sample_format)
|
211
|
+
ALSA::try_to "set sample format" do
|
212
|
+
ALSA::PCM::Native::hw_params_set_format self.device.handle, self.handle, ALSA::PCM::Native::Format.const_get(sample_format.to_s.upcase)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def sample_format
|
217
|
+
format = nil
|
218
|
+
FFI::MemoryPointer.new(:int) do |format_pointer|
|
219
|
+
ALSA::try_to "get sample format" do
|
220
|
+
ALSA::PCM::Native::hw_params_get_format self.handle, format_pointer
|
221
|
+
end
|
222
|
+
format = format_pointer.read_int
|
223
|
+
end
|
224
|
+
format
|
225
|
+
end
|
226
|
+
|
227
|
+
def channels
|
228
|
+
channels = nil
|
229
|
+
FFI::MemoryPointer.new(:int) do |channels_pointer|
|
230
|
+
ALSA::try_to "get channels" do
|
231
|
+
ALSA::PCM::Native::hw_params_get_channels self.handle, channels_pointer
|
232
|
+
end
|
233
|
+
channels = channels_pointer.read_int
|
234
|
+
end
|
235
|
+
channels
|
236
|
+
end
|
237
|
+
|
238
|
+
def buffer_size_for(frame_count)
|
239
|
+
ALSA::PCM::Native::format_size(self.sample_format, frame_count) * self.channels
|
240
|
+
end
|
241
|
+
|
242
|
+
def free
|
243
|
+
ALSA::try_to "unallocate hw_params" do
|
244
|
+
ALSA::PCM::Native::hw_params_free self.handle
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
module Native
|
253
|
+
extend FFI::Library
|
254
|
+
ffi_lib "libasound.so"
|
255
|
+
|
256
|
+
STREAM_CAPTURE = 1
|
257
|
+
BLOCK = 0
|
258
|
+
attach_function :open, :snd_pcm_open, [:pointer, :string, :int, :int], :int
|
259
|
+
attach_function :prepare, :snd_pcm_prepare, [ :pointer ], :int
|
260
|
+
attach_function :close, :snd_pcm_close, [:pointer], :int
|
261
|
+
|
262
|
+
attach_function :readi, :snd_pcm_readi, [ :pointer, :pointer, :ulong ], :long
|
263
|
+
|
264
|
+
attach_function :pcm_recover, :snd_pcm_recover, [ :pointer, :int, :int ], :int
|
265
|
+
|
266
|
+
attach_function :hw_params_malloc, :snd_pcm_hw_params_malloc, [:pointer], :int
|
267
|
+
attach_function :hw_params_free, :snd_pcm_hw_params_free, [:pointer], :int
|
268
|
+
|
269
|
+
attach_function :hw_params, :snd_pcm_hw_params, [ :pointer, :pointer ], :int
|
270
|
+
attach_function :hw_params_any, :snd_pcm_hw_params_any, [:pointer, :pointer], :int
|
271
|
+
attach_function :hw_params_current, :snd_pcm_hw_params_current, [ :pointer, :pointer ], :int
|
272
|
+
|
273
|
+
|
274
|
+
module Access
|
275
|
+
MMAP_INTERLEAVED = 0
|
276
|
+
MMAP_NONINTERLEAVED = 1
|
277
|
+
MMAP_COMPLEX = 2
|
278
|
+
RW_INTERLEAVED = 3
|
279
|
+
RW_NONINTERLEAVED = 4
|
280
|
+
end
|
281
|
+
|
282
|
+
attach_function :hw_params_set_access, :snd_pcm_hw_params_set_access, [ :pointer, :pointer, :int ], :int
|
283
|
+
|
284
|
+
module Format
|
285
|
+
S16_LE = 2
|
286
|
+
end
|
287
|
+
|
288
|
+
attach_function :hw_params_set_format, :snd_pcm_hw_params_set_format, [ :pointer, :pointer, :int ], :int
|
289
|
+
attach_function :hw_params_get_format, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
|
290
|
+
attach_function :hw_params_get_rate, :snd_pcm_hw_params_get_rate, [ :pointer, :pointer, :pointer ], :int
|
291
|
+
attach_function :hw_params_set_rate_near, :snd_pcm_hw_params_set_rate_near, [ :pointer, :pointer, :pointer, :pointer ], :int
|
292
|
+
attach_function :hw_params_set_channels, :snd_pcm_hw_params_set_channels, [ :pointer, :pointer, :uint ], :int
|
293
|
+
attach_function :hw_params_get_channels, :snd_pcm_hw_params_get_format, [ :pointer, :pointer ], :int
|
294
|
+
attach_function :hw_params_set_periods, :snd_pcm_hw_params_set_periods, [ :pointer, :pointer, :uint, :int ], :int
|
295
|
+
|
296
|
+
attach_function :format_size, :snd_pcm_format_size, [ :int, :uint ], :int
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|