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,10 +1,18 @@
1
1
  require 'ffi/libav'
2
+ require 'thread'
2
3
 
3
4
  # Generic Stream class. Most of the logic resides in Libav::Stream::Video.
4
5
  module Libav::Stream
6
+ extend Forwardable
5
7
  include FFI::Libav
6
8
 
7
- attr_reader :reader, :av_stream, :av_codec_ctx
9
+ # Exception raised by Stream#seek when an AFS seek is unable to find the
10
+ # target frame. This can happen for some codecs such as MPEG-TS when
11
+ # attempting to access the earliest frames.
12
+ class FrameNotFound < Exception; end
13
+
14
+ attr_reader :reader, :av_stream, :av_codec_ctx, :buffer
15
+ def_delegator :@av_stream, :[], :[]
8
16
 
9
17
  def initialize(p={})
10
18
  @reader = p[:reader] or raise ArgumentError, "no :reader"
@@ -34,28 +42,29 @@ module Libav::Stream
34
42
  @av_stream[:index]
35
43
  end
36
44
 
37
- def decode_frame(packet)
38
- return false
39
- raise NotImplementedError, "decode_frame() not defined for #{self.class}"
40
- end
41
-
42
45
  # Loop through each frame of this stream
43
- def each_frame(&block)
44
- @reader.each_frame { |frame| yield frame if frame.stream == self }
46
+ #
47
+ # Arguments:
48
+ # [:buffer] Number of frames to buffer
49
+ #
50
+ # Note that when using the :buffer argument, the caller MUST call
51
+ # Frame#release when it is done processing a frame.
52
+ #
53
+ def each_frame(opt={}, &block)
54
+ @reader.each_frame(opt.merge({ :stream => index }), &block)
45
55
  end
46
56
 
47
57
  # Get the next frame in the stream
48
58
  def next_frame
49
- frame = nil
50
- each_frame { |f| frame = f; break }
51
- frame
59
+ each_frame { |f| break f }
52
60
  end
53
61
 
54
62
  # Skip some +n+ frames in the stream
55
63
  def skip_frames(n)
64
+ # XXX not sure if this is true
56
65
  raise RuntimeError, "Cannot skip frames when discarding all frames" if
57
66
  discard == :all
58
- each_frame { |f| n -= 1 != 0 }
67
+ each_frame { |f| f.release; n -= 1; break if n == 0}
59
68
  end
60
69
 
61
70
  # Seek to a specific location within the stream; the location can be either
@@ -63,32 +72,158 @@ module Libav::Stream
63
72
  #
64
73
  # Arguments:
65
74
  # [:pts] PTS location
66
- # [:pos] Byte location
75
+ # [:byte] Byte location
67
76
  # [:backward] Seek backward
68
77
  # [:any] Seek to non-key frames
69
78
  #
79
+ # Examples:
80
+ # seek :frame => 3
81
+ # seek :pts => 90218390
82
+ # seek :pos => 0
83
+ #
84
+ # Last index of afs data should contain last frame read.
70
85
  def seek(p={})
71
- p = { :pts => p } unless p.is_a? Hash
72
86
 
73
- raise ArgumentError, ":pts and :pos are mutually exclusive" \
74
- if p[:pts] and p[:pos]
87
+ raise ArgumentError, ":pts, :frame, and :byte are mutually exclusive" if
88
+ ([:byte, :frame, :pts] & p.keys).size != 1
75
89
 
76
- pos = p[:pts] || p[:pos]
77
- flags = 0
78
- flags |= AVSEEK_FLAG_BYTE if p[:pos]
79
- flags |= AVSEEK_FLAG_BACKWARD if p[:backward]
90
+ # Default seek arguments
91
+ flags = p[:backward] ? AVSEEK_FLAG_BACKWARD : 0
92
+ flags |= AVSEEK_FLAG_BYTE if p[:byte]
93
+ flags |= AVSEEK_FLAG_FRAME if p[:frame]
80
94
  flags |= AVSEEK_FLAG_ANY if p[:any]
