module Libav::Stream
Generic Stream
class. Most of the logic resides in Libav::Stream::Video
.
Attributes
av_codec_ctx[R]
av_stream[R]
buffer[R]
reader[R]
Public Class Methods
new(p={})
click to toggle source
# File lib/libav/stream.rb, line 17 def initialize(p={}) @reader = p[:reader] or raise ArgumentError, "no :reader" @av_stream = p[:av_stream] or raise ArgumentError, "no :av_stream" @av_codec_ctx = @av_stream[:codec] # open the codec codec = avcodec_find_decoder(@av_codec_ctx[:codec_id]) or raise RuntimeError, "No decoder found for #{@av_codec_ctx[:codec_id]}" avcodec_open2(@av_codec_ctx, codec, nil) == 0 or raise RuntimeError, "avcodec_open() failed" end
Public Instance Methods
discard()
click to toggle source
# File lib/libav/stream.rb, line 33 def discard @av_stream[:discard] end
discard=(value)
click to toggle source
# File lib/libav/stream.rb, line 29 def discard=(value) @av_stream[:discard] = value end
each_frame(opt={}, &block)
click to toggle source
Loop through each frame of this stream
Arguments:
[:buffer] Number of frames to buffer
Note that when using the :buffer argument, the caller MUST call Frame#release when it is done processing a frame.
# File lib/libav/stream.rb, line 53 def each_frame(opt={}, &block) @reader.each_frame(opt.merge({ :stream => index }), &block) end
format_afs(a)
click to toggle source
# File lib/libav/stream.rb, line 218 def format_afs(a) "{frame: %d, pts: %d, pos: %d}" % [*a] end
index()
click to toggle source
# File lib/libav/stream.rb, line 41 def index @av_stream[:index] end
next_frame()
click to toggle source
Get the next frame in the stream
# File lib/libav/stream.rb, line 58 def next_frame each_frame { |f| break f } end
seek(p={})
click to toggle source
Seek to a specific location within the stream; the location can be either a PTS value or an absolute byte position.
Arguments:
[:pts] PTS location [:byte] Byte location [:backward] Seek backward [:any] Seek to non-key frames
Examples:
seek :frame => 3 seek :pts => 90218390 seek :pos => 0
Last index of afs data should contain last frame read.
# File lib/libav/stream.rb, line 85 def seek(p={}) raise ArgumentError, ":pts, :frame, and :byte are mutually exclusive" if ([:byte, :frame, :pts] & p.keys).size != 1 # Default seek arguments flags = p[:backward] ? AVSEEK_FLAG_BACKWARD : 0 flags |= AVSEEK_FLAG_BYTE if p[:byte] flags |= AVSEEK_FLAG_FRAME if p[:frame] flags |= AVSEEK_FLAG_ANY if p[:any] seek_args = [[p[:pts] || p[:frame] || p[:byte], flags]] # If we have afs data, and our target frame is within the data, replace # seek_args with an array of arguments for seeking to each key frame # preceding our target frame, in reverse-chronological order. The idea is # that sometimes libav seek puts us someplace strange. If we start at the # closest key frame to our target frame, and the work backwards in the # stream, libav will eventually put us in a place where we can read to the # target frame. if @afs and !@afs.empty? and (p[:frame] && p[:frame] <= @afs.last[0] or p[:pts] && p[:pts] <= @afs.last[1] or p[:byte] && p[:byte] <= @afs.last[2]) # Note that we set the flags to AVSEEK_FLAG_BACKWARD for each of our arg # sets. This is just because by observation the BACKWARD flag seems to # give us better results regardless of the direction of our seek. seek_args = @afs.select do |data| p[:frame] && data[0] < p[:frame] or p[:pts] && data[1] < p[:pts] or p[:byte] && data[2] && data[2] < p[:byte] end.map { |d| [ d[1], AVSEEK_FLAG_BACKWARD, true ] }.reverse # Throw the seek 0 into the end of the list as a fall back seek_args.push [0, AVSEEK_FLAG_BACKWARD, true] end # Disable afs updating because we're about to seek. If the seek ends up # within our afs data, it will be re-enabled. @update_afs = false # We will fill this frame in the next loop, and use it after the loop is # complete. frame = nil # Loop through each set of arguments provided. Seek to the timestamp in # the arguments and then verify that we haven't gone past our target. seek_args.each do |ts, flags, afs| # Flush the codec so we don't get buffered frames after the seek avcodec_flush_buffers(@av_codec_ctx) # Kick off our seek rc = avformat_seek_file(@reader.av_format_ctx, @av_stream[:index], ts, ts, ts, flags) raise RuntimeError, "avformat_seek_file(#{ts}, #{flags.to_s(16)})" + " failed, #{rc}" if rc < 0 # If we performed an afs seek, we need to enable afs data updating, and # if not we need to disable it. @update_afs = (afs == true) # We also need to clear the frame and pts oafsets. If this is a afs # seek, these will be re-set to their new values later. @frame_oafset = 0 @pts_oafset = 0 # If this wasn't an afs seek, we are done here. return true unless afs # Grab the next frame, and see if it precedes, or is, our target frame. # If no frame is returned, we hit EOF, so try seeking a little earlier. frame = next_frame or next frame.release # Although the code is only needed in this loop when we're seeking by # :frame, we're going to adjust our frame oafset here. It's a little # slower, but less complicated overall. # # Find the afs entry for this frame (it's guaranteed to be a key # frame), and use that to adjust the number of the frame we just read. # # If we're unable to find a match in the afs data, then we've gone past # the end of the afs data, and we should try another seek timestamp. seek_afs = @afs.find { |f| f[1] == frame.pts } or next raise RuntimeError, "afs data mismatch: afs #{seek_afs}, " + "frame [#{frame.number}, #{frame.pts}, #{frame.pos}]" if frame.pos != seek_afs[2] # Adjust our frame oafset, and use that to adjust our frame number @frame_oafset = -1 * (frame.number - seek_afs[0]) frame.number += @frame_oafset # If this frame precedes or is our target frame, then the seek was # successful, and we can break out of this loop. break if p[:pts] && p[:pts] >= frame.pts or p[:frame] && p[:frame] >= frame.number or p[:byte] && p[:byte] >= frame.pos # This frame was after our target frame, so ignore it. frame = nil end # If we went through all the seek_args without finding a single frame # before our target frame, throw an exception. unless frame mode = (p.keys & [:pts, :frame, :byte]).first raise FrameNotFound, "Unable to find frame {%s: %d}" % [mode, p[mode]] end # Rewind the frame so we can access it in the next loop rewind(1) # Also mark afs as updating because we're within the known afs data region. @update_afs = true # Now that we have corrected oafsets, we need to read the next few frames # until we encounter the target frame or a frame after it (possible for # pts values that are slightly off). each_frame do |frame| frame.release break if p[:pts] && frame.pts >= p[:pts] or p[:frame] && frame.number >= p[:frame] or p[:byte] && frame.pos >= p[:byte] end # Alright, let's rewind by one frame so the next call to #each_frame will # yield the frame they requested. rewind(1) return true end
skip_frames(n)
click to toggle source
Skip some n
frames in the stream
# File lib/libav/stream.rb, line 63 def skip_frames(n) # XXX not sure if this is true raise RuntimeError, "Cannot skip frames when discarding all frames" if discard == :all each_frame { |f| f.release; n -= 1; break if n == 0} end
type()
click to toggle source
# File lib/libav/stream.rb, line 37 def type @av_codec_ctx[:codec_type] end