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