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.
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +1 -0
- data/README.md +114 -3
- data/Rakefile +27 -0
- data/examples/afs_test.rb +92 -0
- data/lib/ffi-libav.rb +1 -0
- data/lib/ffi/libav.i +1 -0
- data/lib/ffi/libav.rb +34 -1
- data/lib/libav/frame.rb +24 -12
- data/lib/libav/reader.rb +138 -9
- data/lib/libav/stream.rb +342 -64
- data/lib/libav/version.rb +1 -1
- data/spec/frame_spec.rb +274 -0
- data/spec/reader_spec.rb +22 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/stream_spec.rb +684 -0
- metadata +14 -10
data/.gitignore
CHANGED
data/.rspec
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -16,13 +16,13 @@ Or install it yourself as:
|
|
16
16
|
|
17
17
|
$ gem install ffi-libav
|
18
18
|
|
19
|
-
## Usage
|
19
|
+
## Basic Usage
|
20
20
|
|
21
21
|
```ruby
|
22
|
-
require 'ffi-
|
22
|
+
require 'ffi-libav'
|
23
23
|
|
24
24
|
# Allocate a reader for our video file
|
25
|
-
reader =
|
25
|
+
reader = Libav::Reader.new(ARGV[0])
|
26
26
|
|
27
27
|
# Dump the format information for the file
|
28
28
|
reader.dump_format
|
@@ -37,6 +37,117 @@ stream.each_frame do |frame|
|
|
37
37
|
end
|
38
38
|
```
|
39
39
|
|
40
|
+
## Advanced Usage
|
41
|
+
In addition to the basic decoding of video frames, `ffi-libav` supports
|
42
|
+
buffered decoding, for multi-threaded frame processing, stream rewind support,
|
43
|
+
and accurate frame seeking.
|
44
|
+
|
45
|
+
### Buffered Read
|
46
|
+
Buffered decoding for Stream::Video#each_frame allows frame decoding
|
47
|
+
to happen independently of frame processing by providing a series of
|
48
|
+
frames for the stream instance to use while decoding. As long as
|
49
|
+
frames are available to the stream, it will continue decoding. The
|
50
|
+
caller must call Stream::Video#release_frame or Frame#release to
|
51
|
+
notify the Stream that yielded frames are no longer being used in
|
52
|
+
another thread.
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Set up a work queue
|
56
|
+
frames = Queue.new
|
57
|
+
|
58
|
+
# Create 10 worker threads to perform some computationally expensive
|
59
|
+
# task on each frame, while not holding the GIL, think ffi opencv
|
60
|
+
running = true
|
61
|
+
threads = 10.times.map do
|
62
|
+
while running
|
63
|
+
|
64
|
+
# Grab a frame of the queue
|
65
|
+
frame = frames.pop
|
66
|
+
|
67
|
+
# Run something for a long time
|
68
|
+
process_for_a_really_long_time(frame)
|
69
|
+
|
70
|
+
# Release the frame so the stream can reuse it
|
71
|
+
frame.release
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# In the main thread, let's read frames and throw them on the work
|
76
|
+
# queue. #each_frame will block once it has decoded 20 frames, until one of
|
77
|
+
# the worker threads calls frame.release.
|
78
|
+
stream.each_frame(:buffer => 20) do |frame|
|
79
|
+
frames.push frame
|
80
|
+
end
|
81
|
+
```
|
82
|
+
|
83
|
+
### Rewind
|
84
|
+
A simplified rewind capability has been implemented in conjunction with the
|
85
|
+
buffered decoding. Libav::Reader will keep track of all frames released by the
|
86
|
+
stream, and if the caller calls Libav::Reader#rewind or Libav::Stream.rewind,
|
87
|
+
the Reader will rewind as many frame as possible, up to the number requested by
|
88
|
+
the caller.
|
89
|
+
|
90
|
+
NOTE: You can only rewind as many frames as have been buffered. So if you have
|
91
|
+
no buffer set up, you can only call `rewind(1)` to get #next_frame to emit the
|
92
|
+
current frame again.
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
# Decode frames, and buffer about 5 seconds worth (at 24 fps)
|
96
|
+
stream.each_frame(24 * 5) do |frame|
|
97
|
+
|
98
|
+
# Check the frame to see if it matches something
|
99
|
+
if frame_has_some_data?(frame)
|
100
|
+
puts "match at %d, rewound %d frames" % [frame.number, rewind(24 * 5)]
|
101
|
+
frame.release
|
102
|
+
break
|
103
|
+
end
|
104
|
+
|
105
|
+
# Release the frame for reuse. Note that releasing a frame DOES NOT mean
|
106
|
+
# that it will be immediately overwritten.
|
107
|
+
frame.release
|
108
|
+
end
|
109
|
+
|
110
|
+
# Now grab the frame from 5 seconds before we matched
|
111
|
+
frame = stream.next_frame
|
112
|
+
```
|
113
|
+
|
114
|
+
### Accurate Frame Seek (AFS)
|
115
|
+
Accurate Frame Seek (AFS) attempts to provide a reliable mechanism for seeking
|
116
|
+
accurately using libav. It works by reading through the file once and
|
117
|
+
generating an index of frame numbers, pts, and file offset for each key frame.
|
118
|
+
It then uses these locations when seeking accurately.
|
119
|
+
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
# open our video file
|
123
|
+
reader = Libav::Reader.new(ARGV[0], :afs => true)
|
124
|
+
|
125
|
+
# read all the frames; we do nothing with the data here, but this could be a
|
126
|
+
# first processing pass.
|
127
|
+
reader.default_stream.each_frame { }
|
128
|
+
|
129
|
+
# Save off our AFS data
|
130
|
+
afs = reader.afs
|
131
|
+
|
132
|
+
# Open the video file again, but supply the afs data this time
|
133
|
+
reader = Libav::Reader.new(ARGV[0], :afs => afs)
|
134
|
+
|
135
|
+
# Seek to a specific frame (by number)
|
136
|
+
begin
|
137
|
+
reader.default_stream.seek { :frame => 100 }
|
138
|
+
rescue Libav::Stream::FrameNotFound => e
|
139
|
+
# This can happen for the first few frames if the first frame is not a key
|
140
|
+
# frame (MPEG-TS).
|
141
|
+
puts "Unable to access frame 100"
|
142
|
+
end
|
143
|
+
|
144
|
+
# If the exception was not raised, the following call to #next_frame should
|
145
|
+
# give you the exact same data as you got for frame 100 on the first read.
|
146
|
+
frame = reader.default_stream.next_frame
|
147
|
+
```
|
148
|
+
|
149
|
+
A more indepth example can be found in `examples/afs_test.rb`.
|
150
|
+
|
40
151
|
## Contributing
|
41
152
|
|
42
153
|
1. Fork it ( http://github.com/<my-github-username>/ffi-libav/fork )
|
data/Rakefile
CHANGED
@@ -7,3 +7,30 @@ end
|
|
7
7
|
rule ".xml" => ".i" do |t|
|
8
8
|
sh "swig -I/usr/include -xml -o #{t.source.sub(/\.i$/, ".xml")} #{t.source}"
|
9
9
|
end
|
10
|
+
|
11
|
+
task(:test) do |t|
|
12
|
+
unless File.exists? "spec/data/big_buck_bunny_480p_surround-fix.avi"
|
13
|
+
puts <<EOM
|
14
|
+
|
15
|
+
=================================== NOTICE ====================================
|
16
|
+
Some of these tests require a video file to function. For testing purposes we
|
17
|
+
use the freely available, creative commons movie Big Buck Bunny. To get the
|
18
|
+
video file used for testing visit: http://bbb3d.renderfarming.net/download.html
|
19
|
+
Under the "Standard 2D" section, you can download the "480p HD (854x480)"
|
20
|
+
video; it should be named "big_buck_bunny_480p_surround-fix.avi".
|
21
|
+
|
22
|
+
Place this file, or a link to it at:
|
23
|
+
spec/data/big_buck_bunny_480p_surround-fix.avi
|
24
|
+
|
25
|
+
Once that file is in place, you will no longer see this notice when starting
|
26
|
+
the tests.
|
27
|
+
|
28
|
+
WITHOUT THIS VIDEO, SOME OF THE TESTS WILL FAIL.
|
29
|
+
===============================================================================
|
30
|
+
|
31
|
+
[ Press Enter to continue, ^C to abort ]
|
32
|
+
EOM
|
33
|
+
STDIN.getc
|
34
|
+
end
|
35
|
+
sh "rspec"
|
36
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Simple program that reads the first 1000 frames of a video file, then uses
|
4
|
+
# AFS to seek to each frame and verify that the seeked frame matches our
|
5
|
+
# original frame. Frame comparison done using crc.
|
6
|
+
|
7
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
8
|
+
require 'ffi-libav'
|
9
|
+
require 'zlib'
|
10
|
+
require 'pp'
|
11
|
+
|
12
|
+
# We use this to verify that we got back to the same frame
|
13
|
+
class Libav::Frame::Video
|
14
|
+
def crc
|
15
|
+
height.times.inject(0) do |crc,row|
|
16
|
+
Zlib::crc32(data[0].get_bytes(linesize[0] * row, width), crc)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_seconds(s)
|
22
|
+
s = s.to_i
|
23
|
+
h = s/3600
|
24
|
+
s -= h * 3600
|
25
|
+
m = s/60
|
26
|
+
s -= m * 60
|
27
|
+
"%02d:%02d:%02d" % [ h, m, s ]
|
28
|
+
end
|
29
|
+
|
30
|
+
GLYPHS = %w{ | / - \\ }
|
31
|
+
@first = nil
|
32
|
+
@last = nil
|
33
|
+
@calls = 0
|
34
|
+
def spin(frame)
|
35
|
+
now = Time.now()
|
36
|
+
@first ||= now
|
37
|
+
@last ||= now
|
38
|
+
|
39
|
+
@calls += 1
|
40
|
+
|
41
|
+
if (@last + 0.25 < now)
|
42
|
+
fps = @calls.to_f / (now - @first)
|
43
|
+
pos = frame.number / frame.stream.fps.to_f
|
44
|
+
printf("\r[%s] %0.03f fps, frame: %d, pts %d, pos: %s",
|
45
|
+
format_seconds(now - @first), fps, frame.number, frame.pts,
|
46
|
+
format_seconds(pos))
|
47
|
+
@last = now
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# open our video file
|
52
|
+
reader = Libav::Reader.new(ARGV[0], :afs => true)
|
53
|
+
reader.dump_format
|
54
|
+
|
55
|
+
# Read through the default stream until we get say 100 key frames in our
|
56
|
+
# accurate frame seek data.
|
57
|
+
stream = reader.default_stream
|
58
|
+
crc = []
|
59
|
+
stream.each_frame do |frame|
|
60
|
+
spin(frame)
|
61
|
+
crc[frame.number] = frame.crc
|
62
|
+
break if crc.size > 999
|
63
|
+
end
|
64
|
+
puts ""
|
65
|
+
|
66
|
+
# Save off the AFS data, and create a new reader with it
|
67
|
+
afs = reader.afs
|
68
|
+
reader = Libav::Reader.new(ARGV[0], :afs => afs)
|
69
|
+
stream = reader.default_stream
|
70
|
+
|
71
|
+
# Shuffle our frame numbers, and seek to each one.
|
72
|
+
crc.size.times.to_a.shuffle.each_with_index do |number, attempt|
|
73
|
+
STDOUT.write "%3d: frame %3d, " % [attempt, number]
|
74
|
+
STDOUT.flush
|
75
|
+
begin
|
76
|
+
# Perform the seek
|
77
|
+
stream.seek(:frame => number)
|
78
|
+
rescue Libav::Stream::FrameNotFound => e
|
79
|
+
# This can happen for some codeds
|
80
|
+
puts "warn -- inaccessable frame; #{e}"
|
81
|
+
next
|
82
|
+
end
|
83
|
+
|
84
|
+
# Verify the frame data matches our original pass
|
85
|
+
frame = stream.next_frame
|
86
|
+
frame_crc = frame.crc
|
87
|
+
if (crc[number] != frame_crc)
|
88
|
+
puts "FAILED -- got %08x, expected %08x" % [frame_crc, crc[number]]
|
89
|
+
else
|
90
|
+
puts "pass"
|
91
|
+
end
|
92
|
+
end
|
data/lib/ffi-libav.rb
CHANGED
data/lib/ffi/libav.i
CHANGED
data/lib/ffi/libav.rb
CHANGED
@@ -251,6 +251,11 @@ module FFI::Libav
|
|
251
251
|
self[:num].to_f / self[:den]
|
252
252
|
end
|
253
253
|
end
|
254
|
+
|
255
|
+
AV_TIME_BASE_Q = AVRational.new
|
256
|
+
AV_TIME_BASE_Q[:num] = 1
|
257
|
+
AV_TIME_BASE_Q[:den] = AV_TIME_BASE
|
258
|
+
|
254
259
|
# inline function av_cmp_q
|
255
260
|
# inline function av_q2d
|
256
261
|
attach_function :av_reduce, :av_reduce, [ :pointer, :pointer, :int64, :int64, :int64 ], :int
|
@@ -267,6 +272,35 @@ module FFI::Libav
|
|
267
272
|
attach_function :av_mallocz, :av_mallocz, [ :uint ], :pointer
|
268
273
|
attach_function :av_strdup, :av_strdup, [ :string ], :string
|
269
274
|
attach_function :av_freep, :av_freep, [ :pointer ], :void
|
275
|
+
M_E = 2.7182818284590452354
|
276
|
+
M_LN2 = 0.69314718055994530942
|
277
|
+
M_LN10 = 2.30258509299404568402
|
278
|
+
M_LOG2_10 = 3.32192809488736234787
|
279
|
+
M_PHI = 1.61803398874989484820
|
280
|
+
M_PI = 3.14159265358979323846
|
281
|
+
M_SQRT1_2 = 0.70710678118654752440
|
282
|
+
M_SQRT2 = 1.41421356237309504880
|
283
|
+
NAN = (0.0/0.0)
|
284
|
+
INFINITY = (1.0/0.0)
|
285
|
+
AV_ROUND_ZERO = 0
|
286
|
+
AV_ROUND_INF = 1
|
287
|
+
AV_ROUND_DOWN = 2
|
288
|
+
AV_ROUND_UP = 3
|
289
|
+
AV_ROUND_NEAR_INF = 5
|
290
|
+
AVRounding = enum :AVRounding, [
|
291
|
+
:zero, 0,
|
292
|
+
:inf, 1,
|
293
|
+
:down, 2,
|
294
|
+
:up, 3,
|
295
|
+
:near_inf, 5,
|
296
|
+
]
|
297
|
+
|
298
|
+
attach_function :av_gcd, :av_gcd, [ :int64, :int64 ], :int64
|
299
|
+
attach_function :av_rescale, :av_rescale, [ :int64, :int64, :int64 ], :int64
|
300
|
+
attach_function :av_rescale_rnd, :av_rescale_rnd, [ :int64, :int64, :int64, AVRounding ], :int64
|
301
|
+
attach_function :av_rescale_q, :av_rescale_q, [ :int64, AVRational.by_value, AVRational.by_value ], :int64
|
302
|
+
attach_function :av_compare_ts, :av_compare_ts, [ :int64, AVRational.by_value, :int64, AVRational.by_value ], :int
|
303
|
+
attach_function :av_compare_mod, :av_compare_mod, [ :uint64, :uint64, :uint64 ], :int64
|
270
304
|
|
271
305
|
|
272
306
|
ffi_lib [ "libavcodec.so.53", "libavcodec.53.dylib" ]
|
@@ -2292,7 +2326,6 @@ module FFI::Libav
|
|
2292
2326
|
end
|
2293
2327
|
class AVIOContext < FFI::Struct
|
2294
2328
|
layout(
|
2295
|
-
:av_class, :pointer,
|
2296
2329
|
:buffer, :pointer,
|
2297
2330
|
:buffer_size, :int,
|
2298
2331
|
:buf_ptr, :pointer,
|
data/lib/libav/frame.rb
CHANGED
@@ -3,10 +3,12 @@ require 'ffi/libav'
|
|
3
3
|
module Libav::Frame; end
|
4
4
|
|
5
5
|
class Libav::Frame::Video
|
6
|
+
extend Forwardable
|
6
7
|
include FFI::Libav
|
7
8
|
|
8
9
|
attr_reader :av_frame, :stream
|
9
|
-
attr_accessor :number
|
10
|
+
attr_accessor :number, :pos
|
11
|
+
def_delegator :@av_frame, :[], :[]
|
10
12
|
|
11
13
|
# Initialize a new frame, and optionally allocate memory for the frame data.
|
12
14
|
#
|
@@ -38,23 +40,28 @@ class Libav::Frame::Video
|
|
38
40
|
end
|
39
41
|
end
|
40
42
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
# define a few reader methods to read the underlying AVFrame
|
44
|
+
%w{ width height data linesize }.each do |field|
|
45
|
+
define_method(field) { @av_frame[field.to_sym] }
|
46
|
+
end
|
47
|
+
|
48
|
+
# define a few accessor methods to read/write fields in the AVFrame
|
49
|
+
%w{ pts key_frame }.each do |field|
|
50
|
+
define_method(field) { @av_frame[field.to_sym] }
|
51
|
+
define_method("#{field}=") { |v| @av_frame[field.to_sym] = v }
|
46
52
|
end
|
47
53
|
|
48
54
|
def key_frame?
|
49
|
-
key_frame
|
55
|
+
@av_frame[:key_frame] != 0
|
50
56
|
end
|
51
57
|
|
52
58
|
def pixel_format
|
53
|
-
|
59
|
+
@av_frame[:format]
|
54
60
|
end
|
55
61
|
|
56
|
-
|
57
|
-
|
62
|
+
# Get the presentation timestamp for this frame in fractional seconds
|
63
|
+
def timestamp
|
64
|
+
pts * @stream[:time_base].to_f
|
58
65
|
end
|
59
66
|
|
60
67
|
# Scale the frame
|
@@ -94,14 +101,19 @@ class Libav::Frame::Video
|
|
94
101
|
sws_freeContext(ctx) unless p[:scale_ctx]
|
95
102
|
|
96
103
|
# Let's copy a handful of attributes to the scaled frame
|
97
|
-
%w{ pts number key_frame }.each do |
|
98
|
-
out.send("#{
|
104
|
+
%w{ pts number pos key_frame }.each do |field|
|
105
|
+
out.send("#{field}=", send(field))
|
99
106
|
end
|
100
107
|
|
101
108
|
# Return our scaled frame
|
102
109
|
out
|
103
110
|
end
|
104
111
|
|
112
|
+
# Release frame back to buffered stream for re-use
|
113
|
+
def release
|
114
|
+
stream.release_frame(self)
|
115
|
+
end
|
116
|
+
|
105
117
|
private
|
106
118
|
|
107
119
|
# Returns Proc responsible for cleaning up the picture memory when it gets
|
data/lib/libav/reader.rb
CHANGED
@@ -2,15 +2,37 @@ require 'ffi/libav'
|
|
2
2
|
|
3
3
|
# Libav file reader
|
4
4
|
class Libav::Reader
|
5
|
+
extend Forwardable
|
5
6
|
include FFI::Libav
|
6
7
|
|
7
|
-
attr_reader :filename, :streams, :av_format_ctx
|
8
|
+
attr_reader :filename, :streams, :av_format_ctx, :afs
|
9
|
+
def_delegator :@av_format_ctx, :[], :[]
|
8
10
|
|
9
11
|
# Initialize a Reader for a specific file
|
10
12
|
#
|
11
13
|
# ==== Attributes
|
12
14
|
# * +filename+ - file to read
|
13
15
|
#
|
16
|
+
# ==== Options
|
17
|
+
# * +:afs+ - Enable and supply fast-frame-seek data (default: false)
|
18
|
+
#
|
19
|
+
# ==== Usage
|
20
|
+
# # open a file named 'video.ts' for reading
|
21
|
+
# r = Libav::Reader.new("video.ts")
|
22
|
+
#
|
23
|
+
# # open the same video and enable AFS
|
24
|
+
# r = Libav::Reader.new("video.ts", :afs => true)
|
25
|
+
# r.each_frame { |f| do_something(f) }
|
26
|
+
#
|
27
|
+
# # After reading any portion of the file, save the AFS data
|
28
|
+
# File.open("video_afs.yml", "w") do |file|
|
29
|
+
# file.write(r.afs.to_yaml)
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # open a video file and use AFS data from a previous run
|
33
|
+
# afs = Yaml.load_file("video_afs.yml")
|
34
|
+
# r = Libav::Reader.new("video.ts", :afs => afs)
|
35
|
+
#
|
14
36
|
def initialize(filename, p={})
|
15
37
|
@filename = filename or raise ArgumentError, "No filename"
|
16
38
|
|
@@ -24,6 +46,11 @@ class Libav::Reader
|
|
24
46
|
rc = avformat_find_stream_info(@av_format_ctx, nil)
|
25
47
|
raise RuntimeError, "av_find_stream_info() failed, rc=#{rc}" if rc < 0
|
26
48
|
|
49
|
+
# Fast frame seeking data; initialize it if @afs is enabled, but no data
|
50
|
+
# has been provided.
|
51
|
+
@afs = p[:afs]
|
52
|
+
@afs = Array.new(@av_format_ctx[:nb_streams]) {[]} if @afs == true
|
53
|
+
|
27
54
|
# Open all of our streams
|
28
55
|
initialize_streams(p)
|
29
56
|
|
@@ -34,6 +61,13 @@ class Libav::Reader
|
|
34
61
|
# Our packet for reading
|
35
62
|
@packet = AVPacket.new
|
36
63
|
av_init_packet(@packet)
|
64
|
+
|
65
|
+
# output frame buffer; used for #rewind
|
66
|
+
@output_frames = []
|
67
|
+
|
68
|
+
# This is our rewind queue, frames from @output_frame get stuck on here
|
69
|
+
# by #rewind, and shifted off by #each_frame
|
70
|
+
@rewound = []
|
37
71
|
end
|
38
72
|
|
39
73
|
# Call +av_dump_format+ to print out the format info for the video
|
@@ -51,6 +85,10 @@ class Libav::Reader
|
|
51
85
|
# ==== Argument
|
52
86
|
# * +block+ - block of code to call with the frame
|
53
87
|
#
|
88
|
+
# ==== Options
|
89
|
+
# * +:stream+ stream index or indexes to get frames for
|
90
|
+
# * +:buffer+ number of frames to buffer in each stream
|
91
|
+
#
|
54
92
|
# ==== Usage
|
55
93
|
# # Read each frame
|
56
94
|
# reader.each_frame do |frame|
|
@@ -59,14 +97,41 @@ class Libav::Reader
|
|
59
97
|
# my_show_frame(frame)
|
60
98
|
# end
|
61
99
|
#
|
62
|
-
def each_frame(&block)
|
100
|
+
def each_frame(p={}, &block)
|
63
101
|
raise ArgumentError, "No block provided" unless block_given?
|
64
102
|
|
103
|
+
# Patch up the :stream argument
|
104
|
+
p[:stream] ||= @streams.map { |s| s[:index] }
|
105
|
+
p[:stream] = [ p[:stream] ] unless p[:stream].is_a? Array
|
106
|
+
|
107
|
+
# Notify each stream of the requested buffer size
|
108
|
+
p[:stream].each { |i| @streams[i].buffer = p[:buffer] if p[:buffer]}
|
109
|
+
|
110
|
+
# If we have any frames on our @rewound list
|
111
|
+
while frame = @rewound.shift
|
112
|
+
@output_frames.push frame
|
113
|
+
next if p[:stream].include? frame.stream
|
114
|
+
break if yield(frame) == false
|
115
|
+
end
|
116
|
+
|
117
|
+
# Let's read frames
|
65
118
|
while av_read_frame(@av_format_ctx, @packet) >= 0
|
66
|
-
|
119
|
+
|
120
|
+
# Only call the decoder if the packet is from a stream we're interested
|
121
|
+
# in.
|
122
|
+
frame = nil
|
123
|
+
frame = @streams[@packet[:stream_index]].decode_frame(@packet) if
|
124
|
+
p[:stream].include? @packet[:stream_index]
|
125
|
+
|
126
|
+
# release our packet memory
|
67
127
|
av_free_packet(@packet)
|
68
|
-
|
69
|
-
|
128
|
+
|
129
|
+
next unless frame
|
130
|
+
|
131
|
+
# Before yielding the frame, add it to our output list for rewind
|
132
|
+
@output_frames.push frame
|
133
|
+
|
134
|
+
yield frame
|
70
135
|
end
|
71
136
|
end
|
72
137
|
|
@@ -80,6 +145,68 @@ class Libav::Reader
|
|
80
145
|
default_stream.seek(p)
|
81
146
|
end
|
82
147
|
|
148
|
+
# Rewind the reader
|
149
|
+
#
|
150
|
+
# This method will rewind the reader at most +count+ frames for the whole
|
151
|
+
# file, or for the +:stream+ provided. If not enough frames are available,
|
152
|
+
# rewind() will rewind as many as possible.
|
153
|
+
#
|
154
|
+
# After calling #rewind, #each_frame will yield frames
|
155
|
+
#
|
156
|
+
# Arguments:
|
157
|
+
# +count+ Number of frames to rewind
|
158
|
+
#
|
159
|
+
# Options:
|
160
|
+
# +:stream+ [optional] stream +count+ applies to
|
161
|
+
#
|
162
|
+
# Return:
|
163
|
+
# Number of frames for stream that were rewound
|
164
|
+
def rewind(count=nil, p={})
|
165
|
+
|
166
|
+
# Reduce our output frames based on any :stream provided
|
167
|
+
frames = @output_frames.select do |frame|
|
168
|
+
p[:stream].nil? or frame.stream == p[:stream]
|
169
|
+
end
|
170
|
+
|
171
|
+
# Cap the count at the number of frames we can rewind
|
172
|
+
count = frames.size if count.nil? or count > frames.size
|
173
|
+
|
174
|
+
# find the count-th frame from the end of our reduced frame array.
|
175
|
+
frame = frames[-1 * count.to_i]
|
176
|
+
|
177
|
+
# Find the index of that frame in the real output frames array
|
178
|
+
index = @output_frames.find_index(frame) or return 0
|
179
|
+
|
180
|
+
# Split the output frames into two arrays, those that have been yielded
|
181
|
+
# (output_frames), and those that have been rewound (rewound)
|
182
|
+
@rewound = (@output_frames.slice!(index, @output_frames.size) || []) +
|
183
|
+
@rewound
|
184
|
+
|
185
|
+
# Flush the buffer of every stream we rewound. We need to do this because
|
186
|
+
# other threads may have references to some of the frames we rewound. If
|
187
|
+
# it were possible to reverse Libav::Stream#release_frame, there would
|
188
|
+
# still be a problem if another thread released one of our rewound frames
|
189
|
+
# before it was yielded by #each_frame.
|
190
|
+
#
|
191
|
+
# The solution to this problem is to have each stream clear all of their
|
192
|
+
# frame buffers. The next time the stream decodes a frame, it will have to
|
193
|
+
# allocate a new buffer. Expensive, but this shouldn't happen very often.
|
194
|
+
@rewound.map { |f| f.stream }.uniq.each { |s| s.release_all_frames }
|
195
|
+
|
196
|
+
# Return the number of frames rewound for the requested stream. If no
|
197
|
+
# stream were requested, this would be the total number of frames rewound.
|
198
|
+
frames.size - frames.find_index(frame)
|
199
|
+
end
|
200
|
+
|
201
|
+
# This method is used to notify the Reader that the frame is about to be
|
202
|
+
# modified. This call is used to update the output buffer that is used
|
203
|
+
# by #rewind. The supplied frame, and all preceding frames are dropped from
|
204
|
+
# the output buffer. This reduces how far we can rewind.
|
205
|
+
def frame_dirty(frame)
|
206
|
+
index = @output_frames.index(frame) or return
|
207
|
+
@output_frames.shift(index + 1)
|
208
|
+
end
|
209
|
+
|
83
210
|
private
|
84
211
|
|
85
212
|
# Generate the Proc that is responsible for releasing our avcodec, avformat
|
@@ -93,6 +220,7 @@ class Libav::Reader
|
|
93
220
|
|
94
221
|
# Lookup and initialize the streams
|
95
222
|
def initialize_streams(p={})
|
223
|
+
|
96
224
|
@streams = @av_format_ctx[:nb_streams].times.map do |i|
|
97
225
|
av_stream = AVStream.new \
|
98
226
|
@av_format_ctx[:streams].get_pointer(i * FFI::Pointer::SIZE)
|
@@ -101,10 +229,11 @@ class Libav::Reader
|
|
101
229
|
case av_codec_ctx[:codec_type]
|
102
230
|
when :video
|
103
231
|
Libav::Stream::Video.new(:reader => self,
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
232
|
+
:av_stream => av_stream,
|
233
|
+
:pixel_format => p[:pixel_format],
|
234
|
+
:width => p[:width],
|
235
|
+
:height => p[:height],
|
236
|
+
:afs => @afs && @afs[av_stream[:index]])
|
108
237
|
else
|
109
238
|
Libav::Stream::Unsupported.new(:reader => self,
|
110
239
|
:av_stream => av_stream)
|