95
+ seek_args = [[p[:pts] || p[:frame] || p[:byte], flags]]
96
+
97
+ # If we have afs data, and our target frame is within the data, replace
98
+ # seek_args with an array of arguments for seeking to each key frame
99
+ # preceding our target frame, in reverse-chronological order. The idea is
100
+ # that sometimes libav seek puts us someplace strange. If we start at the
101
+ # closest key frame to our target frame, and the work backwards in the
102
+ # stream, libav will eventually put us in a place where we can read to the
103
+ # target frame.
104
+ if @afs and !@afs.empty? and
105
+ (p[:frame] && p[:frame] <= @afs.last[0] or
106
+ p[:pts] && p[:pts] <= @afs.last[1] or
107
+ p[:byte] && p[:byte] <= @afs.last[2])
108
+
109
+ # Note that we set the flags to AVSEEK_FLAG_BACKWARD for each of our arg
110
+ # sets. This is just because by observation the BACKWARD flag seems to
111
+ # give us better results regardless of the direction of our seek.
112
+ seek_args = @afs.select do |data|
113
+ p[:frame] && data[0] < p[:frame] or
114
+ p[:pts] && data[1] < p[:pts] or
115
+ p[:byte] && data[2] && data[2] < p[:byte]
116
+ end.map { |d| [ d[1], AVSEEK_FLAG_BACKWARD, true ] }.reverse
117
+
118
+ # Throw the seek 0 into the end of the list as a fall back
119
+ seek_args.push [0, AVSEEK_FLAG_BACKWARD, true]
120
+ end
121
+
122
+ # Disable afs updating because we're about to seek. If the seek ends up
123
+ # within our afs data, it will be re-enabled.
124
+ @update_afs = false
125
+
126
+ # We will fill this frame in the next loop, and use it after the loop is
127
+ # complete.
128
+ frame = nil
129
+
130
+ # Loop through each set of arguments provided. Seek to the timestamp in
131
+ # the arguments and then verify that we haven't gone past our target.
132
+ seek_args.each do |ts, flags, afs|
133
+
134
+ # Flush the codec so we don't get buffered frames after the seek
135
+ avcodec_flush_buffers(@av_codec_ctx)
136
+
137
+ # Kick off our seek
138
+ rc = avformat_seek_file(@reader.av_format_ctx, @av_stream[:index],
139
+ ts, ts, ts, flags)
140
+ raise RuntimeError, "avformat_seek_file(#{ts}, #{flags.to_s(16)})" +
141
+ " failed, #{rc}" if rc < 0
142
+
143
+ # If we performed an afs seek, we need to enable afs data updating, and
144
+ # if not we need to disable it.
145
+ @update_afs = (afs == true)
146
+
147
+ # We also need to clear the frame and pts oafsets. If this is a afs
148
+ # seek, these will be re-set to their new values later.
149
+ @frame_oafset = 0
150
+ @pts_oafset = 0
151
+
152
+ # If this wasn't an afs seek, we are done here.
153
+ return true unless afs
154
+
155
+ # Grab the next frame, and see if it precedes, or is, our target frame.
156
+ # If no frame is returned, we hit EOF, so try seeking a little earlier.
157
+ frame = next_frame or next
158
+ frame.release
159
+
160
+ # Although the code is only needed in this loop when we're seeking by
161
+ # :frame, we're going to adjust our frame oafset here. It's a little
162
+ # slower, but less complicated overall.
163
+ #
164
+ # Find the afs entry for this frame (it's guaranteed to be a key
165
+ # frame), and use that to adjust the number of the frame we just read.
166
+ #
167
+ # If we're unable to find a match in the afs data, then we've gone past
168
+ # the end of the afs data, and we should try another seek timestamp.
169
+ seek_afs = @afs.find { |f| f[1] == frame.pts } or next
170
+ raise RuntimeError, "afs data mismatch: afs #{seek_afs}, " +
171
+ "frame [#{frame.number}, #{frame.pts}, #{frame.pos}]" if
172
+ frame.pos != seek_afs[2]
173
+
174
+ # Adjust our frame oafset, and use that to adjust our frame number
175
+ @frame_oafset = -1 * (frame.number - seek_afs[0])
176
+ frame.number += @frame_oafset
177
+
178
+ # If this frame precedes or is our target frame, then the seek was
179
+ # successful, and we can break out of this loop.
180
+ break if p[:pts] && p[:pts] >= frame.pts or
181
+ p[:frame] && p[:frame] >= frame.number or
182
+ p[:byte] && p[:byte] >= frame.pos
183
+
184
+ # This frame was after our target frame, so ignore it.
185
+ frame = nil
186
+ end
187
+
188
+ # If we went through all the seek_args without finding a single frame
189
+ # before our target frame, throw an exception.
190
+ unless frame
191
+ mode = (p.keys & [:pts, :frame, :byte]).first
192
+ raise FrameNotFound, "Unable to find frame {%s: %d}" % [mode, p[mode]]
193
+ end
194
+
195
+ # Rewind the frame so we can access it in the next loop
196
+ rewind(1)
197
+
198
+ # Also mark afs as updating because we're within the known afs data region.
199
+ @update_afs = true
200
+
201
+ # Now that we have corrected oafsets, we need to read the next few frames
202
+ # until we encounter the target frame or a frame after it (possible for
203
+ # pts values that are slightly off).
204
+ each_frame do |frame|
205
+ frame.release
206
+ break if p[:pts] && frame.pts >= p[:pts] or
207
+ p[:frame] && frame.number >= p[:frame] or
208
+ p[:byte] && frame.pos >= p[:byte]
209
+ end
210
+
211
+ # Alright, let's rewind by one frame so the next call to #each_frame will
212
+ # yield the frame they requested.
213
+ rewind(1)
214
+
215
+ return true
216
+ end
81
217
 
