Module: Libav::Stream
- Extended by:
- Forwardable
- Includes:
- FFI::Libav
- Included in:
- Unsupported, Video
- Defined in:
- lib/libav/stream.rb
Overview
Generic Stream class. Most of the logic resides in Libav::Stream::Video.
Defined Under Namespace
Classes: FrameNotFound, Unsupported, Video
Instance Attribute Summary collapse
-
#av_codec_ctx ⇒ Object
readonly
Returns the value of attribute av_codec_ctx.
-
#av_stream ⇒ Object
readonly
Returns the value of attribute av_stream.
-
#buffer ⇒ Object
readonly
Returns the value of attribute buffer.
-
#reader ⇒ Object
readonly
Returns the value of attribute reader.
Instance Method Summary collapse
- #discard ⇒ Object
- #discard=(value) ⇒ Object
-
#each_frame(opt = {}, &block) ⇒ Object
Loop through each frame of this stream.
- #format_afs(a) ⇒ Object
- #index ⇒ Object
- #initialize(p = {}) ⇒ Object
-
#next_frame ⇒ Object
Get the next frame in the stream.
-
#seek(p = {}) ⇒ Object
Seek to a specific location within the stream; the location can be either a PTS value or an absolute byte position.
-
#skip_frames(n) ⇒ Object
Skip some
n
frames in the stream. - #type ⇒ Object
Instance Attribute Details
#av_codec_ctx ⇒ Object (readonly)
Returns the value of attribute av_codec_ctx.
14 15 16 |
# File 'lib/libav/stream.rb', line 14 def av_codec_ctx @av_codec_ctx end |
#av_stream ⇒ Object (readonly)
Returns the value of attribute av_stream.
14 15 16 |
# File 'lib/libav/stream.rb', line 14 def av_stream @av_stream end |
#buffer ⇒ Object (readonly)
Returns the value of attribute buffer.
14 15 16 |
# File 'lib/libav/stream.rb', line 14 def buffer @buffer end |
#reader ⇒ Object (readonly)
Returns the value of attribute reader.
14 15 16 |
# File 'lib/libav/stream.rb', line 14 def reader @reader end |
Instance Method Details
#discard ⇒ Object
33 34 35 |
# File 'lib/libav/stream.rb', line 33 def discard @av_stream[:discard] end |
#discard=(value) ⇒ Object
29 30 31 |
# File 'lib/libav/stream.rb', line 29 def discard=(value) @av_stream[:discard] = value end |
#each_frame(opt = {}, &block) ⇒ Object
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.
53 54 55 |
# File 'lib/libav/stream.rb', line 53 def each_frame(opt={}, &block) @reader.each_frame(opt.merge({ :stream => index }), &block) end |
#format_afs(a) ⇒ Object
218 219 220 |
# File 'lib/libav/stream.rb', line 218 def format_afs(a) "{frame: %d, pts: %d, pos: %d}" % [*a] end |
#index ⇒ Object
41 42 43 |
# File 'lib/libav/stream.rb', line 41 def index @av_stream[:index] end |
#initialize(p = {}) ⇒ Object
17 18 19 20 21 22 23 24 25 26 27 |
# 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 |
#next_frame ⇒ Object
Get the next frame in the stream
58 59 60 |
# File 'lib/libav/stream.rb', line 58 def next_frame each_frame { |f| break f } end |
#seek(p = {}) ⇒ Object
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.
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# 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) ⇒ Object
Skip some n
frames in the stream
63 64 65 66 67 68 |
# 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 ⇒ Object
37 38 39 |
# File 'lib/libav/stream.rb', line 37 def type @av_codec_ctx[:codec_type] end |