class Libav::Stream::Video

Attributes

afs[R]
height[R]
pixel_format[R]
reader[R]
width[R]

Public Class Methods

new(p={}) click to toggle source
Calls superclass method Libav::Stream::new
# File lib/libav/stream.rb, line 228
def initialize(p={})
  super(p)

  # Handle frame width and height and setup any scaling necessary
  @width  = p[:widht]  || @av_codec_ctx[:width]
  @height = p[:height] || @av_codec_ctx[:height]
  @pixel_format = p[:pixel_format] || @av_codec_ctx[:pix_fmt]

  # Our stream index info
  @afs = p[:afs]
  @update_afs = @afs.nil? == false

  # Our array and queues for raw frames and scaled frames
  @raw_frames = []
  @raw_queue = Queue.new
  @scaled_frames = []
  @scaled_queue = Queue.new

  # Number of frames to buffer (default is disabled, 0)
  @buffer = 0

  # When this is set to true when all raw and scaled frames have been set up
  # by setup().
  @decode_ready = false

  # Pointer used to denote that decode frame was successful
  @frame_finished = FFI::MemoryPointer.new :int

  # Elaborate schemes to capture an accurate pts value.  When the buffer for
  # the frame is allocated, we pull the dts from the last packet processed
  # (set in decode_frame()), and set it in the opaque field of the frame.
  #
  # Since we may be running on a 32-bit platform, we can't just shove the
  # 64-bit dts in the :opaque pointer, so we have to alloc some space for the
  # address.  Instead of allocating and freeing repeatedly, we're going to
  # alloc it once now and reuse it for each decoded frame.
  @last_dts = nil
  @last_pos = nil
  @opaque = FFI::MemoryPointer.new :uint64, 2

  @av_codec_ctx[:get_buffer] = \
    FFI::Function.new(:int, [AVCodecContext.ptr, AVFrame.ptr]) do |ctx,frame|

      # Use the default method to get the buffer
      ret = avcodec_default_get_buffer(ctx, frame)

      # Update the :opaque field point at a copy of the last pts we've seen.
      @opaque.put_int64(0, @last_dts)
      @opaque.put_uint64(8, @last_pos)
      frame[:opaque] = @opaque

      ret
    end

  # Initialize our frame number oafset, and our pts oafset.  These two are
  # modified by seek(), and used by decode_frame().
  @frame_oafset = 0
  @pts_oafset = 0

end

Public Instance Methods

buffer=(v) click to toggle source

Set the buffer size

# File lib/libav/stream.rb, line 318
def buffer=(v)
  return if v == @buffer
  @buffer = v
  teardown
end
decode_frame(packet) click to toggle source

Called by Libav::Reader.each_frame to decode each frame

# File lib/libav/stream.rb, line 332
def decode_frame(packet)
  setup unless @decode_ready

  # Save off our dts and pos for the buffer allocation callback we declared
  # in #initialize
  @last_dts = packet[:dts]
  @last_pos = packet[:pos]

  # Grab our raw frame off the raw frames queue.  This will block if the
  # caller is still using all the previous frames.
  raw_frame = @raw_queue.shift

  # Let the reader know we're stomping on this frame
  @reader.frame_dirty(raw_frame)

  # Call the decode function on our packet
  avcodec_get_frame_defaults(raw_frame.av_frame)
  rc = avcodec_decode_video2(@av_codec_ctx, raw_frame.av_frame,
                             @frame_finished, packet)

  # Now, if we didn't get a frame, for one reason or another, let's throw the
  # raw frame back on our queue.
  if rc < 0 or @frame_finished.read_int == 0
    @raw_queue.push raw_frame
    return nil
  end

  raw_frame.number = @av_codec_ctx[:frame_number].to_i + @frame_oafset
  raw_frame.pts = raw_frame.av_frame[:opaque].get_int64(0) + @pts_oafset
  raw_frame.pos = raw_frame.av_frame[:opaque].get_uint64(8)

  # AFS Data is broken down as follows:
  #   [ [frame number, pts, pos, true],   # entry for first key frame
  #     [frame number, pts, pos, true],   # entry for second key frame
  #     [frame number, pts, pos, true],   # entry for N-th key frame
  #     [frame number, pts, pos, false],  # optional, last non-key frame
  #   ]
  if @update_afs and (@afs.empty? or raw_frame.number > @afs.last[0])
    @afs.pop unless @afs.empty? or @afs.last[-1] == true
    @afs << [ raw_frame.number, raw_frame.pts,
              raw_frame.pos, raw_frame.key_frame? ]
  end

  # If we're scaling, or not buffering, throw the raw frame back on the
  # queue; it's the only one we have
  @raw_queue.push raw_frame if @swscale_ctx or @buffer == 0

  # If we're not scaling at this point, we need to return the raw frame to
  # the caller.  This is the non-buffering, non-scaling return point.
  return raw_frame unless @swscale_ctx

  # Let's grab a scaled frame from our queue
  scaled_frame = @scaled_queue.shift

  # Let the reader know we're stomping on this frame
  @reader.frame_dirty(scaled_frame)

  # scale the frame
  raw_frame.scale(:scale_ctx => @swscale_ctx,
                  :output_frame => scaled_frame)

  # Throw the scaled frame back on the queue if we're not buffering
  @scaled_queue.push scaled_frame if @buffer == 0

  scaled_frame
