ffi-libfreenect 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/LICENSE.txt +23 -0
- data/README.rdoc +86 -0
- data/Rakefile +45 -0
- data/VERSION +1 -0
- data/examples/record.rb +76 -0
- data/examples/tilt_led.rb +105 -0
- data/examples/tilt_nod.rb +28 -0
- data/examples/video_snapshot.rb +61 -0
- data/lib/ffi/freenect.rb +170 -0
- data/lib/freenect.rb +40 -0
- data/lib/freenect/context.rb +66 -0
- data/lib/freenect/device.rb +210 -0
- data/lib/freenect/sync.rb +93 -0
- data/spec/context_spec.rb +61 -0
- data/spec/device_spec.rb +97 -0
- data/spec/freenect_spec.rb +90 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +11 -0
- metadata +113 -0
data/lib/freenect.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
# we may one day have a native extension for bindings... for now only
|
3
|
+
# ffi/freenect exists
|
4
|
+
require 'ffi/freenect'
|
5
|
+
require 'freenect/context'
|
6
|
+
require 'freenect/device'
|
7
|
+
|
8
|
+
module Freenect
|
9
|
+
include FFI::Freenect
|
10
|
+
|
11
|
+
def self.init(*args)
|
12
|
+
Context.new(*args)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.lookup_video_format(fmt)
|
16
|
+
return (fmt.is_a?(Numeric) ? fmt : FFI::Freenect::VIDEO_FORMATS[fmt])
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.lookup_video_size(fmt)
|
20
|
+
l_fmt = (fmt.is_a?(Numeric) ? FFI::Freenect::VIDEO_FORMATS[fmt] : fmt)
|
21
|
+
if l_fmt.nil? or (sz = FFI::Freenect::VIDEO_SIZES[l_fmt]).nil?
|
22
|
+
return nil
|
23
|
+
else
|
24
|
+
return sz
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.lookup_depth_format(fmt)
|
29
|
+
return (fmt.is_a?(Numeric) ? fmt : FFI::Freenect::DEPTH_FORMATS[fmt])
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.lookup_depth_size(fmt)
|
33
|
+
l_fmt = (fmt.is_a?(Numeric) ? FFI::Freenect::DEPTH_FORMATS[fmt] : fmt)
|
34
|
+
if l_fmt.nil? or (sz = FFI::Freenect::DEPTH_SIZES[l_fmt]).nil?
|
35
|
+
return nil
|
36
|
+
else
|
37
|
+
return sz
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'ffi/freenect'
|
2
|
+
require 'freenect/device'
|
3
|
+
|
4
|
+
module Freenect
|
5
|
+
class ContextError < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Context
|
9
|
+
def initialize(usb_ctx=nil)
|
10
|
+
ctx_p = FFI::MemoryPointer.new(:pointer)
|
11
|
+
if ::FFI::Freenect.freenect_init(ctx_p, usb_ctx) != 0
|
12
|
+
raise ContextError, "freenect_init() returned nonzero"
|
13
|
+
elsif ctx_p.null?
|
14
|
+
raise ContextError, "freenect_init() produced a NULL context"
|
15
|
+
end
|
16
|
+
@ctx = ctx_p.read_pointer
|
17
|
+
end
|
18
|
+
|
19
|
+
def context
|
20
|
+
if @ctx_closed
|
21
|
+
raise ContextError, "This context has been shut down and can no longer be used"
|
22
|
+
else
|
23
|
+
return @ctx
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def num_devices
|
28
|
+
::FFI::Freenect.freenect_num_devices(self.context)
|
29
|
+
end
|
30
|
+
|
31
|
+
def open_device(idx)
|
32
|
+
return Device.new(self, idx)
|
33
|
+
end
|
34
|
+
|
35
|
+
alias [] open_device
|
36
|
+
|
37
|
+
def set_log_level(loglevel)
|
38
|
+
::FFI::Freenect.freenect_set_log_level(self.context, loglevel)
|
39
|
+
end
|
40
|
+
|
41
|
+
alias log_level= set_log_level
|
42
|
+
|
43
|
+
def set_log_callback(&block)
|
44
|
+
::FFI::Freenect.freenect_set_log_callback(self.context, block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def process_events
|
48
|
+
::FFI::Freenect.freenect_process_events(self.context)
|
49
|
+
end
|
50
|
+
|
51
|
+
def close
|
52
|
+
unless closed?
|
53
|
+
if ::FFI::Freenect.freenect_shutdown(@ctx) != 0
|
54
|
+
raise ContextError, "freenect_shutdown() returned nonzero"
|
55
|
+
end
|
56
|
+
@ctx_closed = true
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
alias shutdown close
|
61
|
+
|
62
|
+
def closed?
|
63
|
+
@ctx_closed == true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
|
2
|
+
require 'ffi/freenect'
|
3
|
+
require 'freenect/context'
|
4
|
+
|
5
|
+
module Freenect
|
6
|
+
RawTiltState = FFI::Freenect::RawTiltState
|
7
|
+
|
8
|
+
class DeviceError < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class Device
|
12
|
+
# Returns a device object tracked by its ruby object reference ID stored
|
13
|
+
# in user data.
|
14
|
+
#
|
15
|
+
# This method is intended for internal use.
|
16
|
+
def self.by_reference(devp)
|
17
|
+
unless devp.null? or (refp=FFI::Freenect.freenect_get_user(devp)).null?
|
18
|
+
obj=ObjectSpace._id2ref(refp.read_long_long)
|
19
|
+
return obj if obj.is_a?(Device)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(ctx, idx)
|
24
|
+
dev_p = ::FFI::MemoryPointer.new(:pointer)
|
25
|
+
@ctx = ctx
|
26
|
+
|
27
|
+
if ::FFI::Freenect.freenect_open_device(@ctx.context, dev_p, idx) != 0
|
28
|
+
raise DeviceError, "unable to open device #{idx} from #{ctx.inspect}"
|
29
|
+
end
|
30
|
+
|
31
|
+
@dev = dev_p.read_pointer
|
32
|
+
save_object_id!()
|
33
|
+
end
|
34
|
+
|
35
|
+
def closed?
|
36
|
+
@ctx.closed? or (@dev_closed == true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
unless closed?
|
41
|
+
if ::FFI::Freenect.freenect_close_device(@dev) == 0
|
42
|
+
@dev_closed = true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def device
|
48
|
+
if closed?
|
49
|
+
raise DeviceError, "this device is closed and can no longer be used"
|
50
|
+
else
|
51
|
+
return @dev
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def context
|
56
|
+
@ctx
|
57
|
+
end
|
58
|
+
|
59
|
+
def get_tilt_state
|
60
|
+
unless (p=::FFI::Freenect.freenect_get_tilt_state(self.device)).null?
|
61
|
+
return RawTiltState.new(p)
|
62
|
+
else
|
63
|
+
raise DeviceError, "freenect_get_tilt_state() returned a NULL tilt_state"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
alias tilt_state get_tilt_state
|
68
|
+
|
69
|
+
# Returns the current tilt angle
|
70
|
+
def get_tilt_degrees
|
71
|
+
::FFI::Freenect.freenect_get_tilt_degs(self.device)
|
72
|
+
end
|
73
|
+
|
74
|
+
alias tilt get_tilt_degrees
|
75
|
+
|
76
|
+
# Sets the tilt angle.
|
77
|
+
# Maximum tilt angle range is between +30 and -30
|
78
|
+
def set_tilt_degrees(angle)
|
79
|
+
::FFI::Freenect.freenect_set_tilt_degs(self.device, angle)
|
80
|
+
return(update_tilt_state() < 0) # based on libfreenect error cond. as of 12-21-10
|
81
|
+
end
|
82
|
+
|
83
|
+
alias tilt= set_tilt_degrees
|
84
|
+
|
85
|
+
# Defines a handler for depth events.
|
86
|
+
#
|
87
|
+
# @yield [device, depth_buf, timestamp]
|
88
|
+
# @yieldparam device A pointer to the device that generated the event.
|
89
|
+
# @yieldparam depth_buf A pointer to the buffer containing the depth data.
|
90
|
+
# @yieldparam timestamp A timestamp for the event?
|
91
|
+
def set_depth_callback(&block)
|
92
|
+
@depth_callback = block
|
93
|
+
::FFI::Freenect.freenect_set_depth_callback(self.device, @depth_callback)
|
94
|
+
end
|
95
|
+
|
96
|
+
alias on_depth set_depth_callback
|
97
|
+
|
98
|
+
# Defines a handler for video events.
|
99
|
+
#
|
100
|
+
# @yield [device, video_buf, timestamp]
|
101
|
+
# @yieldparam device A pointer to the device that generated the event.
|
102
|
+
# @yieldparam video_buf A pointer to the buffer containing the video data.
|
103
|
+
# @yieldparam timestamp A timestamp for the event?
|
104
|
+
def set_video_callback(&block)
|
105
|
+
@video_callback = block
|
106
|
+
::FFI::Freenect.freenect_set_video_callback(self.device, @video_callback)
|
107
|
+
end
|
108
|
+
|
109
|
+
alias on_video set_video_callback
|
110
|
+
|
111
|
+
def start_depth
|
112
|
+
unless(::FFI::Freenect.freenect_start_depth(self.device) == 0)
|
113
|
+
raise DeviceError, "Error in freenect_start_depth()"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def stop_depth
|
118
|
+
unless(::FFI::Freenect.freenect_stop_depth(self.device) == 0)
|
119
|
+
raise DeviceError, "Error in freenect_stop_depth()"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def start_video
|
124
|
+
unless(::FFI::Freenect.freenect_start_video(self.device) == 0)
|
125
|
+
raise DeviceError, "Error in freenect_start_video()"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def stop_video
|
130
|
+
unless(::FFI::Freenect.freenect_stop_video(self.device) == 0)
|
131
|
+
raise DeviceError, "Error in freenect_stop_video()"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def set_depth_format(fmt)
|
136
|
+
l_fmt = fmt.is_a?(Numeric)? fmt : Freenect::DEPTH_FORMATS[fmt]
|
137
|
+
ret = ::FFI::Freenect.freenect_set_depth_format(self.device, l_fmt)
|
138
|
+
if (ret== 0)
|
139
|
+
@depth_format = fmt
|
140
|
+
else
|
141
|
+
raise DeviceError, "Error calling freenect_set_depth_format(self, #{fmt})"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
alias depth_format= set_depth_format
|
146
|
+
|
147
|
+
# returns the symbolic constant for the current depth format
|
148
|
+
def depth_format
|
149
|
+
(@depth_format.is_a?(Numeric))? Freenect::DEPTH_FORMATS[@depth_format] : @depth_format
|
150
|
+
end
|
151
|
+
|
152
|
+
# Sets the video format to one of the following accepted values:
|
153
|
+
#
|
154
|
+
def set_video_format(fmt)
|
155
|
+
l_fmt = fmt.is_a?(Numeric)? fmt : Freenect::VIDEO_FORMATS[fmt]
|
156
|
+
ret = ::FFI::Freenect.freenect_set_video_format(self.device, l_fmt)
|
157
|
+
if (ret== 0)
|
158
|
+
@video_format = fmt
|
159
|
+
else
|
160
|
+
raise DeviceError, "Error calling freenect_set_video_format(self, #{fmt})"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
alias video_format= set_video_format
|
165
|
+
|
166
|
+
def video_format
|
167
|
+
(@video_format.is_a?(Numeric))? ::Freenect::VIDEO_FORMATS[@video_format] : @video_format
|
168
|
+
end
|
169
|
+
|
170
|
+
# Sets the led to one of the following accepted values:
|
171
|
+
# :off, Freenect::LED_OFF
|
172
|
+
# :green, Freenect::LED_GREEN
|
173
|
+
# :red, Freenect::LED_RED
|
174
|
+
# :yellow, Freenect::LED_YELLOW
|
175
|
+
# :blink_yellow, Freenect::LED_BLINK_YELLOW
|
176
|
+
# :blink_green, Freenect::LED_BLINK_GREEN
|
177
|
+
# :blink_red_yellow, Freenect::LED_BLINK_RED_YELLOW
|
178
|
+
#
|
179
|
+
# Either the symbol or numeric constant can be specified.
|
180
|
+
def set_led(mode)
|
181
|
+
return(::FFI::Freenect.freenect_set_led(self.device, mode) == 0)
|
182
|
+
end
|
183
|
+
|
184
|
+
alias led= set_led
|
185
|
+
|
186
|
+
def reference_id
|
187
|
+
unless (p=::FFI::Freenect.freenect_get_user(device)).null?
|
188
|
+
p.read_long_long
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
private
|
193
|
+
def set_depth_buffer(buf)
|
194
|
+
end
|
195
|
+
|
196
|
+
def set_video_buffer(buf)
|
197
|
+
end
|
198
|
+
|
199
|
+
def save_object_id!
|
200
|
+
objid_p = FFI::MemoryPointer.new(:long_long)
|
201
|
+
objid_p.write_long_long(self.object_id)
|
202
|
+
::FFI::Freenect.freenect_set_user(self.device, objid_p)
|
203
|
+
end
|
204
|
+
|
205
|
+
def update_tilt_state
|
206
|
+
::FFI::Freenect.freenect_update_tilt_state(self.device)
|
207
|
+
end
|
208
|
+
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
|
2
|
+
require 'freenect'
|
3
|
+
|
4
|
+
module Freenect
|
5
|
+
module Sync
|
6
|
+
class FormatError < StandardError
|
7
|
+
end
|
8
|
+
|
9
|
+
# Synchronous video function (starts the runloop if it isn't running)
|
10
|
+
#
|
11
|
+
# @param idx
|
12
|
+
# Device index. Default: 0
|
13
|
+
#
|
14
|
+
# @param fmt
|
15
|
+
# Video Format. See FFI::Freenect::VIDEO_FORMATS. Default is :rgb
|
16
|
+
#
|
17
|
+
# @return [Numeric, String]
|
18
|
+
# Returns an array containing a numeric timestamp and the video buffer
|
19
|
+
# snapshot with a size based on the requested video format.
|
20
|
+
#
|
21
|
+
# @raise FormatError
|
22
|
+
# An exception is raised if an invalid format is specified.
|
23
|
+
#
|
24
|
+
# @raise RuntimeError
|
25
|
+
# An exception is raised if an unknown error occurs in the
|
26
|
+
# freenect_sync_get_video function
|
27
|
+
#
|
28
|
+
def self.get_video(idx=nil, fmt=nil)
|
29
|
+
idx ||= 0
|
30
|
+
fmt ||= :rgb
|
31
|
+
|
32
|
+
if (buf_size = Freenect.lookup_video_size(fmt)).nil?
|
33
|
+
raise(FormatError, "Invalid video format: #{fmt.inspect}")
|
34
|
+
end
|
35
|
+
|
36
|
+
video_p = FFI::MemoryPointer.new(buf_size)
|
37
|
+
timestamp_p = FFI::MemoryPointer.new(:uint32)
|
38
|
+
|
39
|
+
ret = ::FFI::Freenect.freenect_sync_get_video(video_p, timestamp_p, idx, Freenect.lookup_video_format(fmt))
|
40
|
+
|
41
|
+
if ret != 0
|
42
|
+
raise("Unknown error in freenect_sync_get_video()") # TODO is errno set or something here?
|
43
|
+
else
|
44
|
+
return [timestamp_p.read_int, video_p.read_string_length(buf_size)]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Synchronous depth function (starts the runloop if it isn't running)
|
49
|
+
#
|
50
|
+
# @param idx
|
51
|
+
# Device index. Default: 0
|
52
|
+
#
|
53
|
+
# @param fmt
|
54
|
+
# Video Format. See FFI::Freenect::DEPTH_FORMATS. Default is :rgb
|
55
|
+
#
|
56
|
+
# @return [Numeric, String]
|
57
|
+
# Returns an array containing a numeric timestamp and the depth buffer
|
58
|
+
# snapshot with a size based on the requested video format.
|
59
|
+
#
|
60
|
+
# @raise FormatError
|
61
|
+
# An exception is raised if an invalid format is specified.
|
62
|
+
#
|
63
|
+
# @raise RuntimeError
|
64
|
+
# An exception is raised if an unknown error occurs in the
|
65
|
+
# freenect_sync_get_video function
|
66
|
+
#
|
67
|
+
def self.get_depth(idx=0, fmt=:depth_11bit)
|
68
|
+
idx ||= 0
|
69
|
+
fmt ||= :depth_11bit
|
70
|
+
|
71
|
+
if (buf_size = Freenect.lookup_depth_size(fmt)).nil?
|
72
|
+
raise(FormatError, "Invalid depth format: #{fmt.inspect}")
|
73
|
+
end
|
74
|
+
|
75
|
+
depth_p = FFI::MemoryPointer.new(buf_size)
|
76
|
+
timestamp_p = FFI::MemoryPointer.new(:uint32)
|
77
|
+
|
78
|
+
ret = ::FFI::Freenect.freenect_sync_get_depth(depth_p, timestamp_p, idx, Freenect.lookup_depth_format(fmt))
|
79
|
+
if ret != 0
|
80
|
+
raise("Unknown error in freenect_sync_get_depth()") # TODO is errno set or something here?
|
81
|
+
else
|
82
|
+
return [timestamp_p.read_int, video_p.read_string_length(buf_size)]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Stops the sync runloop
|
87
|
+
def self.stop
|
88
|
+
FFI::Freenect.freenect_sync_stop()
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Freenect::Context do
|
5
|
+
before(:all) { @ctx = Freenect::Context.new() }
|
6
|
+
after(:all) { @ctx.close if @ctx }
|
7
|
+
|
8
|
+
it "should initialize a context" do
|
9
|
+
@ctx.should be_kind_of(Freenect::Context)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should indicate the number of devices attached" do
|
13
|
+
@ctx.num_devices.should >= 0
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should open a device when one is connected" do
|
17
|
+
if @ctx.num_devices > 0
|
18
|
+
dev = @ctx[0]
|
19
|
+
dev.should be_kind_of(Freenect::Device)
|
20
|
+
lambda { dev.close }.should_not raise_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should raise an exception when an invalid device index is opened" do
|
25
|
+
lambda { @ctx[@ctx.num_devices + 1] }.should raise_error(StandardError)
|
26
|
+
lambda { @ctx.open_device(@ctx.num_devices + 1) }.should raise_error(StandardError)
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should allow a libfreenect log level to be set using symbols or constants" do
|
30
|
+
(@ctx.log_level = :fatal).should == :fatal
|
31
|
+
(@ctx.log_level = Freenect::LOG_FATAL).should == 0
|
32
|
+
|
33
|
+
(@ctx.log_level = :error).should == :error
|
34
|
+
(@ctx.log_level = Freenect::LOG_ERROR).should == 1
|
35
|
+
|
36
|
+
(@ctx.log_level = :warning).should == :warning
|
37
|
+
(@ctx.log_level = Freenect::LOG_WARNING).should == 2
|
38
|
+
|
39
|
+
(@ctx.log_level = :notice).should == :notice
|
40
|
+
(@ctx.log_level = Freenect::LOG_NOTICE).should == 3
|
41
|
+
|
42
|
+
(@ctx.log_level = :info).should == :info
|
43
|
+
(@ctx.log_level = Freenect::LOG_INFO).should == 4
|
44
|
+
|
45
|
+
(@ctx.log_level = :debug).should == :debug
|
46
|
+
(@ctx.log_level = Freenect::LOG_DEBUG).should == 5
|
47
|
+
|
48
|
+
(@ctx.log_level = :spew).should == :spew
|
49
|
+
(@ctx.log_level = Freenect::LOG_SPEW).should == 6
|
50
|
+
|
51
|
+
(@ctx.log_level = :flood).should == :flood
|
52
|
+
(@ctx.log_level = Freenect::LOG_FLOOD).should == 7
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should allow a log callback to be set" do
|
56
|
+
@ctx.set_log_callback {|a,b,c| p [a,b,c] }
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
end
|
61
|
+
|