82
- rc = av_seek_frame(@reader.av_format_ctx, @av_stream[:index], pos, flags)
83
- raise RuntimeError, "av_seek_frame() failed, %d" % rc if rc < 0
84
- true
218
+ def format_afs(a)
219
+ "{frame: %d, pts: %d, pos: %d}" % [*a]
85
220
  end
86
221
  end
87
222
 
88
223
  class Libav::Stream::Video
89
224
  include Libav::Stream
90
225
 
91
- attr_reader :raw_frame, :width, :height, :pixel_format, :reader
226
+ attr_reader :width, :height, :pixel_format, :reader, :afs
92
227
 
93
228
  def initialize(p={})
94
229
  super(p)
@@ -97,14 +232,23 @@ class Libav::Stream::Video
97
232
  @width = p[:widht] || @av_codec_ctx[:width]
98
233
  @height = p[:height] || @av_codec_ctx[:height]
99
234
  @pixel_format = p[:pixel_format] || @av_codec_ctx[:pix_fmt]
100
- init_scaling
101
235
 
102
- # Our frame structure for decoding the frame
103
- @raw_frame = Libav::Frame::Video.new :stream => self, :alloc => false,
104
- :width => @av_codec_ctx[:width],
105
- :height => @av_codec_ctx[:height],
106
- :pixel_format =>
107
- @av_codec_ctx[:pix_fmt]
236
+ # Our stream index info
237
+ @afs = p[:afs]
238
+ @update_afs = @afs.nil? == false
239
+
240
+ # Our array and queues for raw frames and scaled frames
241
+ @raw_frames = []
242
+ @raw_queue = Queue.new
243
+ @scaled_frames = []
244
+ @scaled_queue = Queue.new
245
+
246
+ # Number of frames to buffer (default is disabled, 0)
247
+ @buffer = 0
248
+
249
+ # When this is set to true when all raw and scaled frames have been set up
250
+ # by setup().
251
+ @decode_ready = false
108
252
 
109
253
  # Pointer used to denote that decode frame was successful
110
254
  @frame_finished = FFI::MemoryPointer.new :int
@@ -118,7 +262,8 @@ class Libav::Stream::Video
118
262
  # address. Instead of allocating and freeing repeatedly, we're going to
119
263
  # alloc it once now and reuse it for each decoded frame.
120
264
  @last_dts = nil
121
- @opaque = FFI::MemoryPointer.new :uint64
265
+ @last_pos = nil
266
+ @opaque = FFI::MemoryPointer.new :uint64, 2
122
267
 
123
268
  @av_codec_ctx[:get_buffer] = \
124
269
  FFI::Function.new(:int, [AVCodecContext.ptr, AVFrame.ptr]) do |ctx,frame|
@@ -127,77 +272,202 @@ class Libav::Stream::Video
127
272
  ret = avcodec_default_get_buffer(ctx, frame)
128
273
 
129
274
  # Update the :opaque field point at a copy of the last pts we've seen.
130
- @opaque.write_int64 @last_dts
275
+ @opaque.put_int64(0, @last_dts)
276
+ @opaque.put_uint64(8, @last_pos)
131
277
  frame[:opaque] = @opaque
132
278
 
133
279
  ret
134
280
  end
281
+
282
+ # Initialize our frame number oafset, and our pts oafset. These two are
283
+ # modified by seek(), and used by decode_frame().
284
+ @frame_oafset = 0
285
+ @pts_oafset = 0
286
+
135
287
  end
136
288
 
137
289
  def fps
138
- @av_stream[:r_frame_rate]
290
+ @av_stream[:r_frame_rate].to_f
139
291
  end
140
292
 
141
293
  # Set the +width+ of the frames returned by +decode_frame+
142
294
  def width=(width)
295
+ return if width == @width
143
296
  @width = width
144
- init_scaling
297
+ teardown
145
298
  end
146
299
 
