hornetseye-ffmpeg 0.4.3 → 0.5.1
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.
- data/Rakefile +7 -7
- data/ext/avinput.cc +246 -82
- data/ext/avinput.hh +28 -12
- data/ext/avoutput.cc +7 -4
- data/ext/sequence.cc +50 -0
- data/ext/sequence.hh +39 -0
- data/lib/hornetseye-ffmpeg/avinput.rb +42 -10
- data/lib/hornetseye_ffmpeg_ext.rb +1 -0
- metadata +12 -10
data/Rakefile
CHANGED
@@ -7,7 +7,7 @@ require 'rake/loaders/makefile'
|
|
7
7
|
require 'rbconfig'
|
8
8
|
|
9
9
|
PKG_NAME = 'hornetseye-ffmpeg'
|
10
|
-
PKG_VERSION = '0.
|
10
|
+
PKG_VERSION = '0.5.1'
|
11
11
|
CXX = ENV[ 'CXX' ] || 'g++'
|
12
12
|
STRIP = ENV[ 'STRIP' ] || 'strip'
|
13
13
|
RB_FILES = FileList[ 'lib/**/*.rb' ]
|
@@ -207,9 +207,9 @@ begin
|
|
207
207
|
s.has_rdoc = 'yard'
|
208
208
|
s.extra_rdoc_files = []
|
209
209
|
s.rdoc_options = %w{--no-private}
|
210
|
-
s.add_dependency %<malloc>, [ '~> 1.
|
211
|
-
s.add_dependency %<multiarray>, [ '~> 0.
|
212
|
-
s.add_dependency %<hornetseye-frame>, [ '~> 0.
|
210
|
+
s.add_dependency %<malloc>, [ '~> 1.2' ]
|
211
|
+
s.add_dependency %<multiarray>, [ '~> 0.11' ]
|
212
|
+
s.add_dependency %<hornetseye-frame>, [ '~> 0.7' ]
|
213
213
|
s.add_development_dependency %q{rake}
|
214
214
|
end
|
215
215
|
GEM_SOURCE = "#{PKG_NAME}-#{PKG_VERSION}.gem"
|
@@ -230,9 +230,9 @@ begin
|
|
230
230
|
s.has_rdoc = 'yard'
|
231
231
|
s.extra_rdoc_files = []
|
232
232
|
s.rdoc_options = %w{--no-private}
|
233
|
-
s.add_dependency %<malloc>, [ '~> 1.
|
234
|
-
s.add_dependency %<multiarray>, [ '~> 0.
|
235
|
-
s.add_dependency %<hornetseye-frame>, [ '~> 0.
|
233
|
+
s.add_dependency %<malloc>, [ '~> 1.2' ]
|
234
|
+
s.add_dependency %<multiarray>, [ '~> 0.11' ]
|
235
|
+
s.add_dependency %<hornetseye-frame>, [ '~> 0.7' ]
|
236
236
|
end
|
237
237
|
GEM_BINARY = "#{PKG_NAME}-#{PKG_VERSION}-#{$BINSPEC.platform}.gem"
|
238
238
|
desc "Build the gem file #{GEM_SOURCE}"
|
data/ext/avinput.cc
CHANGED
@@ -13,7 +13,9 @@
|
|
13
13
|
|
14
14
|
You should have received a copy of the GNU General Public License
|
15
15
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
16
|
+
#ifndef NDEBUG
|
16
17
|
#include <iostream>
|
18
|
+
#endif
|
17
19
|
#include "avinput.hh"
|
18
20
|
|
19
21
|
#if !defined(INT64_C)
|
@@ -27,35 +29,59 @@ using namespace std;
|
|
27
29
|
VALUE AVInput::cRubyClass = Qnil;
|
28
30
|
|
29
31
|
AVInput::AVInput( const string &mrl ) throw (Error):
|
30
|
-
m_mrl( mrl ), m_ic( NULL ),
|
31
|
-
|
32
|
+
m_mrl( mrl ), m_ic( NULL ), m_videoDec( NULL ), m_audioDec( NULL ),
|
33
|
+
m_videoCodec( NULL ), m_audioCodec( NULL ),
|
34
|
+
m_videoStream( -1 ), m_audioStream( -1 ), m_videoPts( 0 ), m_audioPts( 0 ),
|
35
|
+
m_swsContext( NULL ), m_avFrame( NULL )
|
32
36
|
{
|
33
37
|
try {
|
34
38
|
int err = av_open_input_file( &m_ic, mrl.c_str(), NULL, 0, NULL );
|
35
39
|
ERRORMACRO( err >= 0, Error, , "Error opening file \"" << mrl << "\": "
|
36
|
-
<< strerror(
|
40
|
+
<< strerror( errno ) );
|
37
41
|
err = av_find_stream_info( m_ic );
|
38
42
|
ERRORMACRO( err >= 0, Error, , "Error finding stream info for file \""
|
39
|
-
<< mrl << "\": " << strerror(
|
40
|
-
for ( int i=0; i<m_ic->nb_streams; i++ )
|
41
|
-
if ( m_ic->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
42
|
-
|
43
|
-
|
43
|
+
<< mrl << "\": " << strerror( errno ) );
|
44
|
+
for ( int i=0; i<m_ic->nb_streams; i++ ) {
|
45
|
+
if ( m_ic->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO )
|
46
|
+
m_videoStream = i;
|
47
|
+
if ( m_ic->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO )
|
48
|
+
m_audioStream = i;
|
49
|
+
};
|
50
|
+
#ifndef NDEBUG
|
51
|
+
cerr << "Video stream index is " << m_videoStream << endl;
|
52
|
+
cerr << "Audio stream index is " << m_audioStream << endl;
|
53
|
+
#endif
|
54
|
+
ERRORMACRO( m_videoStream >= 0, Error, ,
|
55
|
+
"Could not find video stream in file \"" << mrl << "\"" );
|
56
|
+
m_videoDec = m_ic->streams[ m_videoStream ]->codec;
|
57
|
+
if ( m_audioStream >= 0 )
|
58
|
+
m_audioDec = m_ic->streams[ m_audioStream ]->codec;
|
59
|
+
m_videoCodec = avcodec_find_decoder( m_videoDec->codec_id );
|
60
|
+
ERRORMACRO( m_videoCodec != NULL, Error, , "Could not find video decoder for "
|
61
|
+
"file \"" << mrl << "\"" );
|
62
|
+
err = avcodec_open( m_videoDec, m_videoCodec );
|
63
|
+
if ( err < 0 ) {
|
64
|
+
m_videoCodec = NULL;
|
65
|
+
ERRORMACRO( false, Error, , "Error opening video codec for file \""
|
66
|
+
<< mrl << "\": " << strerror( errno ) );
|
67
|
+
};
|
68
|
+
if ( m_audioDec != NULL ) {
|
69
|
+
m_audioCodec = avcodec_find_decoder( m_audioDec->codec_id );
|
70
|
+
ERRORMACRO( m_audioCodec != NULL, Error, , "Could not find audio decoder for "
|
71
|
+
"file \"" << mrl << "\"" );
|
72
|
+
err = avcodec_open( m_audioDec, m_audioCodec );
|
73
|
+
if ( err < 0 ) {
|
74
|
+
m_audioCodec = NULL;
|
75
|
+
ERRORMACRO( false, Error, , "Error opening audio codec for file \""
|
76
|
+
<< mrl << "\": " << strerror( errno ) );
|
44
77
|
};
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
ERRORMACRO( err >= 0, Error, , "Error opening codec for file \""
|
53
|
-
<< mrl << "\": " << strerror( -err ) );
|
54
|
-
m_swsContext = sws_getContext( m_dec->width, m_dec->height, m_dec->pix_fmt,
|
55
|
-
m_dec->width, m_dec->height, PIX_FMT_YUV420P,
|
56
|
-
SWS_FAST_BILINEAR, 0, 0, 0 );
|
57
|
-
m_frame = avcodec_alloc_frame();
|
58
|
-
ERRORMACRO( m_frame, Error, , "Error allocating frame" );
|
78
|
+
};
|
79
|
+
m_swsContext = sws_getContext( m_videoDec->width, m_videoDec->height,
|
80
|
+
m_videoDec->pix_fmt,
|
81
|
+
m_videoDec->width, m_videoDec->height,
|
82
|
+
PIX_FMT_YUV420P, SWS_FAST_BILINEAR, 0, 0, 0 );
|
83
|
+
m_avFrame = avcodec_alloc_frame();
|
84
|
+
ERRORMACRO( m_avFrame, Error, , "Error allocating frame" );
|
59
85
|
} catch ( Error &e ) {
|
60
86
|
close();
|
61
87
|
throw e;
|
@@ -69,66 +95,110 @@ AVInput::~AVInput(void)
|
|
69
95
|
|
70
96
|
void AVInput::close(void)
|
71
97
|
{
|
72
|
-
|
73
|
-
|
74
|
-
|
98
|
+
m_audioFrame.reset();
|
99
|
+
m_videoFrame.reset();
|
100
|
+
if ( m_avFrame ) {
|
101
|
+
av_free( m_avFrame );
|
102
|
+
m_avFrame = NULL;
|
75
103
|
};
|
76
104
|
if ( m_swsContext ) {
|
77
105
|
sws_freeContext( m_swsContext );
|
78
106
|
m_swsContext = NULL;
|
79
107
|
};
|
80
|
-
if (
|
81
|
-
|
82
|
-
|
83
|
-
|
108
|
+
if ( m_audioCodec ) {
|
109
|
+
avcodec_close( m_audioDec );
|
110
|
+
m_audioCodec = NULL;
|
111
|
+
};
|
112
|
+
if ( m_videoCodec ) {
|
113
|
+
m_ic->streams[ m_videoStream ]->discard = AVDISCARD_ALL;
|
114
|
+
avcodec_close( m_videoDec );
|
115
|
+
m_videoCodec = NULL;
|
84
116
|
};
|
85
|
-
|
86
|
-
|
117
|
+
m_audioDec = NULL;
|
118
|
+
m_videoDec = NULL;
|
119
|
+
m_audioStream = -1;
|
120
|
+
m_videoStream = -1;
|
87
121
|
if ( m_ic ) {
|
88
122
|
av_close_input_file( m_ic );
|
89
123
|
m_ic = NULL;
|
90
124
|
};
|
91
125
|
}
|
92
126
|
|
93
|
-
|
127
|
+
void AVInput::readAV(void) throw (Error)
|
94
128
|
{
|
129
|
+
m_audioFrame.reset();
|
130
|
+
m_videoFrame.reset();
|
95
131
|
ERRORMACRO( m_ic != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
96
132
|
"Did you call \"close\" before?" );
|
97
|
-
FramePtr retVal;
|
98
133
|
AVPacket packet;
|
134
|
+
long long firstPacketPts = AV_NOPTS_VALUE;
|
99
135
|
while ( av_read_frame( m_ic, &packet ) >= 0 ) {
|
100
|
-
if ( packet.stream_index ==
|
136
|
+
if ( packet.stream_index == m_videoStream ) {
|
101
137
|
int frameFinished;
|
102
|
-
int err = avcodec_decode_video(
|
138
|
+
int err = avcodec_decode_video( m_videoDec, m_avFrame, &frameFinished,
|
103
139
|
packet.data, packet.size );
|
104
140
|
ERRORMACRO( err >= 0, Error, ,
|
105
|
-
"Error decoding frame of video \"" << m_mrl << "\"" );
|
106
|
-
|
141
|
+
"Error decoding video frame of video \"" << m_mrl << "\"" );
|
142
|
+
if ( firstPacketPts == AV_NOPTS_VALUE ) firstPacketPts = packet.pts;
|
107
143
|
if ( frameFinished ) {
|
108
|
-
if ( packet.dts != AV_NOPTS_VALUE )
|
144
|
+
if ( packet.dts != AV_NOPTS_VALUE )
|
145
|
+
m_videoPts = packet.dts;
|
146
|
+
else
|
147
|
+
m_videoPts = firstPacketPts;
|
109
148
|
av_free_packet( &packet );
|
110
149
|
AVFrame picture;
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
picture.data[
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
picture.linesize[0] =
|
119
|
-
picture.linesize[1] =
|
120
|
-
picture.linesize[2] =
|
121
|
-
sws_scale( m_swsContext,
|
122
|
-
|
123
|
-
retVal = FramePtr( new Frame( "YV12", m_dec->width, m_dec->height,
|
124
|
-
m_data.get() ) );
|
150
|
+
m_videoFrame = FramePtr( new Frame( "YV12", m_videoDec->width,
|
151
|
+
m_videoDec->height ) );
|
152
|
+
picture.data[0] = (uint8_t *)m_videoFrame->data();
|
153
|
+
picture.data[1] = (uint8_t *)m_videoFrame->data() +
|
154
|
+
m_videoDec->width * m_videoDec->height * 5 / 4;
|
155
|
+
picture.data[2] = (uint8_t *)m_videoFrame->data() +
|
156
|
+
m_videoDec->width * m_videoDec->height;
|
157
|
+
picture.linesize[0] = m_videoDec->width;
|
158
|
+
picture.linesize[1] = m_videoDec->width / 2;
|
159
|
+
picture.linesize[2] = m_videoDec->width / 2;
|
160
|
+
sws_scale( m_swsContext, m_avFrame->data, m_avFrame->linesize, 0,
|
161
|
+
m_videoDec->height, picture.data, picture.linesize );
|
125
162
|
break;
|
163
|
+
} else
|
164
|
+
av_free_packet( &packet );
|
165
|
+
} else if ( packet.stream_index == m_audioStream ) {
|
166
|
+
if ( packet.pts != AV_NOPTS_VALUE ) m_audioPts = packet.pts;
|
167
|
+
m_audioFrame.reset();
|
168
|
+
unsigned char *data = packet.data;
|
169
|
+
int size = packet.size;
|
170
|
+
while ( size > 0 ) {
|
171
|
+
short int buffer[ ( AVCODEC_MAX_AUDIO_FRAME_SIZE +
|
172
|
+
FF_INPUT_BUFFER_PADDING_SIZE ) / sizeof( short int ) ];
|
173
|
+
int bufSize = sizeof( buffer );
|
174
|
+
int len = avcodec_decode_audio2( m_audioDec, &buffer[0], &bufSize,
|
175
|
+
data, size );
|
176
|
+
if ( len < 0 ) {
|
177
|
+
m_audioFrame.reset();
|
178
|
+
ERRORMACRO( false, Error, , "Error decoding audio frame of video \""
|
179
|
+
<< m_mrl << "\"" );
|
180
|
+
};
|
181
|
+
data += len;
|
182
|
+
size -= len;
|
183
|
+
if ( bufSize > 0 ) {
|
184
|
+
if ( m_audioFrame.get() ) {
|
185
|
+
SequencePtr extended( new Sequence( m_audioFrame->size() + bufSize ) );
|
186
|
+
memcpy( extended->data(), m_audioFrame->data(), m_audioFrame->size() );
|
187
|
+
memcpy( extended->data() + m_audioFrame->size(), &buffer[0], bufSize );
|
188
|
+
m_audioFrame = extended;
|
189
|
+
} else {
|
190
|
+
m_audioFrame = SequencePtr( new Sequence( bufSize ) );
|
191
|
+
memcpy( m_audioFrame->data(), &buffer[0], bufSize );
|
192
|
+
};
|
193
|
+
};
|
126
194
|
};
|
127
|
-
|
128
|
-
|
195
|
+
av_free_packet( &packet );
|
196
|
+
if ( m_audioFrame.get() ) break;
|
197
|
+
} else
|
198
|
+
av_free_packet( &packet );
|
129
199
|
};
|
130
|
-
ERRORMACRO(
|
131
|
-
|
200
|
+
ERRORMACRO( m_videoFrame.get() || m_audioFrame.get(), Error, ,
|
201
|
+
"No more frames available" );
|
132
202
|
}
|
133
203
|
|
134
204
|
bool AVInput::status(void) const
|
@@ -138,44 +208,65 @@ bool AVInput::status(void) const
|
|
138
208
|
|
139
209
|
int AVInput::width(void) const throw (Error)
|
140
210
|
{
|
141
|
-
ERRORMACRO(
|
211
|
+
ERRORMACRO( m_videoDec != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
142
212
|
"Did you call \"close\" before?" );
|
143
|
-
return
|
213
|
+
return m_videoDec->width;
|
144
214
|
}
|
145
215
|
|
146
216
|
int AVInput::height(void) const throw (Error)
|
147
217
|
{
|
148
|
-
ERRORMACRO(
|
218
|
+
ERRORMACRO( m_videoDec != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
149
219
|
"Did you call \"close\" before?" );
|
150
|
-
return
|
220
|
+
return m_videoDec->height;
|
151
221
|
}
|
152
222
|
|
153
|
-
AVRational AVInput::
|
223
|
+
AVRational AVInput::videoTimeBase(void) throw (Error)
|
154
224
|
{
|
155
|
-
ERRORMACRO(
|
225
|
+
ERRORMACRO( m_videoStream != -1, Error, , "Video \"" << m_mrl << "\" is not open. "
|
156
226
|
"Did you call \"close\" before?" );
|
157
|
-
return m_ic->streams[
|
227
|
+
return m_ic->streams[ m_videoStream ]->time_base;
|
228
|
+
}
|
229
|
+
|
230
|
+
AVRational AVInput::audioTimeBase(void) throw (Error)
|
231
|
+
{
|
232
|
+
ERRORMACRO( m_audioStream != -1, Error, , "Audio \"" << m_mrl << "\" is not open. "
|
233
|
+
"Did you call \"close\" before?" );
|
234
|
+
return m_ic->streams[ m_audioStream ]->time_base;
|
158
235
|
}
|
159
236
|
|
160
237
|
AVRational AVInput::frameRate(void) throw (Error)
|
161
238
|
{
|
162
239
|
ERRORMACRO( m_ic != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
163
240
|
"Did you call \"close\" before?" );
|
164
|
-
return m_ic->streams[
|
241
|
+
return m_ic->streams[ m_videoStream ]->r_frame_rate;
|
242
|
+
}
|
243
|
+
|
244
|
+
int AVInput::sampleRate(void) throw (Error)
|
245
|
+
{
|
246
|
+
ERRORMACRO( m_audioDec != NULL, Error, , "Audio \"" << m_mrl << "\" is not open. "
|
247
|
+
"Did you call \"close\" before?" );
|
248
|
+
return m_audioDec->sample_rate;
|
249
|
+
}
|
250
|
+
|
251
|
+
int AVInput::channels(void) throw (Error)
|
252
|
+
{
|
253
|
+
ERRORMACRO( m_audioDec != NULL, Error, , "Audio \"" << m_mrl << "\" is not open. "
|
254
|
+
"Did you call \"close\" before?" );
|
255
|
+
return m_audioDec->channels;
|
165
256
|
}
|
166
257
|
|
167
258
|
long long AVInput::duration(void) throw (Error)
|
168
259
|
{
|
169
260
|
ERRORMACRO( m_ic != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
170
261
|
"Did you call \"close\" before?" );
|
171
|
-
return m_ic->streams[
|
262
|
+
return m_ic->streams[ m_videoStream ]->duration;
|
172
263
|
}
|
173
264
|
|
174
265
|
long long AVInput::startTime(void) throw (Error)
|
175
266
|
{
|
176
267
|
ERRORMACRO( m_ic != NULL, Error, , "Video \"" << m_mrl << "\" is not open. "
|
177
268
|
"Did you call \"close\" before?" );
|
178
|
-
return m_ic->streams[
|
269
|
+
return m_ic->streams[ m_videoStream ]->start_time;
|
179
270
|
}
|
180
271
|
|
181
272
|
void AVInput::seek( long long timestamp ) throw (Error)
|
@@ -184,14 +275,21 @@ void AVInput::seek( long long timestamp ) throw (Error)
|
|
184
275
|
"Did you call \"close\" before?" );
|
185
276
|
ERRORMACRO( av_seek_frame( m_ic, -1, timestamp, 0 ) >= 0,
|
186
277
|
Error, , "Error seeking in video \"" << m_mrl << "\"" );
|
187
|
-
avcodec_flush_buffers(
|
278
|
+
avcodec_flush_buffers( m_videoDec );
|
188
279
|
}
|
189
280
|
|
190
|
-
long long AVInput::
|
281
|
+
long long AVInput::videoPts(void) throw (Error)
|
191
282
|
{
|
192
|
-
ERRORMACRO(
|
283
|
+
ERRORMACRO( m_videoStream != -1, Error, , "Video \"" << m_mrl << "\" is not open. "
|
284
|
+
"Did you call \"close\" before?" );
|
285
|
+
return m_videoPts;
|
286
|
+
}
|
287
|
+
|
288
|
+
long long AVInput::audioPts(void) throw (Error)
|
289
|
+
{
|
290
|
+
ERRORMACRO( m_audioStream != -1, Error, , "Audio \"" << m_mrl << "\" is not open. "
|
193
291
|
"Did you call \"close\" before?" );
|
194
|
-
return
|
292
|
+
return m_audioPts;
|
195
293
|
}
|
196
294
|
|
197
295
|
VALUE AVInput::registerRubyClass( VALUE rbModule )
|
@@ -202,16 +300,22 @@ VALUE AVInput::registerRubyClass( VALUE rbModule )
|
|
202
300
|
rb_define_const( cRubyClass, "AV_TIME_BASE", INT2NUM( AV_TIME_BASE ) );
|
203
301
|
rb_define_const( cRubyClass, "AV_NOPTS_VALUE", LL2NUM( AV_NOPTS_VALUE ) );
|
204
302
|
rb_define_method( cRubyClass, "close", RUBY_METHOD_FUNC( wrapClose ), 0 );
|
205
|
-
rb_define_method( cRubyClass, "
|
303
|
+
rb_define_method( cRubyClass, "read_av", RUBY_METHOD_FUNC( wrapReadAV ), 0 );
|
206
304
|
rb_define_method( cRubyClass, "status?", RUBY_METHOD_FUNC( wrapStatus ), 0 );
|
207
|
-
rb_define_method( cRubyClass, "
|
305
|
+
rb_define_method( cRubyClass, "video_time_base",
|
306
|
+
RUBY_METHOD_FUNC( wrapVideoTimeBase ), 0 );
|
307
|
+
rb_define_method( cRubyClass, "audio_time_base",
|
308
|
+
RUBY_METHOD_FUNC( wrapAudioTimeBase ), 0 );
|
208
309
|
rb_define_method( cRubyClass, "frame_rate", RUBY_METHOD_FUNC( wrapFrameRate ), 0 );
|
310
|
+
rb_define_method( cRubyClass, "sample_rate", RUBY_METHOD_FUNC( wrapSampleRate ), 0 );
|
311
|
+
rb_define_method( cRubyClass, "channels", RUBY_METHOD_FUNC( wrapChannels ), 0 );
|
209
312
|
rb_define_method( cRubyClass, "duration", RUBY_METHOD_FUNC( wrapDuration ), 0 );
|
210
313
|
rb_define_method( cRubyClass, "start_time", RUBY_METHOD_FUNC( wrapStartTime ), 0 );
|
211
314
|
rb_define_method( cRubyClass, "width", RUBY_METHOD_FUNC( wrapWidth ), 0 );
|
212
315
|
rb_define_method( cRubyClass, "height", RUBY_METHOD_FUNC( wrapHeight ), 0 );
|
213
316
|
rb_define_method( cRubyClass, "seek", RUBY_METHOD_FUNC( wrapSeek ), 1 );
|
214
|
-
rb_define_method( cRubyClass, "
|
317
|
+
rb_define_method( cRubyClass, "video_pts", RUBY_METHOD_FUNC( wrapVideoPTS ), 0 );
|
318
|
+
rb_define_method( cRubyClass, "audio_pts", RUBY_METHOD_FUNC( wrapAudioPTS ), 0 );
|
215
319
|
}
|
216
320
|
|
217
321
|
void AVInput::deleteRubyObject( void *ptr )
|
@@ -240,13 +344,23 @@ VALUE AVInput::wrapClose( VALUE rbSelf )
|
|
240
344
|
return rbSelf;
|
241
345
|
}
|
242
346
|
|
243
|
-
VALUE AVInput::
|
347
|
+
VALUE AVInput::wrapReadAV( VALUE rbSelf )
|
348
|
+
{
|
349
|
+
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
350
|
+
return (*self)->wrapReadAVInst();
|
351
|
+
}
|
352
|
+
|
353
|
+
VALUE AVInput::wrapReadAVInst(void)
|
244
354
|
{
|
245
355
|
VALUE retVal = Qnil;
|
246
356
|
try {
|
247
|
-
|
248
|
-
|
249
|
-
|
357
|
+
readAV();
|
358
|
+
if ( m_videoFrame.get() )
|
359
|
+
retVal = m_videoFrame->rubyObject();
|
360
|
+
if ( m_audioFrame.get() )
|
361
|
+
retVal = m_audioFrame->rubyObject();
|
362
|
+
m_audioFrame.reset();
|
363
|
+
m_videoFrame.reset();
|
250
364
|
} catch ( exception &e ) {
|
251
365
|
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
252
366
|
};
|
@@ -259,14 +373,28 @@ VALUE AVInput::wrapStatus( VALUE rbSelf )
|
|
259
373
|
return (*self)->status() ? Qtrue : Qfalse;
|
260
374
|
}
|
261
375
|
|
262
|
-
VALUE AVInput::
|
376
|
+
VALUE AVInput::wrapVideoTimeBase( VALUE rbSelf )
|
263
377
|
{
|
264
378
|
VALUE retVal = Qnil;
|
265
379
|
try {
|
266
380
|
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
267
|
-
AVRational
|
381
|
+
AVRational videoTimeBase = (*self)->videoTimeBase();
|
268
382
|
retVal = rb_funcall( rb_cObject, rb_intern( "Rational" ), 2,
|
269
|
-
INT2NUM(
|
383
|
+
INT2NUM( videoTimeBase.num ), INT2NUM( videoTimeBase.den ) );
|
384
|
+
} catch( exception &e ) {
|
385
|
+
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
386
|
+
};
|
387
|
+
return retVal;
|
388
|
+
}
|
389
|
+
|
390
|
+
VALUE AVInput::wrapAudioTimeBase( VALUE rbSelf )
|
391
|
+
{
|
392
|
+
VALUE retVal = Qnil;
|
393
|
+
try {
|
394
|
+
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
395
|
+
AVRational audioTimeBase = (*self)->audioTimeBase();
|
396
|
+
retVal = rb_funcall( rb_cObject, rb_intern( "Rational" ), 2,
|
397
|
+
INT2NUM( audioTimeBase.num ), INT2NUM( audioTimeBase.den ) );
|
270
398
|
} catch( exception &e ) {
|
271
399
|
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
272
400
|
};
|
@@ -287,6 +415,30 @@ VALUE AVInput::wrapFrameRate( VALUE rbSelf )
|
|
287
415
|
return retVal;
|
288
416
|
}
|
289
417
|
|
418
|
+
VALUE AVInput::wrapSampleRate( VALUE rbSelf )
|
419
|
+
{
|
420
|
+
VALUE retVal = Qnil;
|
421
|
+
try {
|
422
|
+
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
423
|
+
retVal = INT2NUM( (*self)->sampleRate() );
|
424
|
+
} catch( exception &e ) {
|
425
|
+
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
426
|
+
};
|
427
|
+
return retVal;
|
428
|
+
}
|
429
|
+
|
430
|
+
VALUE AVInput::wrapChannels( VALUE rbSelf )
|
431
|
+
{
|
432
|
+
VALUE retVal = Qnil;
|
433
|
+
try {
|
434
|
+
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
435
|
+
retVal = INT2NUM( (*self)->channels() );
|
436
|
+
} catch( exception &e ) {
|
437
|
+
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
438
|
+
};
|
439
|
+
return retVal;
|
440
|
+
}
|
441
|
+
|
290
442
|
VALUE AVInput::wrapDuration( VALUE rbSelf )
|
291
443
|
{
|
292
444
|
VALUE retVal = Qnil;
|
@@ -347,12 +499,24 @@ VALUE AVInput::wrapSeek( VALUE rbSelf, VALUE rbPos )
|
|
347
499
|
return retVal;
|
348
500
|
}
|
349
501
|
|
350
|
-
VALUE AVInput::
|
502
|
+
VALUE AVInput::wrapVideoPTS( VALUE rbSelf )
|
503
|
+
{
|
504
|
+
VALUE retVal = Qnil;
|
505
|
+
try {
|
506
|
+
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
507
|
+
retVal = LL2NUM( (*self)->videoPts() );
|
508
|
+
} catch( exception &e ) {
|
509
|
+
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
510
|
+
};
|
511
|
+
return retVal;
|
512
|
+
}
|
513
|
+
|
514
|
+
VALUE AVInput::wrapAudioPTS( VALUE rbSelf )
|
351
515
|
{
|
352
516
|
VALUE retVal = Qnil;
|
353
517
|
try {
|
354
518
|
AVInputPtr *self; Data_Get_Struct( rbSelf, AVInputPtr, self );
|
355
|
-
retVal = LL2NUM( (*self)->
|
519
|
+
retVal = LL2NUM( (*self)->audioPts() );
|
356
520
|
} catch( exception &e ) {
|
357
521
|
rb_raise( rb_eRuntimeError, "%s", e.what() );
|
358
522
|
};
|
data/ext/avinput.hh
CHANGED
@@ -36,6 +36,7 @@ extern "C" {
|
|
36
36
|
#include "rubyinc.hh"
|
37
37
|
#include "error.hh"
|
38
38
|
#include "frame.hh"
|
39
|
+
#include "sequence.hh"
|
39
40
|
|
40
41
|
class AVInput
|
41
42
|
{
|
@@ -43,43 +44,58 @@ public:
|
|
43
44
|
AVInput( const std::string &mrl ) throw (Error);
|
44
45
|
virtual ~AVInput(void);
|
45
46
|
void close(void);
|
46
|
-
|
47
|
+
void readAV(void) throw (Error);
|
47
48
|
bool status(void) const;
|
48
49
|
int width(void) const throw (Error);
|
49
50
|
int height(void) const throw (Error);
|
50
|
-
AVRational
|
51
|
+
AVRational videoTimeBase(void) throw (Error);
|
52
|
+
AVRational audioTimeBase(void) throw (Error);
|
51
53
|
AVRational frameRate(void) throw (Error);
|
54
|
+
int sampleRate(void) throw (Error);
|
55
|
+
int channels(void) throw (Error);
|
52
56
|
long long duration(void) throw (Error);
|
53
57
|
long long startTime(void) throw (Error);
|
54
58
|
void seek( long long timestamp ) throw (Error);
|
55
|
-
long long
|
59
|
+
long long videoPts(void) throw (Error);
|
60
|
+
long long audioPts(void) throw (Error);
|
56
61
|
static VALUE cRubyClass;
|
57
62
|
static VALUE registerRubyClass( VALUE rbModule );
|
58
63
|
static void deleteRubyObject( void *ptr );
|
59
64
|
static VALUE wrapNew( VALUE rbClass, VALUE rbMRL );
|
60
65
|
static VALUE wrapClose( VALUE rbSelf );
|
61
|
-
static VALUE
|
66
|
+
static VALUE wrapReadAV( VALUE rbSelf );
|
67
|
+
VALUE wrapReadAVInst(void);
|
62
68
|
static VALUE wrapStatus( VALUE rbSelf );
|
63
|
-
static VALUE
|
69
|
+
static VALUE wrapVideoTimeBase( VALUE rbSelf );
|
70
|
+
static VALUE wrapAudioTimeBase( VALUE rbSelf );
|
64
71
|
static VALUE wrapFrameRate( VALUE rbSelf );
|
72
|
+
static VALUE wrapSampleRate( VALUE rbSelf );
|
73
|
+
static VALUE wrapChannels( VALUE rbSelf );
|
65
74
|
static VALUE wrapDuration( VALUE rbSelf );
|
66
75
|
static VALUE wrapStartTime( VALUE rbSelf );
|
67
76
|
static VALUE wrapWidth( VALUE rbSelf );
|
68
77
|
static VALUE wrapHeight( VALUE rbSelf );
|
69
78
|
static VALUE wrapSeek( VALUE rbSelf, VALUE rbPos );
|
70
|
-
static VALUE
|
79
|
+
static VALUE wrapVideoPTS( VALUE rbSelf );
|
80
|
+
static VALUE wrapAudioPTS( VALUE rbSelf );
|
71
81
|
protected:
|
72
82
|
std::string m_mrl;
|
73
83
|
AVFormatContext *m_ic;
|
74
|
-
AVCodecContext *
|
75
|
-
|
76
|
-
|
77
|
-
|
84
|
+
AVCodecContext *m_videoDec;
|
85
|
+
AVCodecContext *m_audioDec;
|
86
|
+
AVCodec *m_videoCodec;
|
87
|
+
AVCodec *m_audioCodec;
|
88
|
+
int m_videoStream;
|
89
|
+
int m_audioStream;
|
90
|
+
long long m_videoPts;
|
91
|
+
long long m_audioPts;
|
78
92
|
struct SwsContext *m_swsContext;
|
79
|
-
AVFrame *
|
80
|
-
|
93
|
+
AVFrame *m_avFrame;
|
94
|
+
FramePtr m_videoFrame;
|
95
|
+
SequencePtr m_audioFrame;
|
81
96
|
};
|
82
97
|
|
83
98
|
typedef boost::shared_ptr< AVInput > AVInputPtr;
|
84
99
|
|
85
100
|
#endif
|
101
|
+
|
data/ext/avoutput.cc
CHANGED
@@ -13,6 +13,7 @@
|
|
13
13
|
|
14
14
|
You should have received a copy of the GNU General Public License
|
15
15
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
16
|
+
#include <cerrno>
|
16
17
|
#include "avoutput.hh"
|
17
18
|
|
18
19
|
#if !defined(INT64_C)
|
@@ -62,11 +63,11 @@ AVOutput::AVOutput( const string &mrl, int bitrate, int width, int height,
|
|
62
63
|
if ( m_oc->oformat->flags & AVFMT_GLOBALHEADER )
|
63
64
|
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
64
65
|
ERRORMACRO( av_set_parameters( m_oc, NULL ) >= 0, Error, ,
|
65
|
-
"Invalid output format parameters" );
|
66
|
+
"Invalid output format parameters: " << strerror( errno ) );
|
66
67
|
AVCodec *codec = avcodec_find_encoder( c->codec_id );
|
67
68
|
ERRORMACRO( codec != NULL, Error, , "Could not find codec " << c->codec_id );
|
68
69
|
ERRORMACRO( avcodec_open( c, codec ) >= 0, Error, ,
|
69
|
-
"Error opening codec " << c->codec_id );
|
70
|
+
"Error opening codec " << c->codec_id << ": " << strerror( errno ) );
|
70
71
|
m_video_codec_open = true;
|
71
72
|
if ( !( m_oc->oformat->flags & AVFMT_RAWPICTURE ) ) {
|
72
73
|
m_video_buf = (char *)av_malloc( VIDEO_BUF_SIZE );
|
@@ -79,7 +80,8 @@ AVOutput::AVOutput( const string &mrl, int bitrate, int width, int height,
|
|
79
80
|
m_file_open = true;
|
80
81
|
};
|
81
82
|
ERRORMACRO( av_write_header( m_oc ) >= 0, Error, ,
|
82
|
-
"Error writing header of video \"" << mrl << "\""
|
83
|
+
"Error writing header of video \"" << mrl << "\": "
|
84
|
+
<< strerror( errno ) );
|
83
85
|
m_header_written = true;
|
84
86
|
m_swsContext = sws_getContext( width, height, PIX_FMT_YUV420P,
|
85
87
|
width, height, PIX_FMT_YUV420P,
|
@@ -184,7 +186,8 @@ void AVOutput::write( FramePtr frame ) throw (Error)
|
|
184
186
|
packet.data = (uint8_t *)m_video_buf;
|
185
187
|
packet.size = packetSize;
|
186
188
|
ERRORMACRO( av_interleaved_write_frame( m_oc, &packet ) >= 0, Error, ,
|
187
|
-
"Error writing frame of video \"" << m_mrl << "\""
|
189
|
+
"Error writing frame of video \"" << m_mrl << "\": "
|
190
|
+
<< strerror( errno ) );
|
188
191
|
};
|
189
192
|
};
|
190
193
|
}
|
data/ext/sequence.cc
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
/* HornetsEye - Computer Vision with Ruby
|
2
|
+
Copyright (C) 2006, 2007, 2008, 2009, 2010 Jan Wedekind
|
3
|
+
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
16
|
+
#include "sequence.hh"
|
17
|
+
|
18
|
+
using namespace std;
|
19
|
+
|
20
|
+
Sequence::Sequence( int size ):
|
21
|
+
m_sequence( Qnil )
|
22
|
+
{
|
23
|
+
VALUE mModule = rb_define_module( "Hornetseye" );
|
24
|
+
VALUE cMalloc = rb_define_class_under( mModule, "Malloc", rb_cObject );
|
25
|
+
VALUE cSequence = rb_define_class_under( mModule, "Sequence", rb_cObject );
|
26
|
+
VALUE rbSize = INT2NUM( size );
|
27
|
+
VALUE rbMemory = rb_funcall( cMalloc, rb_intern( "new" ), 1, rbSize );
|
28
|
+
m_sequence = rb_funcall( cSequence, rb_intern( "import" ), 3,
|
29
|
+
rb_const_get( mModule, rb_intern( "UBYTE" ) ),
|
30
|
+
rbMemory, rbSize );
|
31
|
+
}
|
32
|
+
|
33
|
+
int Sequence::size(void)
|
34
|
+
{
|
35
|
+
return NUM2INT( rb_funcall( m_sequence, rb_intern( "size" ), 0 ) );
|
36
|
+
}
|
37
|
+
|
38
|
+
char *Sequence::data(void)
|
39
|
+
{
|
40
|
+
VALUE rbMemory = rb_funcall( m_sequence, rb_intern( "memory" ), 0 );
|
41
|
+
char *ptr;
|
42
|
+
Data_Get_Struct( rbMemory, char, ptr );
|
43
|
+
return ptr;
|
44
|
+
}
|
45
|
+
|
46
|
+
void Sequence::markRubyMember(void)
|
47
|
+
{
|
48
|
+
rb_gc_mark( m_sequence );
|
49
|
+
}
|
50
|
+
|
data/ext/sequence.hh
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
/* HornetsEye - Computer Vision with Ruby
|
2
|
+
Copyright (C) 2006, 2007, 2008, 2009, 2010 Jan Wedekind
|
3
|
+
|
4
|
+
This program is free software: you can redistribute it and/or modify
|
5
|
+
it under the terms of the GNU General Public License as published by
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
7
|
+
(at your option) any later version.
|
8
|
+
|
9
|
+
This program is distributed in the hope that it will be useful,
|
10
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
GNU General Public License for more details.
|
13
|
+
|
14
|
+
You should have received a copy of the GNU General Public License
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
16
|
+
#ifndef SEQUENCE_HH
|
17
|
+
#define SEQUENCE_HH
|
18
|
+
|
19
|
+
#include <boost/smart_ptr.hpp>
|
20
|
+
#include "rubyinc.hh"
|
21
|
+
#include <string>
|
22
|
+
|
23
|
+
class Sequence
|
24
|
+
{
|
25
|
+
public:
|
26
|
+
Sequence( int size );
|
27
|
+
Sequence( VALUE rbSequence ): m_sequence( rbSequence ) {}
|
28
|
+
virtual ~Sequence(void) {}
|
29
|
+
int size(void);
|
30
|
+
char *data(void);
|
31
|
+
VALUE rubyObject(void) { return m_sequence; }
|
32
|
+
void markRubyMember(void);
|
33
|
+
protected:
|
34
|
+
VALUE m_sequence;
|
35
|
+
};
|
36
|
+
|
37
|
+
typedef boost::shared_ptr< Sequence > SequencePtr;
|
38
|
+
|
39
|
+
#endif
|
@@ -25,7 +25,13 @@ module Hornetseye
|
|
25
25
|
|
26
26
|
def new( mrl )
|
27
27
|
retval = orig_new mrl
|
28
|
-
retval.instance_eval
|
28
|
+
retval.instance_eval do
|
29
|
+
@frame = nil
|
30
|
+
@video = Queue.new
|
31
|
+
@audio = Queue.new
|
32
|
+
@video_pts = AV_NOPTS_VALUE
|
33
|
+
@audio_pts = AV_NOPTS_VALUE
|
34
|
+
end
|
29
35
|
retval
|
30
36
|
end
|
31
37
|
|
@@ -35,36 +41,62 @@ module Hornetseye
|
|
35
41
|
[ width, height ]
|
36
42
|
end
|
37
43
|
|
38
|
-
|
44
|
+
def read_video
|
45
|
+
enqueue_frame while @video.empty?
|
46
|
+
frame, @video_pts = @video.deq
|
47
|
+
frame
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method :read, :read_video
|
39
51
|
|
40
|
-
def
|
41
|
-
|
52
|
+
def read_audio
|
53
|
+
enqueue_frame while @audio.empty?
|
54
|
+
frame, @audio_pts = @audio.deq
|
55
|
+
frame
|
56
|
+
end
|
57
|
+
|
58
|
+
def enqueue_frame
|
59
|
+
frame = read_av
|
60
|
+
if frame.is_a? Frame_
|
61
|
+
@video.enq [ frame, video_pts ]
|
62
|
+
@frame = frame
|
63
|
+
else
|
64
|
+
n = channels
|
65
|
+
target = Hornetseye::MultiArray SINT, n, frame.size / ( 2 * n )
|
66
|
+
@audio.enq [ target.new( frame.memory ), audio_pts ]
|
67
|
+
end
|
42
68
|
end
|
43
69
|
|
44
70
|
def pos=( timestamp )
|
45
71
|
unless @frame
|
46
72
|
begin
|
47
|
-
|
73
|
+
read_video
|
48
74
|
rescue Exception
|
49
75
|
end
|
50
76
|
end
|
51
77
|
seek timestamp * AV_TIME_BASE
|
52
78
|
end
|
53
79
|
|
54
|
-
def
|
55
|
-
|
80
|
+
def video_pos
|
81
|
+
@video_pts == AV_NOPTS_VALUE ? nil : @video_pts * video_time_base
|
82
|
+
end
|
83
|
+
|
84
|
+
alias_method :pos, :video_pos
|
85
|
+
|
86
|
+
def audio_pos
|
87
|
+
@audio_pts == AV_NOPTS_VALUE ? nil : @audio_pts * audio_time_base
|
56
88
|
end
|
57
89
|
|
58
90
|
alias_method :orig_duration, :duration
|
59
91
|
|
60
92
|
def duration
|
61
|
-
orig_duration == AV_NOPTS_VALUE ? nil : orig_duration *
|
93
|
+
orig_duration == AV_NOPTS_VALUE ? nil : orig_duration * video_time_base
|
62
94
|
end
|
63
95
|
|
64
96
|
alias_method :orig_start_time, :start_time
|
65
97
|
|
66
|
-
def
|
67
|
-
orig_start_time == AV_NOPTS_VALUE ? nil : orig_start_time *
|
98
|
+
def video_start_time
|
99
|
+
orig_start_time == AV_NOPTS_VALUE ? nil : orig_start_time * video_time_base
|
68
100
|
end
|
69
101
|
|
70
102
|
end
|
@@ -14,6 +14,7 @@
|
|
14
14
|
# You should have received a copy of the GNU General Public License
|
15
15
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
16
16
|
|
17
|
+
require 'thread'
|
17
18
|
require 'hornetseye_frame'
|
18
19
|
require 'hornetseye-ffmpeg/avinput'
|
19
20
|
require 'hornetseye-ffmpeg/avoutput'
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 5
|
8
|
+
- 1
|
9
|
+
version: 0.5.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Jan Wedekind
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-10-
|
17
|
+
date: 2010-10-19 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -27,8 +27,8 @@ dependencies:
|
|
27
27
|
- !ruby/object:Gem::Version
|
28
28
|
segments:
|
29
29
|
- 1
|
30
|
-
-
|
31
|
-
version: "1.
|
30
|
+
- 2
|
31
|
+
version: "1.2"
|
32
32
|
type: :runtime
|
33
33
|
version_requirements: *id001
|
34
34
|
- !ruby/object:Gem::Dependency
|
@@ -41,8 +41,8 @@ dependencies:
|
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
segments:
|
43
43
|
- 0
|
44
|
-
-
|
45
|
-
version: "0.
|
44
|
+
- 11
|
45
|
+
version: "0.11"
|
46
46
|
type: :runtime
|
47
47
|
version_requirements: *id002
|
48
48
|
- !ruby/object:Gem::Dependency
|
@@ -55,8 +55,8 @@ dependencies:
|
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
segments:
|
57
57
|
- 0
|
58
|
-
-
|
59
|
-
version: "0.
|
58
|
+
- 7
|
59
|
+
version: "0.7"
|
60
60
|
type: :runtime
|
61
61
|
version_requirements: *id003
|
62
62
|
- !ruby/object:Gem::Dependency
|
@@ -92,7 +92,9 @@ files:
|
|
92
92
|
- ext/avinput.cc
|
93
93
|
- ext/init.cc
|
94
94
|
- ext/frame.cc
|
95
|
+
- ext/sequence.cc
|
95
96
|
- ext/frame.hh
|
97
|
+
- ext/sequence.hh
|
96
98
|
- ext/avoutput.hh
|
97
99
|
- ext/avinput.hh
|
98
100
|
- ext/rubyinc.hh
|