Class: Libav::Reader
- Inherits:
-
Object
- Object
- Libav::Reader
- Extended by:
- Forwardable
- Includes:
- FFI::Libav
- Defined in:
- lib/libav/reader.rb
Overview
Libav file reader
Instance Attribute Summary collapse
-
#afs ⇒ Object
readonly
Returns the value of attribute afs.
-
#av_format_ctx ⇒ Object
readonly
Returns the value of attribute av_format_ctx.
-
#filename ⇒ Object
readonly
Returns the value of attribute filename.
-
#streams ⇒ Object
readonly
Returns the value of attribute streams.
Instance Method Summary collapse
-
#default_stream ⇒ Object
Get the default stream.
-
#dump_format ⇒ Object
Call
av_dump_format
to print out the format info for the video. -
#duration ⇒ Object
Video duration in (fractional) seconds.
-
#each_frame(p = {}, &block) ⇒ Object
Loop through each frame.
-
#frame_dirty(frame) ⇒ Object
This method is used to notify the Reader that the frame is about to be modified.
-
#initialize(filename, p = {}) ⇒ Reader
constructor
Initialize a Reader for a specific file.
-
#rewind(count = nil, p = {}) ⇒ Object
Rewind the reader.
-
#seek(p = {}) ⇒ Object
See Libav::Stream.seek.
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)
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
#afs ⇒ Object (readonly)
Returns the value of attribute afs.
8 9 10 |
# File 'lib/libav/reader.rb', line 8 def afs @afs end |
#av_format_ctx ⇒ Object (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 |
#filename ⇒ Object (readonly)
Returns the value of attribute filename.
8 9 10 |
# File 'lib/libav/reader.rb', line 8 def filename @filename end |
#streams ⇒ Object (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_stream ⇒ Object
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_format ⇒ Object
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 |
#duration ⇒ Object
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
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 |