147
300
  # Set the +height+ of the frames returned by +decode_frame+
148
301
  def height=(height)
302
+ return if height == @height
149
303
  @height = height
150
- init_scaling
304
+ teardown
151
305
  end
152
306
 
153
307
  # Set the +pixel format+ of the frames returned by +decode_frame+
154
308
  def pixel_format=(pixel_format)
309
+ return if pixel_format == @pixel_format
155
310
  @pixel_format = pixel_format
156
- init_scaling
311
+ teardown
312
+ end
313
+
314
+ # Set the buffer size
315
+ def buffer=(v)
316
+ return if v == @buffer
317
+ @buffer = v
318
+ teardown
319
+ end
320
+
321
+ # Check to see if this frame is being scaled
322
+ def scaling?
323
+ @av_codec_ctx[:width] != @width or
324
+ @av_codec_ctx[:height] != @height or
325
+ @av_codec_ctx[:pix_fmt] != @pixel_format
157
326
  end
158
327
 
159
328
  # Called by Libav::Reader.each_frame to decode each frame
160
329
  def decode_frame(packet)
161
- # initialize_scaling unless @scaling_initialized
330
+ setup unless @decode_ready
162
331
 
332
+ # Save off our dts and pos for the buffer allocation callback we declared
333
+ # in #initialize
163
334
  @last_dts = packet[:dts]
