vncrec 1.0.1
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.
- 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
|