vncrec 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +36 -0
- data/Rakefile +8 -0
- data/bin/vncrec +80 -0
- data/examples/exit.rb +8 -0
- data/examples/mp4.rb +10 -0
- data/examples/mp4audio.rb +11 -0
- data/ext/enchex_c/ReadRect.c +215 -0
- data/ext/enchex_c/extconf.rb +5 -0
- data/lib/vncrec.rb +6 -0
- data/lib/vncrec/constants.rb +10 -0
- data/lib/vncrec/recorder.rb +286 -0
- data/lib/vncrec/rfb/enchex.rb +115 -0
- data/lib/vncrec/rfb/encraw.rb +33 -0
- data/lib/vncrec/rfb/enczrle.rb +202 -0
- data/lib/vncrec/rfb/proxy.rb +200 -0
- data/lib/vncrec/version.rb +3 -0
- data/lib/vncrec/writers.rb +209 -0
- data/spec/executable_spec.rb +40 -0
- data/spec/recorder_spec.rb +338 -0
- data/spec/spec_helper.rb +171 -0
- data/spec/writers_spec.rb +121 -0
- data/vncrec.gemspec +34 -0
- metadata +165 -0
@@ -0,0 +1,202 @@
|
|
1
|
+
module VNCRec
|
2
|
+
module RFB
|
3
|
+
|
4
|
+
module EncZRLE
|
5
|
+
|
6
|
+
class Stream
|
7
|
+
|
8
|
+
def initialize(io,bitspp,depth)
|
9
|
+
@io = io
|
10
|
+
@zstream = Zlib::Inflate.new
|
11
|
+
@bpp_orig = (bitspp.to_f/8.0).ceil
|
12
|
+
@bpp = case bitspp
|
13
|
+
when 32 then
|
14
|
+
@depth <= 24 ? 3 : 4
|
15
|
+
when 8 then
|
16
|
+
1
|
17
|
+
else
|
18
|
+
raise "Cannot handle such pixel format"
|
19
|
+
end
|
20
|
+
@depth = depth
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_zchunk
|
24
|
+
zdata_len = (@io.readpartial 4).unpack("L>")[0]
|
25
|
+
zdata = ""
|
26
|
+
to_read = zdata_len
|
27
|
+
|
28
|
+
while zdata.size < zdata_len
|
29
|
+
zdata += @io.read(to_read)
|
30
|
+
to_read = zdata_len - zdata.size
|
31
|
+
end
|
32
|
+
return zdata
|
33
|
+
end
|
34
|
+
|
35
|
+
def read_rect(w,h)
|
36
|
+
fb = Array.new(w*h*@bpp)
|
37
|
+
data = ""
|
38
|
+
begin
|
39
|
+
data = @zstream.inflate(read_zchunk)
|
40
|
+
rescue Zlib::DataError
|
41
|
+
return
|
42
|
+
end
|
43
|
+
|
44
|
+
stream = StringIO.new data
|
45
|
+
|
46
|
+
tile_cols = (w.to_f/64).ceil
|
47
|
+
tile_rows = (h.to_f/64).ceil
|
48
|
+
tile_cols_rem = w % 64
|
49
|
+
tile_rows_rem = h % 64
|
50
|
+
|
51
|
+
tile_rows.times do |tile_row_num|
|
52
|
+
tile_cols.times do |tile_col_num|
|
53
|
+
th = if ((tile_row_num == tile_rows-1) and (tile_rows_rem > 0))
|
54
|
+
tile_rows_rem
|
55
|
+
else
|
56
|
+
64
|
57
|
+
end
|
58
|
+
tw = if ((tile_col_num == tile_cols-1) and (tile_cols_rem > 0))
|
59
|
+
tile_cols_rem
|
60
|
+
else
|
61
|
+
64
|
62
|
+
end
|
63
|
+
|
64
|
+
subenc = stream.readbyte
|
65
|
+
tile = case subenc
|
66
|
+
when 0 then #Raw
|
67
|
+
tile = Array.new(tw*th)
|
68
|
+
th.times do
|
69
|
+
tile << (stream.read tw*@bpp_orig).unpack("C*").join
|
70
|
+
end
|
71
|
+
tile
|
72
|
+
when 1 then #Solid
|
73
|
+
Array.new(tw*th, stream.read(@bpp_orig))
|
74
|
+
when 2..16 then #Packed palette
|
75
|
+
handle_ZRLE_packed_palette(stream, subenc, tw,th)
|
76
|
+
when 128 then #Plain RLE
|
77
|
+
handle_ZRLE_plain_RLE_tile(stream,tw,th)
|
78
|
+
when 130..255 then #RLE w/ palette
|
79
|
+
handle_ZRLE_palette_RLE_tile(stream,subenc-128, tw,th)
|
80
|
+
end
|
81
|
+
|
82
|
+
th.times do |y|
|
83
|
+
boline = (64 * tile_row_num + y) * w
|
84
|
+
offx = 64 * tile_col_num
|
85
|
+
fb[(boline+offx)...(boline+offx+tw)] = tile[(tw*y)...(tw*(y+1))]
|
86
|
+
end
|
87
|
+
end #tile_col.times
|
88
|
+
end#tile_row.times
|
89
|
+
return fb.join
|
90
|
+
end
|
91
|
+
|
92
|
+
def handle_ZRLE_palette_RLE_tile(stream,psize,tw=64,th=64)
|
93
|
+
palette = []
|
94
|
+
pixels = Array.new(tw*th)
|
95
|
+
psize.times do
|
96
|
+
palette << stream.read(@bpp)
|
97
|
+
end
|
98
|
+
len = 0
|
99
|
+
begin
|
100
|
+
while len < tw*th
|
101
|
+
id = stream.read(1).unpack("C")[0]
|
102
|
+
#
|
103
|
+
#+--------+--------+--------+--------+
|
104
|
+
#| id | 255 | .. | <255 |
|
105
|
+
#+--------+--------+--------+--------+
|
106
|
+
#
|
107
|
+
if (id & 0b10000000) == 0
|
108
|
+
rl = 1
|
109
|
+
else
|
110
|
+
id -= 128
|
111
|
+
rl = 0
|
112
|
+
rem = 0
|
113
|
+
while (rem = stream.readbyte) == 255
|
114
|
+
rl += 255
|
115
|
+
end
|
116
|
+
rl += rem + 1
|
117
|
+
end
|
118
|
+
pixels[len...(len+rl)] = Array.new(rl, palette[id]) #TODO: if rl == 1
|
119
|
+
len += rl
|
120
|
+
end
|
121
|
+
rescue EOFError
|
122
|
+
end
|
123
|
+
pixels
|
124
|
+
end
|
125
|
+
|
126
|
+
def handle_ZRLE_plain_RLE_tile(stream,tw=64,th=64)
|
127
|
+
pixels = Array.new(tw*th)
|
128
|
+
len = 0
|
129
|
+
begin
|
130
|
+
while len < tw*th
|
131
|
+
color = stream.read(@bpp)
|
132
|
+
#
|
133
|
+
#+--------+--------+--------+--------+
|
134
|
+
#| color | 255 | .. | <255 |
|
135
|
+
#+--------+--------+--------+--------+
|
136
|
+
#
|
137
|
+
rl = 0
|
138
|
+
rem = 0
|
139
|
+
while (rem = stream.readbyte) == 255
|
140
|
+
rl += 255
|
141
|
+
end
|
142
|
+
rl += rem + 1
|
143
|
+
pixels[len...(len+rl)] = Array.new(rl, color) #TODO: if rl == 1
|
144
|
+
len += rl
|
145
|
+
end
|
146
|
+
rescue EOFError
|
147
|
+
end
|
148
|
+
pixels
|
149
|
+
end
|
150
|
+
|
151
|
+
def handle_ZRLE_packed_palette(stream, psize, tw=64, th=64)
|
152
|
+
pixels = Array.new(tw*th, 0)
|
153
|
+
bitspp = case psize
|
154
|
+
when 2 then 1
|
155
|
+
when 3..4 then 2
|
156
|
+
when 5..16 then 4
|
157
|
+
else
|
158
|
+
return pixels
|
159
|
+
end
|
160
|
+
palette = []
|
161
|
+
psize.times do
|
162
|
+
palette << stream.read(@bpp).unpack("C*")
|
163
|
+
end
|
164
|
+
count = case psize
|
165
|
+
when 2 then th*((tw+7)/8)
|
166
|
+
when 3..4 then th*((tw+3)/4)
|
167
|
+
when 5..16 then th*((tw+1)/2)
|
168
|
+
end
|
169
|
+
off_bits = 0
|
170
|
+
bits_per_row = bitspp * tw
|
171
|
+
padding_bits = bits_per_row % 8
|
172
|
+
encoded_len_bits = 64 * (bits_per_row + padding_bits)
|
173
|
+
encoded = stream.read(count).unpack("C*")
|
174
|
+
pixnum = 0
|
175
|
+
while off_bits < (encoded_len_bits - padding_bits - bitspp)
|
176
|
+
b1 = encoded[off_bits/8]
|
177
|
+
b2 = encoded[off_bits/8 + 1] || 0
|
178
|
+
b1 <<= 8
|
179
|
+
pixels[pixnum] = palette[h_bitmask(b2 + b1, bitspp, off_bits % 16)].pack("C*")
|
180
|
+
off_bits += if (off_bits % bits_per_row) > (bits_per_row - padding_bits) and (padding_bits > 0)
|
181
|
+
bitspp + padding_bits
|
182
|
+
else
|
183
|
+
bitspp
|
184
|
+
end
|
185
|
+
pixnum += 1
|
186
|
+
end
|
187
|
+
pixels
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def h_bitmask(input,count,offset=0)
|
198
|
+
#return first n bits of ushort as integer
|
199
|
+
#TODO: make generalization of input type
|
200
|
+
input <<= offset
|
201
|
+
(input & (0xFFFF - 2**(16-count) + 1)) >> (16 - count)
|
202
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
require 'vncrec/rfb/encraw.rb'
|
4
|
+
require 'vncrec/rfb/enczrle.rb'
|
5
|
+
require 'vncrec/rfb/enchex.rb'
|
6
|
+
|
7
|
+
module VNCRec
|
8
|
+
module RFB
|
9
|
+
class Proxy
|
10
|
+
attr_accessor :name, :w, :h, :io, :data
|
11
|
+
|
12
|
+
# @param io [IO, #read, #sysread, #syswrite, #read_nonblock] string stream from VNC server.
|
13
|
+
# @param rfbv [String] version of RFB protocol, 3.8 is the only supported by now
|
14
|
+
# @param enc [Integer] encoding of video data used to transfer. One of the following:
|
15
|
+
# * {ENC_RAW}
|
16
|
+
# * {ENC_HEXTILE}
|
17
|
+
# * {ENC_ZRLE}
|
18
|
+
# @param pf [Hash] pixel format:
|
19
|
+
# * {VNCRec::PIX_FMT_BGR8} - 8 bits per pixel
|
20
|
+
# * {VNCRec::PIX_FMT_BGR32} - 32 bits per pixel
|
21
|
+
# @param w width of the screen area
|
22
|
+
# @param h height of the screen area
|
23
|
+
def initialize(io, rfbv, enc, pf)
|
24
|
+
@io = io
|
25
|
+
@version = rfbv
|
26
|
+
@enc = enc
|
27
|
+
@pf = pf
|
28
|
+
end
|
29
|
+
|
30
|
+
def prepare_framebuffer(w, h, bpp)
|
31
|
+
@w = w
|
32
|
+
@h = h
|
33
|
+
@bpp = bpp
|
34
|
+
@bypp = (bpp / 8.0).to_i
|
35
|
+
@wb = @w * @bypp
|
36
|
+
@data = "\x00" * @wb * @h
|
37
|
+
end
|
38
|
+
|
39
|
+
# Perform handshake
|
40
|
+
# @return w,h,server_name or nil
|
41
|
+
def handshake
|
42
|
+
# version
|
43
|
+
version = @io.readpartial 12
|
44
|
+
@io.syswrite(@version + "\n")
|
45
|
+
|
46
|
+
# security
|
47
|
+
num_of_st = @io.readbyte
|
48
|
+
if num_of_st == 0 # failed
|
49
|
+
reason_len = @io.readpartial(4).unpack('L>')[0]
|
50
|
+
reason = @io.readpartial(reason_len)
|
51
|
+
fail reason
|
52
|
+
else
|
53
|
+
num_of_st.times do
|
54
|
+
@io.readbyte
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
reply = [1].pack('C') # security type:none
|
59
|
+
@io.syswrite reply
|
60
|
+
|
61
|
+
stype = (@io.readpartial 4).unpack('H' * 8)
|
62
|
+
# client init
|
63
|
+
@io.syswrite reply
|
64
|
+
|
65
|
+
# server init
|
66
|
+
w = @io.readpartial(2).unpack('S>')[0]
|
67
|
+
h = @io.readpartial(2).unpack('S>')[0]
|
68
|
+
pf = @io.readpartial 16
|
69
|
+
nlen = @io.readpartial(4).unpack('L>')[0]
|
70
|
+
@name = @io.readpartial nlen
|
71
|
+
return [w, h, @name]
|
72
|
+
rescue
|
73
|
+
return nil
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set a way that server should use to represent pixel data
|
77
|
+
# @param [Hash] pixel format:
|
78
|
+
# * {VNCRec::PIX_FMT_BGR8}
|
79
|
+
# * {VNCRec::PIX_FMT_BGRA}
|
80
|
+
def set_pixel_format(format)
|
81
|
+
msg = [0, 0, 0, 0].pack('CC3')
|
82
|
+
begin
|
83
|
+
@io.syswrite msg
|
84
|
+
|
85
|
+
msg = [
|
86
|
+
format[:bpp],
|
87
|
+
format[:depth],
|
88
|
+
format[:bend],
|
89
|
+
format[:tcol],
|
90
|
+
format[:rmax],
|
91
|
+
format[:gmax],
|
92
|
+
format[:bmax],
|
93
|
+
format[:rshif],
|
94
|
+
format[:gshif],
|
95
|
+
format[:bshif],
|
96
|
+
0, 0, 0
|
97
|
+
].pack('CCCCS>S>S>CCCC3')
|
98
|
+
return @io.syswrite msg
|
99
|
+
|
100
|
+
rescue
|
101
|
+
return nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Set way of encoding video frames.
|
106
|
+
# @param encodings [Array<Integer>] encoding of video data used to transfer.
|
107
|
+
# * {ENC_RAW}
|
108
|
+
# * {ENC_HEXTILE}
|
109
|
+
# * {ENC_ZRLE}
|
110
|
+
def set_encodings(encodings)
|
111
|
+
num = encodings.size
|
112
|
+
msg = [2, 0, num].pack('CCS>')
|
113
|
+
begin
|
114
|
+
@io.syswrite msg
|
115
|
+
encodings.each do |e|
|
116
|
+
@io.syswrite([e].pack('l>'))
|
117
|
+
end
|
118
|
+
rescue
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Request framebuffer update.
|
124
|
+
# @param [Integer] inc incremental, request just difference
|
125
|
+
# between previous and current framebuffer state.
|
126
|
+
# @param x [Integer]
|
127
|
+
# @param y [Integer]
|
128
|
+
# @param w [Integer]
|
129
|
+
# @param h [Integer]
|
130
|
+
def fb_update_request(inc, x, y, w, h)
|
131
|
+
@inc = inc > 0
|
132
|
+
msg = [3, inc, x, y, w, h].pack('CCS>S>S>S>')
|
133
|
+
return @io.write msg
|
134
|
+
rescue
|
135
|
+
return nil
|
136
|
+
end
|
137
|
+
|
138
|
+
# Handle VNC server response. Call it right after +fb_update_request+.
|
139
|
+
# @return [Array] type, (either framebuffer, "bell", +handle_server_cuttext+ or +handle_colormap_update+ results)
|
140
|
+
def handle_response
|
141
|
+
t = (io.readpartial 1).ord
|
142
|
+
case t
|
143
|
+
when 0 then
|
144
|
+
handle_fb_update
|
145
|
+
return [t, @data]
|
146
|
+
when 1 then
|
147
|
+
return [t, handle_colormap_update]
|
148
|
+
when 2 then
|
149
|
+
return [t, 'bell']
|
150
|
+
when 3 then
|
151
|
+
return [t, handle_server_cuttext]
|
152
|
+
else
|
153
|
+
return [-1, nil]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Receives data and applies diffs(if incremental) to the @data
|
158
|
+
def handle_fb_update
|
159
|
+
fail 'run #prepare_framebuffer first' unless @data
|
160
|
+
enc = nil
|
161
|
+
@encs ||= { 0 => VNCRec::RFB::EncRaw,
|
162
|
+
5 => VNCRec::RFB::EncHextile,
|
163
|
+
16 => VNCRec::RFB::EncZRLE
|
164
|
+
}
|
165
|
+
_, numofrect = @io.read(3).unpack('CS>')
|
166
|
+
i = 0
|
167
|
+
while i < numofrect
|
168
|
+
hdr = @io.read 12
|
169
|
+
x, y, w, h, enc = hdr.unpack('S>S>S>S>l>')
|
170
|
+
mod = @encs.fetch(enc) { fail "Unsupported encoding #{enc}" }
|
171
|
+
mod.read_rect @io, x, y, w, h, @bpp, @data, @wb, @h
|
172
|
+
i += 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# @return [Array] palette
|
177
|
+
def handle_colormap_update
|
178
|
+
_, first_color, noc = (@io.read 5).unpack('CS>S>')
|
179
|
+
palette = []
|
180
|
+
noc.times do
|
181
|
+
palette << (@io.read 6).unpack('S>S>S>')
|
182
|
+
end
|
183
|
+
return palette
|
184
|
+
rescue
|
185
|
+
return nil
|
186
|
+
end
|
187
|
+
|
188
|
+
# @return [String] server cut text
|
189
|
+
def handle_server_cuttext
|
190
|
+
begin
|
191
|
+
_, _, _, len = (@io.read 7).unpack('C3L>')
|
192
|
+
text = @io.read len
|
193
|
+
rescue
|
194
|
+
return nil
|
195
|
+
end
|
196
|
+
text
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require "vncrec/constants.rb"
|
2
|
+
require "timeout"
|
3
|
+
require "thread"
|
4
|
+
|
5
|
+
module VNCRec
|
6
|
+
# Writers are wrappers for video files.
|
7
|
+
module Writers
|
8
|
+
# Raw video writer. Very similar to File
|
9
|
+
class RawVideo
|
10
|
+
def initialize(filename)
|
11
|
+
@filename = filename
|
12
|
+
@file = File.open(filename, 'w')
|
13
|
+
end
|
14
|
+
|
15
|
+
def write(data)
|
16
|
+
@file.write data
|
17
|
+
@file.flush
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@file.close
|
22
|
+
end
|
23
|
+
|
24
|
+
def closed?
|
25
|
+
@file.closed?
|
26
|
+
end
|
27
|
+
|
28
|
+
def size
|
29
|
+
@file.size
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# FFmpeg writer. Pipes video to FFmpeg instance exactly
|
34
|
+
# *fps* times per second. Audio addition is also
|
35
|
+
# supported (`:ffmpeg_ia`- and `:ffmpeg_out_opts strings`)
|
36
|
+
class FFmpeg
|
37
|
+
# @param filename [String] a name for video file.
|
38
|
+
# Should contain extension i.e. _.mp4_ of _.flv_.
|
39
|
+
# @note Choose _-acodec_ option in +:ffmpeg_out_opts+ accordingly.
|
40
|
+
# @param opts [Hash] options:
|
41
|
+
# * fps
|
42
|
+
# * pix_fmt (see +:colormode+)
|
43
|
+
# * geometry
|
44
|
+
# * ffmpeg_iv_opts
|
45
|
+
# * ffmpeg_ia_opts
|
46
|
+
# * ffmpeg_out_opts
|
47
|
+
# See {VNCRec::Recorder#initialize} for descriptions
|
48
|
+
def initialize(filename, opts = {})
|
49
|
+
@filename = filename
|
50
|
+
@fps = opts[:fps] || 12
|
51
|
+
pf = opts.fetch(:pix_fmt) { fail 'Undefined pixel format' }
|
52
|
+
@pix_fmt = get_pix_fmt pf
|
53
|
+
@size = opts.fetch(:geometry) { fail 'Undefined frame size' }
|
54
|
+
@frame_length = frame_length
|
55
|
+
@ffmpeg_iv_opts = opts[:ffmpeg_iv_opts]
|
56
|
+
@ffmpeg_ia_opts = opts[:ffmpeg_ia_opts]
|
57
|
+
@ffmpeg_out_opts = opts[:ffmpeg_out_opts]
|
58
|
+
@cmd = "ffmpeg -y -s #{@size} -r #{@fps} -f rawvideo -pix_fmt #{@pix_fmt[:string]} \
|
59
|
+
#{@ffmpeg_iv_opts} \
|
60
|
+
-i pipe:0 \
|
61
|
+
#{@ffmpeg_ia_opts} \
|
62
|
+
#{@ffmpeg_out_opts} #{@filename} &>/dev/null"
|
63
|
+
@data_avail = false
|
64
|
+
spawn
|
65
|
+
end
|
66
|
+
|
67
|
+
def write(data)
|
68
|
+
begin
|
69
|
+
written = @pipe_to_writer.syswrite(data)
|
70
|
+
rescue Errno::EPIPE
|
71
|
+
raise 'No writer running'
|
72
|
+
end
|
73
|
+
fail 'Not enough data is piped to writer' if written % @frame_length != 0
|
74
|
+
@pipe_to_writer.flush
|
75
|
+
@data_avail = true
|
76
|
+
end
|
77
|
+
|
78
|
+
def close
|
79
|
+
Process.kill('KILL', @pid)
|
80
|
+
Timeout.timeout(5) do
|
81
|
+
Process.waitpid(@pid)
|
82
|
+
end
|
83
|
+
rescue Timeout::Error
|
84
|
+
raise 'Writer hanged'
|
85
|
+
rescue Errno::ESRCH, Errno::ECHILD
|
86
|
+
raise 'No writer running'
|
87
|
+
end
|
88
|
+
|
89
|
+
def closed?
|
90
|
+
Timeout.timeout(0.05) do
|
91
|
+
Process.waitpid(@pid)
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
rescue Timeout::Error
|
95
|
+
return false
|
96
|
+
rescue Errno::ECHILD
|
97
|
+
return true
|
98
|
+
end
|
99
|
+
|
100
|
+
# @return [Integer] filesize. If no file created yet
|
101
|
+
# 0 is returned.
|
102
|
+
def size
|
103
|
+
s = File.size(@filename)
|
104
|
+
return s
|
105
|
+
rescue Errno::ENOENT
|
106
|
+
return 0
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def spawn
|
112
|
+
@pipe, @pipe_to_writer = IO.pipe
|
113
|
+
@pid = fork do
|
114
|
+
Signal.trap('INT') {}
|
115
|
+
@pipe_to_writer.close
|
116
|
+
@lock = Mutex.new
|
117
|
+
@written = 0
|
118
|
+
@output_ready = false
|
119
|
+
STDIN.reopen(@pipe)
|
120
|
+
routine
|
121
|
+
end
|
122
|
+
@pipe.close
|
123
|
+
end
|
124
|
+
|
125
|
+
def routine
|
126
|
+
@output = IO.popen(@cmd)
|
127
|
+
@output_ready = true
|
128
|
+
IO.select([STDIN])
|
129
|
+
@th = Thread.new(thread_func)
|
130
|
+
loop do
|
131
|
+
data = STDIN.read(@frame_length)
|
132
|
+
fail 'wrong length' if data.length != @frame_length
|
133
|
+
if @lock.try_lock
|
134
|
+
@framebuffer = data
|
135
|
+
@lock.unlock
|
136
|
+
else
|
137
|
+
@framebuffer2 = data
|
138
|
+
@cached = true
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def flush
|
144
|
+
return unless @output.closed? || @data_avail
|
145
|
+
@lock.synchronize do
|
146
|
+
if @cached
|
147
|
+
@framebuffer = @framebuffer2
|
148
|
+
@cached = false
|
149
|
+
end
|
150
|
+
@written += @output.syswrite @framebuffer
|
151
|
+
@output.flush
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def thread_func
|
156
|
+
adjust_sleep_time { flush }
|
157
|
+
i = 0
|
158
|
+
loop do
|
159
|
+
i += 1
|
160
|
+
if (i % 100) == 0
|
161
|
+
adjust_sleep_time { flush }
|
162
|
+
else
|
163
|
+
flush
|
164
|
+
end
|
165
|
+
sleep @sl
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def frame_length
|
170
|
+
bpp = @pix_fmt[:bpp] / 8
|
171
|
+
dim = @size.split('x').map(&:to_i).reduce(&:*)
|
172
|
+
bpp * dim
|
173
|
+
end
|
174
|
+
|
175
|
+
def adjust_sleep_time(&_block)
|
176
|
+
t1 = Time.now
|
177
|
+
yield
|
178
|
+
t2 = Time.now
|
179
|
+
@sl = 1.0 / @fps - (t2 - t1)
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_pix_fmt(fmt)
|
183
|
+
sym = fmt.to_s.upcase.prepend('PIX_FMT_').to_sym
|
184
|
+
fail "Unknown pixel format #{fmt}" unless VNCRec.const_defined? sym
|
185
|
+
VNCRec.const_get(sym)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.get_writer(filename, opts = {})
|
190
|
+
begin
|
191
|
+
File.write(filename, '')
|
192
|
+
rescue Errno::EACCES
|
193
|
+
raise 'Cannot create output file'
|
194
|
+
end
|
195
|
+
@path, @filename = File.split filename
|
196
|
+
@extname = File.extname filename
|
197
|
+
return RawVideo.new(@path + '/' + @filename) if @extname == '.raw'
|
198
|
+
if @extname.empty?
|
199
|
+
if @path != '/dev'
|
200
|
+
return RawVideo.new(@path + '/' + @filename + '.raw')
|
201
|
+
else
|
202
|
+
return FFmpeg.new(@path + '/' + @filename, opts)
|
203
|
+
end
|
204
|
+
else
|
205
|
+
return FFmpeg.new(@path + '/' + @filename, opts)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|