164
- avcodec_get_frame_defaults(@raw_frame.av_frame)
165
- rc = avcodec_decode_video2(@av_codec_ctx, @raw_frame.av_frame,
335
+ @last_pos = packet[:pos]
336
+
337
+ # Grab our raw frame off the raw frames queue. This will block if the
338
+ # caller is still using all the previous frames.
339
+ raw_frame = @raw_queue.shift
340
+
341
+ # Let the reader know we're stomping on this frame
342
+ @reader.frame_dirty(raw_frame)
343
+
344
+ # Call the decode function on our packet
345
+ avcodec_get_frame_defaults(raw_frame.av_frame)
346
+ rc = avcodec_decode_video2(@av_codec_ctx, raw_frame.av_frame,
166
347
  @frame_finished, packet)
167
- raise RuntimeError, "avcodec_decode_video2() failed, rc=#{rc}" if rc < 0
168
- return if @frame_finished.read_int == 0
169
348
 
170
- @raw_frame.pts = @raw_frame.av_frame[:opaque].read_int64
171
- @raw_frame.number = @av_codec_ctx[:frame_number].to_i
349
+ # Now, if we didn't get a frame, for one reason or another, let's throw the
350
+ # raw frame back on our queue.
351
+ if rc < 0 or @frame_finished.read_int == 0
352
+ @raw_queue.push raw_frame
353
+ return nil
354
+ end
355
+
356
+ raw_frame.number = @av_codec_ctx[:frame_number].to_i + @frame_oafset
357
+ raw_frame.pts = raw_frame.av_frame[:opaque].get_int64(0) + @pts_oafset
358
+ raw_frame.pos = raw_frame.av_frame[:opaque].get_uint64(8)
359
+
360
+ # AFS Data is broken down as follows:
361
+ # [ [frame number, pts, pos, true], # entry for first key frame
362
+ # [frame number, pts, pos, true], # entry for second key frame
363
+ # [frame number, pts, pos, true], # entry for N-th key frame
364
+ # [frame number, pts, pos, false], # optional, last non-key frame
365
+ # ]
366
+ if @update_afs and (@afs.empty? or raw_frame.number > @afs.last[0])
367
+ @afs.pop unless @afs.empty? or @afs.last[-1] == true
368
+ @afs << [ raw_frame.number, raw_frame.pts,
369
+ raw_frame.pos, raw_frame.key_frame? ]
370
+ end
371
+
372
+ # If we're scaling, or not buffering, throw the raw frame back on the
373
+ # queue; it's the only one we have
374
+ @raw_queue.push raw_frame if @swscale_ctx or @buffer == 0
375
+
376
+ # If we're not scaling at this point, we need to return the raw frame to
377
+ # the caller. This is the non-buffering, non-scaling return point.
378
+ return raw_frame unless @swscale_ctx
379
+
380
+ # Let's grab a scaled frame from our queue
381
+ scaled_frame = @scaled_queue.shift
382
+
383
+ # Let the reader know we're stomping on this frame
384
+ @reader.frame_dirty(scaled_frame)
385
+
386
+ # scale the frame
387
+ raw_frame.scale(:scale_ctx => @swscale_ctx,
388
+ :output_frame => scaled_frame)
389
+
390
+ # Throw the scaled frame back on the queue if we're not buffering
391
+ @scaled_queue.push scaled_frame if @buffer == 0
392
+
393
+ scaled_frame
394
+ end
172
395
 
173
- return @raw_frame unless @swscale_ctx
396
+ def release_frame(frame)
397
+ @scaled_queue.push frame if @scaled_frames.include? frame
398
+ @raw_queue.push frame if @raw_frames.include? frame
399
+ end
400
+
401
+ def rewind(count=nil)
402
+ @reader.rewind(count, :stream => self)
403
+ end
174
404
 
175
- @raw_frame.scale(:scale_ctx => @swscale_ctx,
176
- :output_frame => @scaled_frame)
405
+ # This method will make the stream release all references to buffered frames.
406
+ # The buffers will be recreated the next time #decode_frame is called.
407
+ def release_all_frames
408
+ teardown
177
409
  end
178
410
 
179
411
  private
180
412
 
181
- def init_scaling
182
- sws_freeContext(@swscale_ctx) unless @swscale_ctx.nil?
183
- @swscale_ctx = nil
184
- @scaled_frame = nil
413
+ # Initialize our raw and scaled frames along with our scaling context
414
+ def setup
415
+ return if @decode_ready
416
+
417
+ # call teardown() now just to make sure everything is released
418
+ teardown
419
+
420
+ # If @buffer is set to zero, we aren't buffering, but we will need one raw
421
+ # frame and one scaled frame.
422
+ buffer = @buffer
423
+ buffer += 1 if buffer == 0
424
+
425
+ # Let's allocate our raw frames. If we're scaling, we only need one raw
426
+ # frame, otherwise we'll need more raw frames.
427
+ ( scaling? ? 1 : buffer ).times do
428
+ frame = Libav::Frame::Video.new :stream => self, :alloc => false,
429
+ :width => @av_codec_ctx[:width],
430
+ :height => @av_codec_ctx[:height],
431
+ :pixel_format => @av_codec_ctx[:pix_fmt]
432
+ @raw_frames.push frame
433
+ @raw_queue.push frame
434
+ end
435
+
436
+ # If we're scaling, allocate our scaled frames and scaling context
437
+ if scaling?
438
+
439
+ # allocate our scaled frames
440
+ buffer.times do
441
+ frame = Libav::Frame::Video.new(:width => @width,
442
+ :height => @height,
443
+ :pixel_format => @pixel_format,
444
+ :stream => self)
445
+ @scaled_frames.push frame
446
+ @scaled_queue.push frame
447
+ end
185
448
 
186
- return if @width == @av_codec_ctx[:width] &&
187
- @height == @av_codec_ctx[:height] &&
188
- @pixel_format == @av_codec_ctx[:pix_fmt]
449
+ # Let's throw together a scaling context
450
+ @swscale_ctx = sws_getContext(@av_codec_ctx[:width],
451
+ @av_codec_ctx[:height],
452
+ @av_codec_ctx[:pix_fmt],
453
+ @width, @height, @pixel_format,
454
+ SWS_BICUBIC, nil, nil, nil) or
455
+ raise NoMemoryError, "sws_getContext() failed"
456
+ end
189
457
 
190
- @swscale_ctx = sws_getContext(@av_codec_ctx[:width],
191
- @av_codec_ctx[:height],
192
- @av_codec_ctx[:pix_fmt],
193
- @width, @height, @pixel_format,
194
- SWS_BICUBIC, nil, nil, nil) or
195
- raise NoMemoryError, "sws_getContext() failed"
458
+ @decode_ready = true
459
+ end
196
460
 
197
- @scaled_frame = Libav::Frame::Video.new(:width => @width,
198
- :height => @height,
199
- :pixel_format => @pixel_format,
200
- :stream => self)
461
+ # Release all references to our frames (raw & scaled), and free our scaling
462
+ # context.
463
+ def teardown
464
+ @raw_frames.clear
465
+ @raw_queue.clear
466
+ @scaled_frames.clear
467
+ @scaled_queue.clear
468
+ sws_freeContext(@swscale_ctx) if @swscale_ctx
469
+ @swscale_ctx = nil
470
+ @decode_ready = false
201
471
  end
202
472
  end
203
473
 
@@ -208,4 +478,12 @@ class Libav::Stream::Unsupported
208
478
  super(p)
209
479
  self.discard = :all
210
480
  end
481
+
482
+ def buffer=(v)
483
+ # nothing to be done here
484
+ end
485
+
486
+ def decode_frame(packet)
487
+ return nil
488
+ end
211
489
  end