hornetseye-ffmpeg 0.4.3 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|