end
fps() click to toggle source
# File lib/libav/stream.rb, line 289
def fps
  fps = @av_stream[:r_frame_rate].to_f
  # Some codecs don't set frame rate, so we'll use 1/timebase
  fps = 1/@av_stream[:time_base].to_f if fps.nan?
  fps
end
height=(height) click to toggle source

Set the height of the frames returned by decode_frame

# File lib/libav/stream.rb, line 304
def height=(height)
  return if height == @height
  @height = height
  teardown
end
pixel_format=(pixel_format) click to toggle source

Set the +pixel format+ of the frames returned by decode_frame

# File lib/libav/stream.rb, line 311
def pixel_format=(pixel_format)
  return if pixel_format == @pixel_format
  @pixel_format = pixel_format
  teardown
end
release_all_frames() click to toggle source

This method will make the stream release all references to buffered frames. The buffers will be recreated the next time decode_frame is called.

# File lib/libav/stream.rb, line 410
def release_all_frames
  teardown
end
release_frame(frame) click to toggle source
# File lib/libav/stream.rb, line 399
def release_frame(frame)
  @scaled_queue.push frame if @scaled_frames.include? frame
  @raw_queue.push frame if @raw_frames.include? frame
end
rewind(count=nil) click to toggle source
# File lib/libav/stream.rb, line 404
def rewind(count=nil)
  @reader.rewind(count, :stream => self)
end
scaling?() click to toggle source

Check to see if this frame is being scaled

# File lib/libav/stream.rb, line 325
def scaling?
  @av_codec_ctx[:width] != @width or
    @av_codec_ctx[:height] != @height or
    @av_codec_ctx[:pix_fmt] != @pixel_format
end
width=(width) click to toggle source

Set the width of the frames returned by decode_frame

# File lib/libav/stream.rb, line 297
def width=(width)
  return if width == @width
  @width = width
  teardown
end

Private Instance Methods

setup() click to toggle source

Initialize our raw and scaled frames along with our scaling context

# File lib/libav/stream.rb, line 417
def setup
  return if @decode_ready

  # call teardown() now just to make sure everything is released
  teardown

  # If @buffer is set to zero, we aren't buffering, but we will need one raw
  # frame and one scaled frame.
  buffer = @buffer
  buffer += 1 if buffer == 0

  # Let's allocate our raw frames.  If we're scaling, we only need one raw
  # frame, otherwise we'll need more raw frames.
  ( scaling? ? 1 : buffer ).times do
    frame = Libav::Frame::Video.new :stream => self, :alloc => false,
                                    :width => @av_codec_ctx[:width],
                                    :height => @av_codec_ctx[:height],
                                    :pixel_format => @av_codec_ctx[:pix_fmt]
    @raw_frames.push frame
    @raw_queue.push frame
  end

  # If we're scaling, allocate our scaled frames and scaling context
  if scaling?

    # allocate our scaled frames
    buffer.times do
      frame = Libav::Frame::Video.new(:width => @width,
                                      :height => @height,
                                      :pixel_format => @pixel_format,
                                      :stream => self)
      @scaled_frames.push frame
      @scaled_queue.push frame
    end

    # Let's throw together a scaling context
    @swscale_ctx = sws_getCachedContext(nil, @av_codec_ctx[:width],
                                        @av_codec_ctx[:height],
                                        @av_codec_ctx[:pix_fmt], @width,
                                        @height, @pixel_format, SWS_BICUBIC,
                                        nil, nil, nil) or
      raise NoMemoryError, "sws_getCachedContext() failed"
  end

  @decode_ready = true
end
teardown() click to toggle source

Release all references to our frames (raw & scaled), and free our scaling context.

# File lib/libav/stream.rb, line 466
def teardown
  @raw_frames.clear
  @raw_queue.clear
  @scaled_frames.clear
  @scaled_queue.clear
  sws_freeContext(@swscale_ctx) if @swscale_ctx
  @swscale_ctx = nil
  @decode_ready = false
end