ffi-ffmpeg 0.5.8135c9c5dc36

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ require 'ffi/ffmpeg'
2
+
3
+ module FFmpeg::Frame; end
4
+
5
+ class FFmpeg::Frame::Video
6
+ include FFI::FFmpeg
7
+
8
+ attr_reader :av_frame, :width, :height, :pixel_format, :stream
9
+ attr_accessor :pts, :number
10
+
11
+ def initialize(p={})
12
+ @stream = p[:stream] or raise ArgumentError, "no :stream"
13
+ @width = p[:width] or raise ArgumentError, "no :width"
14
+ @height = p[:height] or raise ArgumentError, "no :height"
15
+ @pixel_format = p[:pixel_format] or
16
+ raise ArgumentError "no :pixel_format"
17
+ @av_frame = avcodec_alloc_frame or
18
+ raise NoMemoryError "avcodec_alloc_frame() failed"
19
+ @av_frame = AVFrame.new @av_frame
20
+
21
+ # Set up our finalizer which calls av_free() on the av_frame.
22
+ ObjectSpace.define_finalizer(self, self.class.method(:finalize).to_proc)
23
+
24
+ bytes = avpicture_get_size(@pixel_format, @width, @height)
25
+ @buffer = FFI::MemoryPointer.new(:uchar, bytes)
26
+ avpicture_fill(@av_frame, @buffer, @pixel_format, @width, @height)
27
+ end
28
+
29
+ def self.finalize(id)
30
+ av_free(@av_frame)
31
+ end
32
+
33
+ def key_frame?
34
+ @av_frame[:key_frame] == 1
35
+ end
36
+
37
+ def scale(p={})
38
+ width = p[:width] || @width
39
+ height = p[:height] || @height
40
+ pixel_format = p[:pixel_format] || @pixel_format
41
+ out = FFmpeg::Frame::Video.new(:width => width, :height => height,
42
+ :pixel_format => pixel_format,
43
+ :stream => stream)
44
+
45
+ scale_ctx = sws_getContext(@width, @height, @pixel_format,
46
+ width, height, pixel_format,
47
+ :bicubic, nil, nil, nil) or
48
+ raise NoMemoryError, "sws_getContext() failed"
49
+
50
+ rc = sws_scale(scale_ctx, @av_frame[:data], @av_frame[:linesize], 0,
51
+ @height, out.av_frame[:data], out.av_frame[:linesize])
52
+ sws_freeContext(scale_ctx)
53
+
54
+ out.pts = @pts
55
+ out.number = @number
56
+ out
57
+ end
58
+ end
@@ -0,0 +1,74 @@
1
+ require 'ffi/ffmpeg'
2
+
3
+ class FFmpeg::Reader
4
+ include FFI::FFmpeg
5
+
6
+ attr_reader :filename, :streams, :av_format_ctx
7
+
8
+ def initialize(filename, p={})
9
+ @filename = filename or raise ArgumentError, "No filename"
10
+
11
+ FFmpeg.register_all
12
+ @av_format_ctx = FFI::MemoryPointer.new(:pointer)
13
+ rc = av_open_input_file(@av_format_ctx, @filename, nil, 0, nil)
14
+ raise RuntimeError, "av_open_input_file() failed, filename='%s', rc=%d" %
15
+ [filename, rc] if rc != 0
16
+ @av_format_ctx = AVFormatContext.new @av_format_ctx.get_pointer(0)
17
+
18
+ rc = av_find_stream_info(@av_format_ctx)
19
+ raise RuntimeError, "av_find_stream_info() failed, rc=#{rc}" if rc < 0
20
+
21
+ initialize_streams(p)
22
+ end
23
+
24
+ def dump_format
25
+ FFI::FFmpeg.dump_format(@av_format_ctx, 0, @filename, 0)
26
+ end
27
+
28
+ def each_frame(&block)
29
+ raise ArgumentError, "No block provided" unless block_given?
30
+
31
+ packet = avcodec_alloc_frame or
32
+ raise NoMemoryError, "avcodec_alloc_frame() failed"
33
+ packet = AVPacket.new packet
34
+
35
+ while av_read_frame(@av_format_ctx, packet) >= 0
36
+ frame = @streams[packet[:stream_index]].decode_frame(packet)
37
+ rc = frame ? yield(frame) : true
38
+ av_free_packet(packet)
39
+
40
+ break if rc == false
41
+ end
42
+
43
+ av_free(packet)
44
+ end
45
+
46
+ def default_stream
47
+ @streams[av_find_default_stream_index(@av_format_ctx)]
48
+ end
49
+
50
+ def seek(p = {})
51
+ default_stream.seek(p)
52
+ end
53
+
54
+ private
55
+
56
+ def initialize_streams(p={})
57
+ @streams = @av_format_ctx[:nb_streams].times.map do |i|
58
+ av_stream = AVStream.new @av_format_ctx[:streams][i]
59
+ av_codec_ctx = AVCodecContext.new av_stream[:codec]
60
+
61
+ case av_codec_ctx[:codec_type]
62
+ when :video
63
+ FFmpeg::Stream::Video.new(:reader => self,
64
+ :av_stream => av_stream,
65
+ :pixel_format => p[:pixel_format],
66
+ :width => p[:width],
67
+ :height => p[:height])
68
+ else
69
+ FFmpeg::Stream::Unsupported.new(:reader => self,
70
+ :av_stream => av_stream)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,244 @@
1
+ require 'ffi/ffmpeg'
2
+ require 'pp'
3
+
4
+ class String
5
+ def hexdump
6
+ buf = ""
7
+ offset = 0
8
+ words = self.unpack("N%d" % (self.length/4.0).ceil)
9
+ until words.empty?
10
+ line = words.shift(4).compact
11
+ buf += sprintf("[%04x] " + ("%08x " * line.size) + "|%s|\n",
12
+ offset * 16, *line,
13
+ line.pack("N%d" % line.size).tr("^\040-\176","."))
14
+ offset += 1
15
+ end
16
+ buf
17
+ end
18
+ end
19
+
20
+ module FFmpeg::Stream
21
+ include FFI::FFmpeg
22
+
23
+ attr_reader :reader, :av_stream, :av_codec_ctx
24
+
25
+ def initialize(p={})
26
+ @reader = p[:reader] or raise ArgumentError, "no :reader"
27
+ @av_stream = p[:av_stream] or raise ArgumentError, "no :av_stream"
28
+ @av_codec_ctx = AVCodecContext.new @av_stream[:codec]
29
+
30
+ # open the codec
31
+ codec = avcodec_find_decoder(@av_codec_ctx[:codec_id]) or
32
+ raise RuntimeError, "No decoder found for #{@av_codec_ctx[:codec_id]}"
33
+ avcodec_open(@av_codec_ctx, codec) == 0 or
34
+ raise RuntimeError, "avcodec_open() failed"
35
+ end
36
+
37
+ def discard=(value)
38
+ @av_stream[:discard] = value
39
+ end
40
+
41
+ def discard
42
+ @av_stream[:discard]
43
+ end
44
+
45
+ def type
46
+ @av_codec_ctx[:codec_type]
47
+ end
48
+
49
+ def index
50
+ @av_stream[:index]
51
+ end
52
+
53
+ def decode_frame(packet)
54
+ return false
55
+ raise NotImplementedError, "decode_frame() not defined for #{self.class}"
56
+ end
57
+
58
+ def each_frame
59
+ @reader.each_frame { |frame| yield frame if frame.stream == self }
60
+ end
61
+
62
+ def next_frame
63
+ frame = nil
64
+ each_frame { |f| frame = f; break }
65
+ frame
66
+ end
67
+
68
+ def skip_frames(n)
69
+ raise RuntimeError, "Cannot skip frames when discarding all frames" if
70
+ discard == :all
71
+ each_frame { |f| n -= 1 != 0 }
72
+ end
73
+
74
+ # Seek to a specific location within the stream; the location can be either
75
+ # a PTS value or an absolute byte position.
76
+ #
77
+ # Arguments:
78
+ # [:pts] PTS location
79
+ # [:pos] Byte location
80
+ # [:backward] Seek backward
81
+ # [:any] Seek to non-key frames
82
+ #
83
+ def seek(p={})
84
+ p = { :pts => p } unless p.is_a? Hash
85
+
86
+ raise ArgumentError, ":pts and :pos are mutually exclusive" \
87
+ if p[:pts] and p[:pos]
88
+
89
+ pos = p[:pts] || p[:pos]
90
+ flags = 0
91
+ flags |= AVSEEK_FLAG_BYTE if p[:pos]
92
+ flags |= AVSEEK_FLAG_BACKWARD if p[:backward]
93
+ flags |= AVSEEK_FLAG_ANY if p[:any]
94
+
95
+ rc = av_seek_frame(@reader.av_format_ctx, @av_stream[:index], pos, flags)
96
+ raise RuntimeError, "av_seek_frame() failed, %d" % rc if rc < 0
97
+ true
98
+ end
99
+ end
100
+
101
+ class FFmpeg::Stream::Video
102
+ include FFmpeg::Stream
103
+
104
+ attr_reader :raw_frame, :width, :height, :pixel_format, :buffer_size, :reader
105
+
106
+ def initialize(p={})
107
+ super(p)
108
+ @width = p[:widht] || @av_codec_ctx[:width]
109
+ @height = p[:height] || @av_codec_ctx[:height]
110
+ @pixel_format = p[:pixel_format] || @av_codec_ctx[:pix_fmt]
111
+ @buffer_size = p[:buffer_size] || 1
112
+
113
+ @raw_frame = FFmpeg::Frame::Video.new :stream => self,
114
+ :width => @av_codec_ctx[:width],
115
+ :height => @av_codec_ctx[:height],
116
+ :pixel_format =>
117
+ @av_codec_ctx[:pix_fmt]
118
+
119
+ @frame_finished = FFI::MemoryPointer.new :int
120
+
121
+ @scaling_initialized = false
122
+ @swscale_ctx = nil
123
+ @buffered_frames = nil
124
+ @last_pts = nil
125
+
126
+ # Callback function for storing the dts at the time of buffer
127
+ # allocation to later be used as pts.
128
+ @av_codec_ctx[:get_buffer] = @get_buffer_callback = \
129
+ FFI::Function.new(:void, [:pointer, :pointer]) do |ctx, frame|
130
+ ret = avcodec_default_get_buffer(ctx, frame)
131
+ AVFrame.new(frame)[:opaque] = FFI::Pointer.new(@last_pts)
132
+ # pp :alloc => @last_pts
133
+ ret
134
+ end
135
+ end
136
+
137
+ def fps
138
+ @av_stream[:r_frame_rate]
139
+ end
140
+
141
+ def width=(width)
142
+ @scaling_initialized = false
143
+ @width = width
144
+ end
145
+
146
+ def height=(height)
147
+ @scaling_initialized = false
148
+ @height = height
149
+ end
150
+
151
+ def pixel_format=(pixel_format)
152
+ @scaling_initialized = false
153
+ @pixel_format = pixel_format
154
+ end
155
+
156
+ def buffer_size=(frames)
157
+ @scaling_initialized = false
158
+ @buffer_size = frames
159
+ end
160
+
161
+ def decode_frame(packet)
162
+ initialize_scaling unless @scaling_initialized
163
+
164
+ # pp :read => packet[:dts]
165
+ @last_pts = packet[:dts]
166
+ rc = avcodec_decode_video(@av_codec_ctx, @raw_frame.av_frame,
167
+ @frame_finished, packet[:data], packet[:size])
168
+ raise RuntimeError, "avcodec_decode_video() failed, rc=#{rc}" if rc < 0
169
+
170
+ return if @frame_finished.read_int == 0
171
+ # pp :finished => @raw_frame.av_frame[:opaque].address,
172
+ # :pts => @raw_frame.av_frame[:pts],
173
+ # :dts => packet[:dts],
174
+ # :type => @raw_frame.av_frame[:pict_type],
175
+ # :key_frame => @raw_frame.av_frame[:key_frame]
176
+
177
+ # avcodec_decode_video() returns frames in the correct pts order, and
178
+ # according to the dranger tutorial, the packet's dts is the frame's
179
+ # pts. When the dts has not been set (AV_NOPTS_VALUE) use the dts from
180
+ # the first packet of the frame which is stored in the :opaque field of
181
+ # the AVFrame.
182
+ @raw_frame.pts = nil
183
+ @raw_frame.pts = packet[:dts] unless packet[:dts] == AV_NOPTS_VALUE
184
+ @raw_frame.pts ||= @raw_frame.av_frame[:opaque].address
185
+
186
+ @raw_frame.number = @av_codec_ctx[:frame_number].to_i
187
+
188
+ return @raw_frame unless @swscale_ctx
189
+
190
+ # XXX Need to provide a better mechanism for making sure buffer is ready
191
+ # for use.
192
+ scaled_frame = @buffered_frames.shift
193
+ @buffered_frames << scaled_frame
194
+
195
+ out_frame = scaled_frame.av_frame
196
+ in_frame = @raw_frame.av_frame
197
+
198
+ # Make sure we copy the key_frame value across.
199
+ # XXX Need to also do this for some other fields
200
+ out_frame[:key_frame] = in_frame[:key_frame]
201
+
202
+ rc = sws_scale(@swscale_ctx, in_frame[:data], in_frame[:linesize], 0,
203
+ @raw_frame.height, out_frame[:data], out_frame[:linesize])
204
+ scaled_frame.pts = @raw_frame.pts
205
+ scaled_frame.number = @av_codec_ctx[:frame_number]
206
+ scaled_frame
207
+ end
208
+
209
+ private
210
+
211
+ def initialize_scaling
212
+ @scaling_initialized = true
213
+ @swscale_ctx = nil
214
+ @buffered_frames = nil
215
+
216
+ return if @width == @av_codec_ctx[:width] &&
217
+ @height == @av_codec_ctx[:height] &&
218
+ @pixel_format == @av_codec_ctx[:pix_fmt] &&
219
+ @buffer_size < 2
220
+
221
+ @buffered_frames = @buffer_size.times.map do
222
+ FFmpeg::Frame::Video.new :stream => self,
223
+ :width => @width,
224
+ :height => @height,
225
+ :pixel_format => @pixel_format
226
+ end
227
+
228
+ @swscale_ctx = sws_getContext(@av_codec_ctx[:width],
229
+ @av_codec_ctx[:height],
230
+ @av_codec_ctx[:pix_fmt],
231
+ @width, @height, @pixel_format,
232
+ :bicubic, nil, nil, nil) or
233
+ raise NoMemoryError, "sws_getContext() failed"
234
+ end
235
+ end
236
+
237
+ class FFmpeg::Stream::Unsupported
238
+ include FFmpeg::Stream
239
+
240
+ def initialize(p={})
241
+ super(p)
242
+ self.discard = :all
243
+ end
244
+ end
@@ -0,0 +1,3 @@
1
+ module FFmpeg
2
+ VERSION = "0.5"
3
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ffi-ffmpeg
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.8135c9c5dc36
5
+ prerelease: 8
6
+ platform: ruby
7
+ authors:
8
+ - David M. Lary
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-01 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ffi
16
+ requirement: &12966700 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *12966700
25
+ description: ! "Ruby FFI bindings for FFmpeg libraries:\n * libavformat\n
26
+ \ * libavcodec\n * libavutil\n *
27
+ libswscale"
28
+ email:
29
+ - dmlary@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - .hgignore
35
+ - Gemfile
36
+ - README.md
37
+ - Rakefile
38
+ - examples/extract_keyframes.rb
39
+ - examples/read_file.rb
40
+ - examples/seek.rb
41
+ - ffi-ffmpeg.gemspec
42
+ - lib/ffi-ffmpeg.rb
43
+ - lib/ffi/ffmpeg.rb
44
+ - lib/ffmpeg/frame.rb
45
+ - lib/ffmpeg/reader.rb
46
+ - lib/ffmpeg/stream.rb
47
+ - lib/ffmpeg/version.rb
48
+ homepage: http://bitbucket.com/dmlary/ffi-ffmpeg
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>'
64
+ - !ruby/object:Gem::Version
65
+ version: 1.3.1
66
+ requirements: []
67
+ rubyforge_project: ffi-ffmpeg
68
+ rubygems_version: 1.8.10
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Ruby FFI bindings for FFmpeg libraries
72
+ test_files: []