Class: Libav::Reader

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Includes:
FFI::Libav
Defined in:
lib/libav/reader.rb

Overview

Libav file reader

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(filename, p = {}) ⇒ Reader

Initialize a Reader for a specific file

Attributes

  • filename - file to read

Options

  • :afs - Enable and supply fast-frame-seek data (default: false)

Usage

# open a file named 'video.ts' for reading
r = Libav::Reader.new("video.ts")

# open the same video and enable AFS
r = Libav::Reader.new("video.ts", :afs => true)
r.each_frame { |f| do_something(f) }

# After reading any portion of the file, save the AFS data
File.open("video_afs.yml", "w") do |file|
  file.write(r.afs.to_yaml)
end

# open a video file and use AFS data from a previous run
afs = Yaml.load_file("video_afs.yml")
r = Libav::Reader.new("video.ts", :afs => afs)

Raises:

  • (RuntimeError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/libav/reader.rb', line 36

def initialize(filename, p={})
  @filename = filename or raise ArgumentError, "No filename"

  Libav.register_all
  @av_format_ctx = FFI::MemoryPointer.new(:pointer)
  rc = avformat_open_input(@av_format_ctx, @filename, nil, nil)
  raise RuntimeError, "avformat_open_input() failed, filename='%s', rc=%d" %
    [filename, rc] if rc != 0
  @av_format_ctx = AVFormatContext.new @av_format_ctx.get_pointer(0)

  rc = avformat_find_stream_info(@av_format_ctx, nil)
  raise RuntimeError, "av_find_stream_info() failed, rc=#{rc}" if rc < 0

  # Fast frame seeking data; initialize it if @afs is enabled, but no data
  # has been provided.
  @afs = p[:afs]
  @afs = Array.new(@av_format_ctx[:nb_streams]) {[]} if @afs == true

  # Open all of our streams
  initialize_streams(p)

  # Set up a finalizer to close all the things we've opened
  ObjectSpace.define_finalizer(self,
    cleanup_proc(@av_format_ctx, streams.map { |s| s.av_codec_ctx }))

  # Our packet for reading
  @packet = AVPacket.new
  av_init_packet(@packet)

  # output frame buffer; used for #rewind
  @output_frames = []

  # This is our rewind queue, frames from @output_frame get stuck on here
  # by #rewind, and shifted off by #each_frame
  @rewound = []
end

Instance Attribute Details

#afsObject (readonly)

Returns the value of attribute afs.



8
9
10
# File 'lib/libav/reader.rb', line 8

def afs
  @afs
end

#av_format_ctxObject (readonly)

Returns the value of attribute av_format_ctx.



8
9
10
# File 'lib/libav/reader.rb', line 8

def av_format_ctx
  @av_format_ctx
end

#filenameObject (readonly)

Returns the value of attribute filename.



8
9
10
# File 'lib/libav/reader.rb', line 8

def filename
  @filename
end

#streamsObject (readonly)

Returns the value of attribute streams.



8
9
10
# File 'lib/libav/reader.rb', line 8

def streams
  @streams
end

Instance Method Details

#default_streamObject

Get the default stream



139
140
141
# File 'lib/libav/reader.rb', line 139

def default_stream
  @streams[av_find_default_stream_index(@av_format_ctx)]
end

#dump_formatObject

Call av_dump_format to print out the format info for the video



74
75
76
# File 'lib/libav/reader.rb', line 74

def dump_format
  FFI::Libav.av_dump_format(@av_format_ctx, 0, @filename, 0)
end

#durationObject

Video duration in (fractional) seconds



79
80
81
# File 'lib/libav/reader.rb', line 79

def duration
  @duration ||= @av_format_ctx[:duration].to_f / AV_TIME_BASE
end

#each_frame(p = {}, &block) ⇒ Object

Loop through each frame

Argument

  • block - block of code to call with the frame

Options

  • :stream stream index or indexes to get frames for

  • :buffer number of frames to buffer in each stream

Usage

# Read each frame
reader.each_frame do |frame|

  # call some method for showing the frame
  my_show_frame(frame)
end

Raises:

  • (ArgumentError)


100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/libav/reader.rb', line 100

def each_frame(p={}, &block)
  raise ArgumentError, "No block provided" unless block_given?

  # Patch up the :stream argument
  p[:stream] ||= @streams.map { |s| s[:index] }
  p[:stream] = [ p[:stream] ] unless p[:stream].is_a? Array

  # Notify each stream of the requested buffer size
  p[:stream].each { |i| @streams[i].buffer = p[:buffer] if p[:buffer]}

  # If we have any frames on our @rewound list
  while frame = @rewound.shift
    @output_frames.push frame
    next if p[:stream].include? frame.stream
    break if yield(frame) == false
  end

  # Let's read frames
  while av_read_frame(@av_format_ctx, @packet) >= 0

    # Only call the decoder if the packet is from a stream we're interested
    # in.
    frame = nil
    frame = @streams[@packet[:stream_index]].decode_frame(@packet) if
      p[:stream].include? @packet[:stream_index]

    # release our packet memory
    av_free_packet(@packet)

    next unless frame

    # Before yielding the frame, add it to our output list for rewind
    @output_frames.push frame

    yield frame
  end
end

#frame_dirty(frame) ⇒ Object

This method is used to notify the Reader that the frame is about to be modified. This call is used to update the output buffer that is used by #rewind. The supplied frame, and all preceding frames are dropped from the output buffer. This reduces how far we can rewind.



205
206
207
208
# File 'lib/libav/reader.rb', line 205

def frame_dirty(frame)
  index = @output_frames.index(frame) or return
  @output_frames.shift(index + 1)
end

#rewind(count = nil, p = {}) ⇒ Object

Rewind the reader

This method will rewind the reader at most count frames for the whole file, or for the :stream provided. If not enough frames are available, rewind() will rewind as many as possible.

After calling #rewind, #each_frame will yield frames

Arguments:

+count+ Number of frames to rewind

Options:

+:stream+ [optional] stream +count+ applies to

Return:

Number of frames for stream that were rewound


164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/libav/reader.rb', line 164

def rewind(count=nil, p={})

  # Reduce our output frames based on any :stream provided
  frames = @output_frames.select do |frame|
    p[:stream].nil? or frame.stream == p[:stream]
  end

  # Cap the count at the number of frames we can rewind
  count = frames.size if count.nil? or count > frames.size

  # find the count-th frame from the end of our reduced frame array.
  frame = frames[-1 * count.to_i]

  # Find the index of that frame in the real output frames array
  index = @output_frames.find_index(frame) or return 0

  # Split the output frames into two arrays, those that have been yielded
  # (output_frames), and those that have been rewound (rewound)
  @rewound = (@output_frames.slice!(index, @output_frames.size) || []) +
    @rewound

  # Flush the buffer of every stream we rewound.  We need to do this because
  # other threads may have references to some of the frames we rewound.  If
  # it were possible to reverse Libav::Stream#release_frame, there would
  # still be a problem if another thread released one of our rewound frames
  # before it was yielded by #each_frame.
  #
  # The solution to this problem is to have each stream clear all of their
  # frame buffers.  The next time the stream decodes a frame, it will have to
  # allocate a new buffer.  Expensive, but this shouldn't happen very often.
  @rewound.map { |f| f.stream }.uniq.each { |s| s.release_all_frames }

  # Return the number of frames rewound for the requested stream.  If no
  # stream were requested, this would be the total number of frames rewound.
  frames.size - frames.find_index(frame)
end

#seek(p = {}) ⇒ Object

See Libav::Stream.seek



144
145
146
# File 'lib/libav/reader.rb', line 144

def seek(p = {})
  default_stream.seek(p)
end