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 CHANGED
@@ -25,3 +25,6 @@ lib/ffi/*.xml
25
25
  # OSX artifacts
26
26
  .AppleDouble
27
27
  .DS_Store
28
+
29
+ # We don't want to include the test video
30
+ spec/data/big_buck_bunny_480p_surround-fix.avi
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format doc
2
+ --color
data/Gemfile CHANGED
@@ -7,4 +7,5 @@ group :development do
7
7
  gem 'pry'
8
8
  gem 'ffi-swig-generator',
9
9
  :git => 'https://github.com/ffi/ffi-swig-generator.git'
10
+ gem 'rspec'
10
11
  end
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-ffmpeg'
22
+ require 'ffi-libav'
23
23
 
24
24
  # Allocate a reader for our video file
25
- reader = FFmpeg::Reader.new(ARGV[0])
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
@@ -1,3 +1,4 @@
1
+ require 'forwardable'
1
2
  require 'ffi/libav'
2
3
  require 'libav/version'
3
4
  require 'libav/frame'
@@ -16,6 +16,7 @@ module FFI::Libav
16
16
  %include "libavutil/rational.h"
17
17
  %include "libavutil/mem.h"
18
18
  %include "libavutil/attributes.h"
19
+ %include "libavutil/mathematics.h"
19
20
 
20
21
  %{
21
22
 
@@ -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,
@@ -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
- # Throw together a bunch of helper methods for accessing the AVFrame
42
- # attributes.
43
- AVFrame.members.each do |member|
44
- define_method(member) { @av_frame[member] }
45
- define_method(member.to_s + "=") { |v| @av_frame[member] = v }
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 == 1
55
+ @av_frame[:key_frame] != 0
50
56
  end
51
57
 
52
58
  def pixel_format
53
- format.is_a?(Fixnum) ? PixelFormat[format] : format
59
+ @av_frame[:format]
54
60
  end
55
61
 
56
- def pixel_format=(v)
57
- send("format=", (v.is_a?(Fixnum) ? v : PixelFormat[v]))
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 |k|
98
- out.send("#{k}=", send(k))
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
@@ -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
- frame = @streams[@packet[:stream_index]].decode_frame(@packet)
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
- rc = frame ? yield(frame) : true
69
- break if rc == false
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
- :av_stream => av_stream,
105
- :pixel_format => p[:pixel_format],
106
- :width => p[:width],
107
- :height => p[:height])
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)