ffi-libav 0.1.0 → 0.2.0

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.
@@ -1,3 +1,3 @@
1
1
  module Libav
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,274 @@
1
+ require 'ffi-libav'
2
+ include Libav
3
+
4
+ describe Frame::Video, ".new" do
5
+ context 'when allocating with attributes' do
6
+ subject do
7
+ Frame::Video.new :width => 1200, :height => 100,
8
+ :pixel_format => :gray8
9
+ end
10
+ its(:width) { should eq(1200) }
11
+ its(:height) { should eq(100) }
12
+ its(:pixel_format) { should eq(:gray8) }
13
+ its("data.first.address") { should_not eq(0x0) }
14
+ end
15
+
16
+ context 'when not allocating, with attributes' do
17
+ subject do
18
+ Frame::Video.new :width => 1200, :height => 100,
19
+ :pixel_format => :gray8, :alloc => false
20
+ end
21
+ its(:width) { should eq(1200) }
22
+ its(:height) { should eq(100) }
23
+ its(:pixel_format) { should eq(:gray8) }
24
+ its("data.first.address") { should eq(0x0) }
25
+ end
26
+
27
+ context 'when allocating, with :stream' do
28
+ subject do
29
+ stream = Object.new
30
+ stream.stub(:width) { 1200 }
31
+ stream.stub(:height) { 100 }
32
+ stream.stub(:pixel_format) { :gray8 }
33
+ Frame::Video.new :stream => stream
34
+ end
35
+ its(:width) { should eq(1200) }
36
+ its(:height) { should eq(100) }
37
+ its(:pixel_format) { should eq(:gray8) }
38
+ its("data.first.address") { should_not eq(0x0) }
39
+ end
40
+ end
41
+
42
+ describe Frame::Video do
43
+ subject do
44
+ Frame::Video.new :width => 1200, :height => 100,
45
+ :pixel_format => :gray8
46
+ end
47
+
48
+ describe "#pts=" do
49
+ before { subject.pts = 0xFEEDFACE }
50
+ its(:pts) { should eq(0xFEEDFACE) }
51
+ it "should set av_frame[:pts]" do
52
+ expect(subject.av_frame[:pts]).to eq(0xFEEDFACE)
53
+ end
54
+ end
55
+
56
+ describe "#key_frame?" do
57
+ context 'when av_frame[:key_frame] == 1' do
58
+ before { subject.av_frame[:key_frame] = 1 }
59
+ its(:key_frame?) { should eq(true) }
60
+ end
61
+
62
+ context 'when av_frame[:key_frame] == 0' do
63
+ before { subject.av_frame[:key_frame] = 0 }
64
+ its(:key_frame?) { should eq(false) }
65
+ end
66
+
67
+ context 'when av_frame[:key_frame] == 0xBEEF' do
68
+ before { subject.key_frame = 0xBEEF }
69
+ its(:key_frame?) { should eq(true) }
70
+ end
71
+ end
72
+
73
+ describe "#key_frame=" do
74
+ context 'when frame.key_frame=(1)' do
75
+ before { subject.key_frame = 1 }
76
+ its(:key_frame?) { should eq(true) }
77
+ it "should set av_frame[:key_frame]" do
78
+ expect(subject.av_frame[:key_frame]).to eq(1)
79
+ end
80
+ end
81
+
82
+ context 'when frame.key_frame=(0)' do
83
+ before { subject.key_frame = 0 }
84
+ its(:key_frame?) { should eq(false) }
85
+ it "should set av_frame[:key_frame]" do
86
+ expect(subject.av_frame[:key_frame]).to eq(0)
87
+ end
88
+ end
89
+
90
+ context 'when frame.key_frame=(0xBEEF)' do
91
+ before { subject.key_frame = 0xBEEF }
92
+ its(:key_frame?) { should eq(true) }
93
+ it "should set av_frame[:key_frame]" do
94
+ expect(subject.av_frame[:key_frame]).to eq(0xBEEF)
95
+ end
96
+ end
97
+ end
98
+
99
+ describe "#pixel_format" do
100
+ before { subject.av_frame[:format] = :rgba }
101
+ its(:pixel_format) { should eq(:rgba) }
102
+ end
103
+
104
+ describe "#[]" do
105
+ let(:pts) { rand(0xFFFFFFFF) }
106
+ before { subject.av_frame[:pts] = pts }
107
+ it "should delegate to frame.av_frame" do
108
+ expect(subject[:pts]).to eq pts
109
+ end
110
+ end
111
+
112
+ describe "#av_frame" do
113
+ its('av_frame.class') { should be FFI::Libav::AVFrame }
114
+ end
115
+
116
+ describe "#timestamp" do
117
+ subject do
118
+ # fake a stream
119
+ stream = Object.new
120
+ stream.stub(:width) { 1200 }
121
+ stream.stub(:height) { 100 }
122
+ stream.stub(:pixel_format) { :gray8 }
123
+
124
+ # We stub out the [] method to return the :time_base for 24 FPS
125
+ stream.stub(:[]) do
126
+ f = FFI::Libav::AVRational.new
127
+ f[:num] = 1
128
+ f[:den] = 24
129
+ f
130
+ end
131
+
132
+ # Make a frame from this stream
133
+ frame = Frame::Video.new :stream => stream
134
+ frame.pts = 25
135
+ frame
136
+ end
137
+ its(:timestamp) { should eq(1/24.0 * 25) }
138
+ end
139
+
140
+ describe "#release" do
141
+ it "calls stream.release_frame(self)" do
142
+ stream = Object.new
143
+ stream.stub(:width) { 1200 }
144
+ stream.stub(:height) { 100 }
145
+ stream.stub(:pixel_format) { :gray8 }
146
+ stream.stub(:release_frame) { @released = true }
147
+ stream.stub(:released) { @released }
148
+ frame = Frame::Video.new :stream => stream
149
+
150
+ expect(stream.released).to be nil
151
+ frame.release()
152
+ expect(stream.released).to be true
153
+ end
154
+ end
155
+ end
156
+
157
+ describe Libav::Frame, "#scale" do
158
+ subject(:src_frame) do
159
+ # Craft a frame by hand that has an obvious pattern that we can verify
160
+ # after we scale.
161
+ frame = Frame::Video.new :width => 1000, :height => 1000,
162
+ :pixel_format => :gray8
163
+
164
+ # Set some fields in the source frame that we know should be copied to the
165
+ # scaled frame
166
+ frame.number = rand(0xEFFFFFFF)
167
+ frame.pts = rand(0xEFFFFFFF)
168
+ frame.key_frame = rand(2)
169
+
170
+ # We're dividing the entire image into a 4x4 grid
171
+ rows = 4
172
+ cols = 4
173
+ row_height = frame.height / rows
174
+ col_width = frame.width / cols
175
+
176
+ # calculate how many extra bytes we need to pad for the linesize for each
177
+ # row.
178
+ pad_length = frame.linesize[0] - frame.width
179
+
180
+ pattern = [ "\xff", "\x00", "\xff", "\x00" ].map { |c| c * col_width }
181
+
182
+ rows.times do |row|
183
+ # figure out the contents of a single line in our row
184
+ line = pattern.join("")
185
+ line += " " * pad_length
186
+
187
+ # Now construct the data for the entire row
188
+ buf = line * row_height
189
+
190
+ # Write our row out
191
+ frame.data[0].put_bytes(row * row_height * frame.linesize[0], buf)
192
+
193
+ # rotate our pattern (to make checker board)
194
+ pattern.rotate!
195
+ end
196
+
197
+ frame
198
+ end
199
+
200
+ context 'scale(:width => 100, :height => 100)' do
201
+ subject { src_frame.scale(:width => 100, :height => 100) }
202
+ its(:class) { should be(Libav::Frame::Video) }
203
+ its(:__id__) { should_not be src_frame.__id__ }
204
+ its(:width) { should be(100) }
205
+ its(:height) { should be(100) }
206
+ its(:pixel_format) { should be(src_frame.pixel_format) }
207
+ its(:pts) { should be src_frame.pts }
208
+ its(:number) { should be src_frame.number }
209
+ its(:key_frame?) { should be src_frame.key_frame? }
210
+ end
211
+
212
+ context 'scale(:pixel_format => rgba)' do
213
+ subject { src_frame.scale(:pixel_format => :rgba) }
214
+ its(:class) { should be(Libav::Frame::Video) }
215
+ its(:__id__) { should_not be src_frame.__id__ }
216
+ its(:width) { should be src_frame.width }
217
+ its(:height) { should be src_frame.height }
218
+ its(:pixel_format) { should be :rgba }
219
+ its(:pts) { should be src_frame.pts }
220
+ its(:number) { should be src_frame.number }
221
+ its(:key_frame?) { should be src_frame.key_frame? }
222
+ end
223
+
224
+ context 'scale(:output_frame => frame)' do
225
+ subject(:dst_frame) do
226
+ Frame::Video.new :pixel_format => :rgba, :width => 120, :height => 120
227
+ end
228
+ subject do
229
+ src_frame.scale(:output_frame => dst_frame)
230
+ end
231
+ its(:class) { should be(Libav::Frame::Video) }
232
+ its(:__id__) { should be dst_frame.__id__ }
233
+ its(:width) { should be 120 }
234
+ its(:height) { should be 120 }
235
+ its(:pixel_format) { should be :rgba }
236
+ its(:pts) { should be src_frame.pts }
237
+ its(:number) { should be src_frame.number }
238
+ its(:key_frame?) { should be src_frame.key_frame? }
239
+ end
240
+
241
+ context 'scale(:scale_ctx => ctx)' do
242
+ # There's really not much we can do here to verify that the context is
243
+ # being passed short of examining the data of the scaled frame. So we're
244
+ # going to create a 150x150 dest frame, but create the scaling context to
245
+ # be 100x100. We also set all the pixels in the dest frame to 0xBE. To
246
+ # verify that we're using the scaling context, we make sure that the tail
247
+ # of frame.data[0][100..149] match /\xBE+$/
248
+ subject(:dst_frame) do
249
+ frame = Frame::Video.new :width => 150, :height => 150,
250
+ :pixel_format => :gray8
251
+ frame.data[0].write_bytes("\xBE" * 150 * frame.linesize[0])
252
+ frame
253
+ end
254
+ subject do
255
+ ctx = FFI::Libav.sws_getContext(src_frame.width, src_frame.height,
256
+ src_frame.pixel_format,
257
+ 100, 100, :gray8,
258
+ FFI::Libav::SWS_BICUBIC,
259
+ nil, nil, nil)
260
+ src_frame.scale(:scale_ctx => ctx, :output_frame => dst_frame)
261
+ end
262
+ its(:class) { should be(Libav::Frame::Video) }
263
+ its(:__id__) { should_not be src_frame.__id__ }
264
+ its(:pts) { should be src_frame.pts }
265
+ its(:number) { should be src_frame.number }
266
+ its(:key_frame?) { should be src_frame.key_frame? }
267
+ it "contains a 100x100 image" do
268
+ linesize = subject.linesize[0]
269
+ expect(subject.data[0].get_bytes(100, 50)).to match(/\xBE+$/)
270
+ expect(subject.data[0].get_bytes(linesize * 99, 100)).to_not match(/^\xBE+$/)
271
+ expect(subject.data[0].get_bytes(linesize * 100, 100)).to match(/^\xBE+$/)
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,22 @@
1
+ require 'ffi-libav'
2
+ require 'yaml'
3
+
4
+ describe Libav::Stream::Video do
5
+ # This is the fast frame seek data for the test video. It is used for the
6
+ # AFS related contexts.
7
+ subject(:afs_data) do
8
+ YAML.load_file \
9
+ File.join(File.dirname(__FILE__), "data/big_buck_bunny-afs.yml")
10
+ end
11
+
12
+ subject(:test_video) do
13
+ File.join(File.dirname(__FILE__),
14
+ "data/big_buck_bunny_480p_surround-fix.avi")
15
+ end
16
+ subject(:reader) { Libav::Reader.new(test_video) }
17
+
18
+ context "with :afs => true" do
19
+ subject(:reader) { Libav::Reader.new(test_video, :afs => true) }
20
+ its(:afs) { should eq [[], []] }
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ RSpec.configure do |config|
2
+ config.expect_with :rspec do |c|
3
+ c.syntax = :expect
4
+ end
5
+ end
@@ -0,0 +1,684 @@
1
+ require 'ffi-libav'
2
+ require 'yaml'
3
+ require 'timeout'
4
+ require 'zlib'
5
+
6
+ # For testing
7
+ class Libav::Frame::Video
8
+ def crc
9
+ height.times.inject(0) do |crc,row|
10
+ Zlib::crc32(data[0].get_bytes(linesize[0] * row, width), crc)
11
+ end
12
+ end
13
+ end
14
+
15
+ describe Libav::Stream::Video do
16
+ TEST_VIDEO = File.join(File.dirname(__FILE__),
17
+ "data/big_buck_bunny_480p_surround-fix.avi")
18
+
19
+ subject(:test_video) { TEST_VIDEO }
20
+
21
+ # Generate a subset of afs data for our test video. We read the file until
22
+ # we see 50 entries in the afs data.
23
+ r = Libav::Reader.new(TEST_VIDEO, :afs => true)
24
+ r.each_frame { break if r.afs.size == 25 }
25
+ afs_data = r.afs
26
+ r = nil
27
+ subject(:afs_data) { afs_data }
28
+
29
+ # Set up our reader and stream subjects
30
+ subject(:reader) { Libav::Reader.new(test_video) }
31
+ subject(:stream) { reader.streams.find { |stream| stream.type == :video } }
32
+
33
+ its(:class) { should be Libav::Stream::Video }
34
+ its(:width) { should be 854 }
35
+ its(:height) { should be 480 }
36
+ its(:pixel_format) { should be :yuv420p }
37
+ its(:reader) { should be reader }
38
+ its(:fps) { should eq 24.0 }
39
+
40
+ describe "#each_frame" do
41
+ shared_examples "video frame yielder" do
42
+ it "yields Frame::Video objects" do
43
+ frame = nil
44
+ stream.each_frame { |f| frame = f; break }
45
+ expect(frame.class).to be Libav::Frame::Video
46
+ end
47
+
48
+ it "only yields frames from this stream" do
49
+ # This test is invalid right now because we only support video streams
50
+ # and our test video only contains a single video stream.
51
+ index = []
52
+ subject.each_frame(:buffer => buffer_size) do |f|
53
+ index << f.stream.index
54
+ stream.release_frame(f)
55
+ break if index.size == 250
56
+ end
57
+
58
+ expect(index.uniq).to eq [ subject.index ]
59
+ end
60
+
61
+ it "yields sequential frame numbers" do
62
+ frames = []
63
+ subject.each_frame(:buffer => buffer_size) do |f|
64
+ frames << f.number
65
+ stream.release_frame(f)
66
+ break if frames.size == 250
67
+ end
68
+
69
+ expect(frames).to eq (1..250).to_a
70
+ end
71
+
72
+ it "adjusts the output frame number for the frame number oafset" do
73
+ subject.instance_eval { @frame_oafset = 100_000 }
74
+ subject.each_frame(:buffer => buffer_size) do |frame|
75
+ expect(frame.number).to be 100_001
76
+ stream.release_frame(frame)
77
+ break
78
+ end
79
+ end
80
+
81
+ it "adjusts the output frame pts for the frame pts oafset" do
82
+ subject.instance_eval { @pts_oafset = 100_000_000 }
83
+ subject.each_frame(:buffer => buffer_size) do |frame|
84
+ # PTS of the first frame is 8
85
+ expect(frame.pts).to be 100_000_008
86
+ stream.release_frame(frame)
87
+ break
88
+ end
89
+ end
90
+
91
+ it "will yield frames with correct pts values" do
92
+ pts = []
93
+ stream.each_frame(:buffer => buffer_size) do |frame|
94
+ pts << frame.pts
95
+ stream.release_frame(frame)
96
+ break if pts.size == 10
97
+ end
98
+
99
+ # test video starts at pts 8, increments by 1
100
+ expect(pts).to eq (8..17).to_a
101
+ end
102
+
103
+ it "will yield frames with correct timestamps" do
104
+ stamps = []
105
+ stream.each_frame(:buffer => buffer_size) do |frame|
106
+ stamps << frame.timestamp
107
+ stream.release_frame(frame)
108
+ break if stamps.size == 10
109
+ end
110
+
111
+ expect(stamps).to eq (8..17).to_a.map { |pts| pts * (1/stream.fps) }
112
+ end
113
+
114
+ it "will yield a frame with data" do
115
+ # if one has data, assume they all have it
116
+ expect(frame.data.first.address).to_not eq 0
117
+ end
118
+
119
+ it "will yield a frame with key_frame set correctly" do
120
+ keys = []
121
+ stream.each_frame(:buffer => buffer_size) do |frame|
122
+ keys << frame.key_frame?
123
+ stream.release_frame(frame)
124
+ break if keys.size == 10
125
+ end
126
+
127
+ expect(keys).to eq [true, false, false, false, false, true, false,
128
+ false, true, true]
129
+ end
130
+
131
+ context "file is at EOF" do
132
+ before { subject.seek :byte => File.size(reader.filename) }
133
+ it "yields the block a nil frame" do
134
+ count = 0
135
+ subject.each_frame(:buffer => buffer_size) do |frame|
136
+ stream.release_frame(frame)
137
+ count += 1
138
+ expect(count).to be < 2
139
+ expect(count).to be nil
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ shared_examples "scaling video frame yielder" do
146
+ context "without scaling" do
147
+ let(:frame) do
148
+ stream.each_frame(:buffer => buffer_size) { |f| f.release; break f }
149
+ end
150
+ after(:each) { stream.release_frame(frame) }
151
+
152
+ it_should_behave_like "video frame yielder"
153
+
154
+ it "will preserve frame width" do
155
+ expect(frame.width).to be stream.av_codec_ctx[:width]
156
+ end
157
+
158
+ it "will preserve frame height" do
159
+ expect(frame.height).to be stream.av_codec_ctx[:height]
160
+ end
161
+
162
+ it "will preserve pixel format" do
163
+ expect(frame.pixel_format).to be stream.av_codec_ctx[:pix_fmt]
164
+ end
165
+ end
166
+
167
+ context "with scaling" do
168
+ before do
169
+ stream.width /= 2
170
+ stream.height /= 2
171
+ stream.pixel_format = :gray8
172
+ end
173
+
174
+ let(:frame) do
175
+ frame = nil
176
+ stream.each_frame { |f| frame = f; break }
177
+ frame
178
+ end
179
+
180
+ after(:each) { stream.release_frame(frame) }
181
+
182
+ it_should_behave_like "video frame yielder"
183
+
184
+ it "should scale frame width" do
185
+ expect(frame.width).to be stream.av_codec_ctx[:width] / 2
186
+ end
187
+
188
+ it "should scale frame height" do
189
+ expect(frame.height).to be stream.av_codec_ctx[:height] / 2
190
+ end
191
+
192
+ it "should change pixel format" do
193
+ expect(frame.pixel_format).to be :gray8
194
+ end
195
+ end
196
+ end
197
+
198
+ context "without buffer" do
199
+ let(:buffer_size) { 0 }
200
+ it_behaves_like "scaling video frame yielder"
201
+
202
+ shared_examples "unbuffered frame yielder" do
203
+ it "yields the same Frame object for each frame" do
204
+ frames = []
205
+ subject.each_frame(:buffer => buffer_size) do |frame|
206
+ frames << frame
207
+ break if frames.size == 100
208
+ end
209
+ expect(frames.uniq).to eq [frames.first]
210
+ end
211
+ end
212
+
213
+ context "without scaling" do
214
+ it_should_behave_like "unbuffered frame yielder"
215
+ end
216
+
217
+ context "with scaling" do
218
+ before do
219
+ stream.width /= 2
220
+ stream.height /= 2
221
+ stream.pixel_format = :gray8
222
+ end
223
+
224
+ it_should_behave_like "unbuffered frame yielder"
225
+ end
226
+
227
+ end
228
+
229
+ context "with buffer" do
230
+ let(:buffer_size) { 5 }
231
+
232
+ it_behaves_like "scaling video frame yielder"
233
+
234
+ shared_examples "buffered frame yielder" do
235
+ it "yields multiple Frame objects" do
236
+ frames = []
237
+ stream.each_frame(:buffer => buffer_size) do |frame|
238
+ frames << frame
239
+ stream.release_frame(frame)
240
+ break if frames.size == 100
241
+ end
242
+ expect(frames.uniq.size).to eq stream.buffer
243
+ end
244
+
245
+ it "blocks if the caller doesn't release frames" do
246
+ expect do
247
+ Timeout.timeout(2) do
248
+ count = 0
249
+ stream.each_frame(:buffer => buffer_size) do
250
+ count += 1
251
+ break if count > stream.buffer
252
+ end
253
+ end
254
+ end.to raise_error(Timeout::Error)
255
+ end
256
+
257
+ it "reuses released frames" do
258
+ frame = nil
259
+ count = 0
260
+
261
+ # Set up a scenario where we drain the frame buffer, and save off one
262
+ # of the frames we receive.
263
+ stream.each_frame(:buffer => buffer_size) do |f|
264
+ frame ||= f
265
+ count += 1
266
+ break if count == stream.buffer
267
+ end
268
+
269
+ # Release our saved frame, then pull another 100 frames, saving them to
270
+ # an array, and releasing them to be reused. The idea is that the next
271
+ # 100 frames should be the same object as our saved frame.
272
+ frames = []
273
+ stream.release_frame(frame)
274
+ stream.each_frame(:buffer => buffer_size) do |f|
275
+ frames << f
276
+ stream.release_frame(f)
277
+ break if frames.size == 100
278
+ end
279
+
280
+ expect(frames.uniq).to eq [frame]
281
+ end
282
+
283
+ it "preserves the buffer size across calls unless explicitly set" do
284
+ stream.each_frame(:buffer => 3) { break }
285
+ expect(stream.buffer).to eq 3
286
+ stream.each_frame { break }
287
+ expect(stream.buffer).to eq 3
288
+ end
289
+ end
290
+
291
+ context "without scaling" do
292
+ it_should_behave_like "buffered frame yielder"
293
+ end
294
+
295
+ context "with scaling" do
296
+ before do
297
+ stream.width /= 2
298
+ stream.height /= 2
299
+ stream.pixel_format = :gray8
300
+ end
301
+
302
+ it_should_behave_like "buffered frame yielder"
303
+ end
304
+ end
305
+ end
306
+
307
+ describe "#next_frame" do
308
+ shared_examples "frame reader" do
309
+ it "returns Frame::Video object" do
310
+ expect(stream.next_frame.class).to be Libav::Frame::Video
311
+ end
312
+ it "only returns frames for this stream" do
313
+ streams = 100.times.map { stream.next_frame.stream.index }.uniq
314
+ expect(streams).to eq [ stream.index ]
315
+ end
316
+ it "returns the next frame in the stream" do
317
+ last_frame = nil
318
+ stream.each_frame do |frame|
319
+ last_frame = frame.number and break if rand(50) == 0
320
+ end
321
+
322
+ expect(stream.next_frame.number).to eq last_frame + 1
323
+ end
324
+ end
325
+
326
+ context "without scaling" do
327
+ it_should_behave_like "frame reader"
328
+ end
329
+
330
+ context "with scaling" do
331
+ before do
332
+ stream.width /= 2
333
+ stream.height /= 2
334
+ stream.pixel_format = :gray8
335
+ end
336
+
337
+ it_should_behave_like "frame reader"
338
+ end
339
+ end
340
+
341
+ describe "#skip_frames" do
342
+ before { subject.skip_frames(100) }
343
+ its("next_frame.number") { should eq 101 }
344
+ end
345
+
346
+ describe "#rewind" do
347
+ it "causes #each_frame to yield previously yielded frames" do
348
+ first = []
349
+ stream.each_frame(:buffer => 5) do |frame|
350
+ first << frame.number
351
+ frame.release
352
+ break if first.size == 5
353
+ end
354
+
355
+ stream.rewind(5)
356
+ second = []
357
+ stream.each_frame do |frame|
358
+ second << frame.number
359
+ frame.release
360
+ break if second.size == 5
361
+ end
362
+
363
+ expect(first).to eq second
364
+ end
365
+
366
+ it "returns 0 when no frames have been decoded" do
367
+ # XXX should be in reader_spec
368
+ expect(stream.rewind).to eq 0
369
+ end
370
+
371
+ it "returns 0 when count is 0" do
372
+ # XXX should be in reader_spec
373
+ expect(stream.rewind(0)).to eq 0
374
+ end
375
+
376
+ it "rewinds as many frames as possible when count exceeds buffer" do
377
+ first = []
378
+ stream.each_frame(:buffer => 5) do |frame|
379
+ first << frame.number
380
+ frame.release
381
+ break if first.size == 10
382
+ end
383
+
384
+ expect(stream.rewind(10)).to eq 5
385
+ expect(stream.each_frame { |f| break f }.number).to be 6
386
+ end
387
+
388
+ it "removes rewound frames from decoding queue" do
389
+ frames = []
390
+ stream.each_frame(:buffer => 5) do |frame|
391
+ frames << frame.object_id
392
+ frame.release
393
+ break if frames.size == 5
394
+ end
395
+ expect(frames.uniq.size).to eq 5
396
+
397
+ # rewind the last five frames
398
+ stream.rewind(5)
399
+
400
+ # skip the next five frames
401
+ stream.skip_frames(5)
402
+
403
+ # the sixth frame should be a new object id
404
+ expect(frames).to_not include(stream.next_frame.object_id)
405
+ end
406
+
407
+ it "can be called multiple times" do
408
+ frames = []
409
+ stream.each_frame(:buffer => 5) do |frame|
410
+ frames << frame.number
411
+ frame.release
412
+ break if frames.size == 5
413
+ end
414
+
415
+ stream.rewind(1)
416
+ expect(stream.next_frame.number).to eq frames[4]
417
+
418
+ stream.rewind(2)
419
+ expect(stream.next_frame.number).to eq frames[3]
420
+
421
+ stream.rewind(2)
422
+ expect(stream.next_frame.number).to eq frames[2]
423
+
424
+ stream.rewind(2)
425
+ expect(stream.next_frame.number).to eq frames[1]
426
+
427
+ stream.rewind(2)
428
+ expect(stream.next_frame.number).to eq frames[0]
429
+ expect(stream.next_frame.number).to eq frames[1]
430
+ expect(stream.next_frame.number).to eq frames[2]
431
+ expect(stream.next_frame.number).to eq frames[3]
432
+ expect(stream.next_frame.number).to eq frames[4]
433
+ expect(stream.next_frame.number).to eq frames[4] + 1
434
+ end
435
+
436
+ it "does rewind over re-used frames" do
437
+ first = stream.next_frame
438
+ second = stream.next_frame
439
+ expect(stream.next_frame.number).to eq second.number
440
+ end
441
+ end
442
+
443
+ describe "#index" do
444
+ its("index") { should be subject.av_stream[:index] }
445
+ end
446
+
447
+ describe "#[]" do
448
+ let(:num) { rand(0xFFFFFFFF) }
449
+ before { subject.av_stream[:nb_frames] = num }
450
+ it "delegates to stream.av_stream" do
451
+ expect(subject[:nb_frames]).to eq num
452
+ end
453
+ end
454
+
455
+ describe "#discard" do
456
+ before { subject.av_stream[:discard] = :nonkey }
457
+ its(:discard) { should be :nonkey }
458
+ end
459
+
460
+ describe "#type" do
461
+ its(:type) { should be :video }
462
+ its(:type) { should be subject.av_stream[:codec][:codec_type] }
463
+ end
464
+
465
+ context "with AFS disabled" do
466
+ describe "#each_frame" do
467
+ it "does not update AFS data" do
468
+ count = 0
469
+ subject.each_frame do |f|
470
+ count += 1 if f.key_frame
471
+ break if count == 20
472
+ end
473
+ expect(reader.afs).to be nil
474
+ end
475
+ end
476
+
477
+ describe "#seek" do
478
+ context "by pts" do
479
+ before { subject.seek :pts => 30 * subject.fps }
480
+ its("next_frame.pts") { should be >= 30 * subject.fps }
481
+ its("next_frame.key_frame?") { should be true }
482
+ end
483
+ context "non-key frames" do
484
+ before { subject.seek :pts => 30 * subject.fps, :any => true }
485
+ its("next_frame.pts") { should be >= 30 * subject.fps }
486
+ its("next_frame.key_frame?") { should be false }
487
+ end
488
+ context "by byte" do
489
+ before { subject.seek :byte => 100_000_000 }
490
+ # The codec used in our test video does not correctly adjust the pts
491
+ # following a seek.
492
+ # its("next_frame.pts") { should be ??? }
493
+ its("next_frame.pos") { should be >= 100_000_000 }
494
+
495
+ # Also, seeking by byte does not care about key frames.
496
+ # its("next_frame.key_frame?") { should be true }
497
+ end
498
+ end
499
+ end
500
+
501
+ context "with AFS enabled" do
502
+ subject(:reader) { Libav::Reader.new(test_video, :afs => true) }
503
+ subject(:stream) { reader.streams.find { |stream| stream.type == :video } }
504
+
505
+ its(:afs) { should be_empty }
506
+
507
+ describe "#each_frame" do
508
+ context "before seeking" do
509
+ it "updates the afs data for the last frame read" do
510
+ count = 0
511
+ stream.each_frame do |frame|
512
+ count += 1
513
+ expect(stream.afs.last[0]).to eq frame.number
514
+ expect(stream.afs.last[1]).to eq frame.pts
515
+ expect(stream.afs.last[2]).to eq frame.pos
516
+ expect(stream.afs.last[3]).to eq frame.key_frame?
517
+ break if count == 200
518
+ end
519
+ end
520
+
521
+ it "only preserves afs data for key frames" do
522
+ count = 0
523
+ stream.each_frame do |frame|
524
+ next unless frame.key_frame?
525
+ count += 1
526
+ break if count == 20
527
+ end
528
+ expect(stream.afs.size).to eq count
529
+ end
530
+ end
531
+
532
+ context "after seeking" do
533
+ before { stream.seek(:byte => 100_000) }
534
+
535
+ it "does not update afs data for key frames read" do
536
+ count = 0
537
+ stream.each_frame { |f| count += 1 if f.key_frame?; count < 20 }
538
+ expect(stream.afs).to eq []
539
+ end
540
+ it "does not update last frame data" do
541
+ count = 0
542
+ stream.each_frame do |frame|
543
+ count += 1
544
+ break if count == 100
545
+ end
546
+ expect(stream.afs).to be_empty
547
+ end
548
+ end
549
+ end
550
+ end
551
+
552
+ context "with AFS data provided" do
553
+ subject(:reader) { Libav::Reader.new(test_video, :afs => afs_data) }
554
+ subject(:stream) { reader.streams.find { |stream| stream.type == :video } }
555
+
556
+ its(:afs) { should be afs_data[stream.index] }
557
+
558
+ describe "#each_frame" do
559
+ it "does not update afs for known frames" do
560
+ count = 0
561
+ stream.each_frame { |f| break if (count += 1) == 100 }
562
+ expect(stream.afs).to eq afs_data[stream.index]
563
+ end
564
+
565
+ it "updates afs data for frames beyond existing afs data" do
566
+ stream.seek(:frame => stream.afs[-1][0])
567
+
568
+ count = 0
569
+ stream.each_frame do |frame|
570
+ count += 1
571
+ expect(stream.afs.last[0]).to eq frame.number
572
+ expect(stream.afs.last[1]).to eq frame.pts
573
+ expect(stream.afs.last[2]).to eq frame.pos
574
+ expect(stream.afs.last[3]).to eq frame.key_frame?
575
+ break if count == 200
576
+ end
577
+ end
578
+ end
579
+
580
+ describe "#seek" do
581
+ shared_examples "frame accurate seek" do
582
+ context "within afs data" do
583
+ it "can find a key frame by pts" do
584
+ stream.seek(:pts => 22)
585
+ frame = stream.next_frame
586
+ expect(frame.number).to eq 15
587
+ expect(frame.pts).to eq 22
588
+ expect(frame.pos).to eq 157836
589
+ expect(frame.crc.to_s(16)).to eq "6c56942e"
590
+ end
591
+
592
+ it "can find a non-key frame by pts" do
593
+ stream.seek(:pts => 54)
594
+ frame = stream.next_frame
595
+ expect(frame.number).to eq 47
596
+ expect(frame.pts).to eq 54
597
+ expect(frame.pos).to eq 658930
598
+ expect(frame.crc.to_s(16)).to eq "496919b1"
599
+ end
600
+
601
+ it "can find a key frame by byte" do
602
+ # [270, 277, 4942848, 1, "0xf8ea5eb2"],
603
+ stream.seek(:byte => 4942848)
604
+ frame = stream.next_frame
605
+ expect(frame.number).to eq 270
606
+ expect(frame.pts).to eq 277
607
+ expect(frame.pos).to eq 4942848
608
+ expect(frame.crc.to_s(16)).to eq "f8ea5eb2"
609
+ end
610
+
611
+ it "can find a non-key frame by byte" do
612
+ # [350, 357, 6901944, 0, "0x3ea2c2ff"]]
613
+ stream.seek(:byte => 6901944)
614
+ frame = stream.next_frame
615
+ expect(frame.number).to eq 350
616
+ expect(frame.pts).to eq 357
617
+ expect(frame.pos).to eq 6901944
618
+ expect(frame.crc.to_s(16)).to eq "3ea2c2ff"
619
+ end
620
+
621
+ it "finds key frame by frame number" do
622
+ stream.seek(:frame => 20)
623
+ frame = stream.next_frame
624
+ expect(frame.number).to eq 20
625
+ expect(frame.pts).to eq 27
626
+ expect(frame.pos).to eq 213188
627
+ expect(frame.crc.to_s(16)).to eq "85190ba9"
628
+ end
629
+
630
+ it "can find a non-key frame by frame number" do
631
+ stream.seek(:frame => 21)
632
+ frame = stream.next_frame
633
+ expect(frame.number).to eq 21
634
+ expect(frame.pts).to eq 28
635
+ expect(frame.pos).to eq 224996
636
+ end
637
+
638
+ it "can reach the first key frame" do
639
+ stream.seek :frame => 1
640
+ frame = stream.next_frame
641
+ expect(frame.number).to eq 1
642
+ expect(frame.pts).to eq 8
643
+ expect(frame.pos).to eq 46860
644
+ expect(frame.crc.to_s(16)).to eq "5aaa5831"
645
+ end
646
+ end
647
+
648
+ context "outside of afs data" do
649
+ # Seek past the end of our afs data
650
+ before { stream.seek :frame => stream.afs.last[0] + 1 }
651
+
652
+ it "disables afs updates" do
653
+ expect(stream.instance_eval { @update_afs }).to be false
654
+ end
655
+
656
+ it "re-enables afs updates" do
657
+ stream.seek :frame => 100
658
+ expect(stream.instance_eval { @update_afs }).to be true
659
+ end
660
+ end
661
+ end
662
+
663
+ context "before reading frames" do
664
+ it_should_behave_like "frame accurate seek"
665
+ end
666
+ context "after seeking to the end" do
667
+ before { stream.seek :byte => File.size(reader.filename) }
668
+ it_should_behave_like "frame accurate seek"
669
+ end
670
+ context "after reading 100 frames" do
671
+ before { c = 0; stream.each_frame { break if (c += 1) == 100 } }
672
+ it_should_behave_like "frame accurate seek"
673
+ end
674
+ context "after seeking to the middle, and reading 100 frames" do
675
+ before do
676
+ stream.seek :byte => File.size(reader.filename)/2
677
+ count = 0
678
+ stream.each_frame { break if (count += 1) == 100 }
679
+ end
680
+ it_should_behave_like "frame accurate seek"
681
+ end
682
+ end
683
+ end
684
+ end