alsa-backup 0.0.8
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 +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
|