Added support for seeking
This commit is contained in:
parent
828d1d9bce
commit
1593012d5a
|
|
@ -1571,7 +1571,7 @@ MakeDirectory("built/pandac/input")
|
|||
#
|
||||
##########################################################################################
|
||||
|
||||
DEFAULT_SETTINGS=[
|
||||
DTOOL_CONFIG=[
|
||||
#_Variable_________________________Windows___________________Unix__________
|
||||
("HAVE_PYTHON", '1', '1'),
|
||||
("PYTHON_FRAMEWORK", 'UNDEF', 'UNDEF'),
|
||||
|
|
@ -1669,6 +1669,10 @@ DEFAULT_SETTINGS=[
|
|||
("HAVE_CGGL", 'UNDEF', 'UNDEF'),
|
||||
("HAVE_CGDX9", 'UNDEF', 'UNDEF'),
|
||||
("HAVE_FFMPEG", 'UNDEF', 'UNDEF'),
|
||||
("PRC_SAVE_DESCRIPTIONS", '1', '1'),
|
||||
]
|
||||
|
||||
PRC_PARAMETERS=[
|
||||
("DEFAULT_PRC_DIR", '"<auto>etc"', '"<auto>etc"'),
|
||||
("PRC_DIR_ENVVARS", '"PANDA_PRC_DIR"', '"PANDA_PRC_DIR"'),
|
||||
("PRC_PATH_ENVVARS", '"PANDA_PRC_PATH"', '"PANDA_PRC_PATH"'),
|
||||
|
|
@ -1681,71 +1685,74 @@ DEFAULT_SETTINGS=[
|
|||
("PRC_RESPECT_TRUST_LEVEL", 'UNDEF', 'UNDEF'),
|
||||
("PRC_DCONFIG_TRUST_LEVEL", '0', '0'),
|
||||
("PRC_INC_TRUST_LEVEL", '0', '0'),
|
||||
("PRC_SAVE_DESCRIPTIONS", '1', '1'),
|
||||
]
|
||||
|
||||
def WriteConfigSettings():
|
||||
settings={}
|
||||
dtool_config={}
|
||||
prc_parameters={}
|
||||
if (sys.platform == "win32"):
|
||||
for key,win,unix in DEFAULT_SETTINGS:
|
||||
settings[key] = win
|
||||
for key,win,unix in DTOOL_CONFIG:
|
||||
dtool_config[key] = win
|
||||
for key,win,unix in PRC_PARAMETERS:
|
||||
prc_parameters[key] = win
|
||||
else:
|
||||
for key,win,unix in DEFAULT_SETTINGS:
|
||||
settings[key] = unix
|
||||
|
||||
dtool_config[key] = unix
|
||||
for key,win,unix in PRC_PARAMETERS:
|
||||
prc_parameters[key] = unix
|
||||
|
||||
for x in PACKAGES:
|
||||
if (OMIT.count(x)==0):
|
||||
if (settings.has_key("HAVE_"+x)):
|
||||
settings["HAVE_"+x] = '1'
|
||||
if (dtool_config.has_key("HAVE_"+x)):
|
||||
dtool_config["HAVE_"+x] = '1'
|
||||
|
||||
settings["HAVE_NET"] = '1'
|
||||
dtool_config["HAVE_NET"] = '1'
|
||||
|
||||
if (OMIT.count("NVIDIACG")==0):
|
||||
settings["HAVE_CG"] = '1'
|
||||
settings["HAVE_CGGL"] = '1'
|
||||
settings["HAVE_CGDX9"] = '1'
|
||||
dtool_config["HAVE_CG"] = '1'
|
||||
dtool_config["HAVE_CGGL"] = '1'
|
||||
dtool_config["HAVE_CGDX9"] = '1'
|
||||
|
||||
if (OPTIMIZE <= 3):
|
||||
if (settings["HAVE_NET"] != 'UNDEF'):
|
||||
settings["DO_PSTATS"] = '1'
|
||||
if (dtool_config["HAVE_NET"] != 'UNDEF'):
|
||||
dtool_config["DO_PSTATS"] = '1'
|
||||
|
||||
if (OPTIMIZE <= 3):
|
||||
settings["DO_COLLISION_RECORDING"] = '1'
|
||||
dtool_config["DO_COLLISION_RECORDING"] = '1'
|
||||
|
||||
#if (OPTIMIZE <= 2):
|
||||
# settings["TRACK_IN_INTERPRETER"] = '1'
|
||||
# dtool_config["TRACK_IN_INTERPRETER"] = '1'
|
||||
|
||||
if (OPTIMIZE <= 3):
|
||||
settings["DO_MEMORY_USAGE"] = '1'
|
||||
dtool_config["DO_MEMORY_USAGE"] = '1'
|
||||
|
||||
#if (OPTIMIZE <= 1):
|
||||
# settings["DO_PIPELINING"] = '1'
|
||||
# dtool_config["DO_PIPELINING"] = '1'
|
||||
|
||||
if (OPTIMIZE <= 3):
|
||||
settings["NOTIFY_DEBUG"] = '1'
|
||||
dtool_config["NOTIFY_DEBUG"] = '1'
|
||||
|
||||
conf = "/* prc_parameters.h. Generated automatically by makepanda.py */\n"
|
||||
for key in settings.keys():
|
||||
for key in prc_parameters.keys():
|
||||
if ((key == "DEFAULT_PRC_DIR") or (key[:4]=="PRC_")):
|
||||
val = settings[key]
|
||||
val = prc_parameters[key]
|
||||
if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
|
||||
else: conf = conf + "#define " + key + " " + val + "\n"
|
||||
del settings[key]
|
||||
del prc_parameters[key]
|
||||
ConditionalWriteFile('built/include/prc_parameters.h', conf)
|
||||
|
||||
conf = "/* dtool_config.h. Generated automatically by makepanda.py */\n"
|
||||
for key in settings.keys():
|
||||
val = settings[key]
|
||||
for key in dtool_config.keys():
|
||||
val = dtool_config[key]
|
||||
if (val == 'UNDEF'): conf = conf + "#undef " + key + "\n"
|
||||
else: conf = conf + "#define " + key + " " + val + "\n"
|
||||
del settings[key]
|
||||
del dtool_config[key]
|
||||
ConditionalWriteFile('built/include/dtool_config.h', conf)
|
||||
|
||||
for x in PACKAGES:
|
||||
if (OMIT.count(x)): ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"0\n")
|
||||
else: ConditionalWriteFile('built/tmp/dtool_have_'+x.lower()+'.dat',"1\n")
|
||||
|
||||
|
||||
WriteConfigSettings()
|
||||
|
||||
|
||||
|
|
@ -2541,7 +2548,7 @@ EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libtext.in', obj='libtext_igate.obj',
|
|||
#
|
||||
|
||||
IPATH=['panda/src/movies']
|
||||
OPTS=['BUILDING_PANDA']
|
||||
OPTS=['BUILDING_PANDA', 'FFMPEG']
|
||||
EnqueueCxx(ipath=IPATH, opts=OPTS, src='movies_composite1.cxx', obj='movies_composite1.obj')
|
||||
EnqueueIgate(ipath=IPATH, opts=OPTS, outd='libmovies.in', obj='libmovies_igate.obj',
|
||||
src='panda/src/movies', module='panda', library='libmovies',
|
||||
|
|
|
|||
|
|
@ -16,42 +16,3 @@
|
|||
//
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::get_time_correction
|
||||
// Access: Private
|
||||
// Description: The timestamps of packets in AVI files are often
|
||||
// really inaccurate. This returns a small heuristic
|
||||
// correction based on analysis of the audio packets.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
INLINE double FfmpegVideo::
|
||||
get_time_correction() {
|
||||
// The idea here is simple. For audio packets it
|
||||
// is possible to calculate an accurate time by counting
|
||||
// the audio samples. It is then possible to measure
|
||||
// the difference between the reported time and the
|
||||
// actual time of the audio packet. We average this
|
||||
// difference over the last few audio packets. We then
|
||||
// assume that the video packets are off by the same amount.
|
||||
|
||||
double correction = 0.0;
|
||||
for (int i=0; i<_time_correction_window; i++) {
|
||||
correction += _time_corrections[i];
|
||||
}
|
||||
correction /= _time_correction_window;
|
||||
return correction;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::update_time_correction
|
||||
// Access: Private
|
||||
// Description: The timestamps of packets in AVI files are often
|
||||
// really inaccurate. This records some data which
|
||||
// is needed to calculate a heuristic correction factor.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
INLINE void FfmpegVideo::
|
||||
update_time_correction(double diff) {
|
||||
_time_corrections[_time_correction_next % _time_correction_window] = diff;
|
||||
_time_correction_next += 1;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,15 +34,12 @@ FfmpegVideo::
|
|||
FfmpegVideo(const Filename &name) :
|
||||
MovieVideo(name),
|
||||
_filename(name),
|
||||
_samples_read(0),
|
||||
_format_ctx(0),
|
||||
_video_index(-1),
|
||||
_audio_index(-1),
|
||||
_video_ctx(0),
|
||||
_audio_ctx(0),
|
||||
_frame(0),
|
||||
_frame_out(0),
|
||||
_time_correction_next(0)
|
||||
_min_fseek(3.0)
|
||||
{
|
||||
string osname = _filename.to_os_specific();
|
||||
if (av_open_input_file(&_format_ctx, osname.c_str(), NULL, 0, NULL)!=0) {
|
||||
|
|
@ -55,18 +52,13 @@ FfmpegVideo(const Filename &name) :
|
|||
return;
|
||||
}
|
||||
|
||||
// Find the video and audio streams
|
||||
// Find the video stream
|
||||
for(int i=0; i<_format_ctx->nb_streams; i++) {
|
||||
if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
|
||||
_video_index = i;
|
||||
_video_ctx = _format_ctx->streams[i]->codec;
|
||||
_video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
|
||||
}
|
||||
if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO) {
|
||||
_audio_index = i;
|
||||
_audio_ctx = _format_ctx->streams[i]->codec;
|
||||
_audio_timebase = av_q2d(_format_ctx->streams[i]->time_base);
|
||||
}
|
||||
}
|
||||
|
||||
if (_video_ctx == 0) {
|
||||
|
|
@ -87,34 +79,23 @@ FfmpegVideo(const Filename &name) :
|
|||
_size_x = _video_ctx->width;
|
||||
_size_y = _video_ctx->height;
|
||||
_num_components = 3; // Don't know how to implement RGBA movies yet.
|
||||
|
||||
if (_audio_ctx) {
|
||||
AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id);
|
||||
if (pAudioCodec == 0) {
|
||||
_audio_ctx = 0;
|
||||
_audio_index = -1;
|
||||
} else {
|
||||
if (avcodec_open(_audio_ctx, pAudioCodec)<0) {
|
||||
_audio_ctx = 0;
|
||||
_audio_index = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
|
||||
_can_seek = true;
|
||||
_can_seek_fast = true;
|
||||
|
||||
memset(_time_corrections, 0, sizeof(_time_corrections));
|
||||
|
||||
_packet = new AVPacket;
|
||||
_frame = avcodec_alloc_frame();
|
||||
_frame_out = avcodec_alloc_frame();
|
||||
if ((_frame == 0)||(_frame_out == 0)) {
|
||||
if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) {
|
||||
cleanup();
|
||||
return;
|
||||
}
|
||||
memset(_packet, 0, sizeof(AVPacket));
|
||||
|
||||
read_ahead();
|
||||
fetch_packet(0.0);
|
||||
_initial_dts = _packet->dts;
|
||||
_packet_time = 0.0;
|
||||
_last_start = -1.0;
|
||||
_next_start = 0.0;
|
||||
}
|
||||
|
||||
|
|
@ -136,6 +117,13 @@ FfmpegVideo::
|
|||
void FfmpegVideo::
|
||||
cleanup() {
|
||||
_frame_out->data[0] = 0;
|
||||
if (_packet) {
|
||||
if (_packet->data) {
|
||||
av_free_packet(_packet);
|
||||
}
|
||||
delete _packet;
|
||||
_packet = 0;
|
||||
}
|
||||
if (_format_ctx) {
|
||||
av_close_input_file(_format_ctx);
|
||||
_format_ctx = 0;
|
||||
|
|
@ -149,83 +137,103 @@ cleanup() {
|
|||
_frame_out = 0;
|
||||
}
|
||||
_video_ctx = 0;
|
||||
_audio_ctx = 0;
|
||||
_video_index = -1;
|
||||
_audio_index = -1;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::read_ahead
|
||||
// Access: Protected
|
||||
// Description: The video prefetches the next video frame in order
|
||||
// to be able to implement the next_start query.
|
||||
// This function skips over audio packets, but in the
|
||||
// process, it updates the time_correction value.
|
||||
// If the stream is out of video packets, the packet
|
||||
// is empty.
|
||||
// Function: FfmpegVideo::export_frame
|
||||
// Access: Public, Virtual
|
||||
// Description: Exports the contents of the frame buffer into the
|
||||
// user's target buffer.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideo::
|
||||
read_ahead() {
|
||||
if (_format_ctx == 0) {
|
||||
export_frame(unsigned char *data, bool bgra) {
|
||||
if (bgra) {
|
||||
_frame_out->data[0] = data + ((_size_y - 1) * _size_x * 4);
|
||||
_frame_out->linesize[0] = _size_x * -4;
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA,
|
||||
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
|
||||
} else {
|
||||
_frame_out->data[0] = data + ((_size_y - 1) * _size_x * 3);
|
||||
_frame_out->linesize[0] = _size_x * -3;
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
|
||||
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::fetch_packet
|
||||
// Access: Protected
|
||||
// Description: Fetches a video packet and stores it in the
|
||||
// packet buffer. Sets packet_time to the packet's
|
||||
// timestamp. If a packet could not be read, the
|
||||
// packet is cleared and the packet_time is set to
|
||||
// the specified default value.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideo::
|
||||
fetch_packet(double default_time) {
|
||||
if (_packet->data) {
|
||||
av_free_packet(_packet);
|
||||
}
|
||||
while (av_read_frame(_format_ctx, _packet) >= 0) {
|
||||
if (_packet->stream_index == _video_index) {
|
||||
_packet_time = _packet->dts * _video_timebase;
|
||||
return;
|
||||
}
|
||||
av_free_packet(_packet);
|
||||
}
|
||||
_packet->data = 0;
|
||||
_packet_time = default_time;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::fetch_frame
|
||||
// Access: Protected
|
||||
// Description: Fetches a frame from the stream and stores it in
|
||||
// the frame buffer. Sets last_start and next_start
|
||||
// to indicate the extents of the frame.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideo::
|
||||
fetch_frame() {
|
||||
int finished = 0;
|
||||
_last_start = _packet_time;
|
||||
while (!finished && _packet->data) {
|
||||
avcodec_decode_video(_video_ctx, _frame,
|
||||
&finished, _packet->data, _packet->size);
|
||||
fetch_packet(_last_start + 1.0);
|
||||
}
|
||||
_next_start = _packet_time;
|
||||
cerr << "Fetch yields " << _last_start << " - " << _next_start << "\n";
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Function: FfmpegVideo::seek
|
||||
// Access: Protected
|
||||
// Description: Seeks to a target location. Afterward, the
|
||||
// packet_time is guaranteed to be less than or
|
||||
// equal to the specified time.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideo::
|
||||
seek(double t) {
|
||||
long long target_ts = (long long)(t / _video_timebase);
|
||||
if (target_ts < (long long)(_initial_dts)) {
|
||||
// Attempts to seek before the first packet will fail.
|
||||
target_ts = _initial_dts;
|
||||
}
|
||||
if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) {
|
||||
if (t >= _packet_time) {
|
||||
return;
|
||||
}
|
||||
movies_cat.error() << "Seek failure. Shutting down movie.\n";
|
||||
cleanup();
|
||||
_packet_time = t;
|
||||
return;
|
||||
}
|
||||
|
||||
AVPacket pkt;
|
||||
|
||||
while(av_read_frame(_format_ctx, &pkt)>=0) {
|
||||
|
||||
// Is this a packet from the video stream?
|
||||
// If so, store corrected timestamp and return.
|
||||
|
||||
if (pkt.stream_index== _video_index) {
|
||||
double dts = pkt.dts * _video_timebase;
|
||||
double correction = get_time_correction();
|
||||
_next_start = dts + correction;
|
||||
cerr << "Video: " << dts << " " << dts+correction << "\n";
|
||||
if (_next_start < _last_start) {
|
||||
_next_start = _last_start;
|
||||
}
|
||||
int finished = 0;
|
||||
avcodec_decode_video(_video_ctx, _frame,
|
||||
&finished, pkt.data, pkt.size);
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this a packet from the audio stream?
|
||||
// If so, use it to calibrate the time correction value.
|
||||
|
||||
if (pkt.stream_index== _audio_index) {
|
||||
double dts = pkt.dts * _audio_timebase;
|
||||
double real = (_samples_read * 1.0) / _audio_ctx->sample_rate;
|
||||
update_time_correction(real - dts);
|
||||
|
||||
int16_t buffer[4096];
|
||||
uint8_t *audio_pkt_data = pkt.data;
|
||||
int audio_pkt_size = pkt.size;
|
||||
while(audio_pkt_size > 0) {
|
||||
int data_size = sizeof(buffer);
|
||||
int decoded = avcodec_decode_audio(_audio_ctx, buffer, &data_size,
|
||||
audio_pkt_data, audio_pkt_size);
|
||||
if(decoded <= 0) {
|
||||
break;
|
||||
}
|
||||
audio_pkt_data += decoded;
|
||||
audio_pkt_size -= decoded;
|
||||
if (data_size > 0) {
|
||||
_samples_read += data_size / (2 * _audio_ctx->channels);
|
||||
}
|
||||
}
|
||||
|
||||
cerr << "Audio: " << pkt.dts << "(" << dts << ") " << real << "\n";
|
||||
}
|
||||
|
||||
av_free_packet(&pkt);
|
||||
fetch_packet(t);
|
||||
cerr << "Seeking to " << t << " yields " << _packet_time << "\n";
|
||||
if (_packet_time > t) {
|
||||
_packet_time = t;
|
||||
}
|
||||
|
||||
cerr << "Synthesized dummy frame.\n";
|
||||
_next_start = _next_start + 1.0;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -235,33 +243,49 @@ read_ahead() {
|
|||
////////////////////////////////////////////////////////////////////
|
||||
void FfmpegVideo::
|
||||
fetch_into_buffer(double time, unsigned char *data, bool bgra) {
|
||||
|
||||
// If there was an error at any point, fetch black.
|
||||
|
||||
// If there was an error at any point, synthesize black.
|
||||
if (_format_ctx==0) {
|
||||
memset(data,0,size_x()*size_y()*(bgra?4:3));
|
||||
_last_start = _next_start;
|
||||
_next_start += 1.0;
|
||||
if (data) {
|
||||
memset(data,0,size_x()*size_y()*(bgra?4:3));
|
||||
}
|
||||
_last_start = time;
|
||||
_next_start = time + 1.0;
|
||||
return;
|
||||
}
|
||||
|
||||
AVCodecContext *ctx = _format_ctx->streams[_video_index]->codec;
|
||||
nassertv(ctx->width == size_x());
|
||||
nassertv(ctx->height == size_y());
|
||||
|
||||
if (bgra) {
|
||||
_frame_out->data[0] = data + ((ctx->height-1) * ctx->width * 4);
|
||||
_frame_out->linesize[0] = ctx->width * -4;
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA,
|
||||
(AVPicture *)_frame, ctx->pix_fmt, ctx->width, ctx->height);
|
||||
if (time < _last_start) {
|
||||
// Time is in the past.
|
||||
seek(time);
|
||||
while (_packet_time <= time) {
|
||||
fetch_frame();
|
||||
}
|
||||
} else if (time < _next_start) {
|
||||
// Time is in the present: already have the frame.
|
||||
} else if (time < _next_start + _min_fseek) {
|
||||
// Time is in the near future.
|
||||
while (_packet_time <= time) {
|
||||
fetch_frame();
|
||||
}
|
||||
} else {
|
||||
_frame_out->data[0] = data + ((ctx->height-1) * ctx->width * 3);
|
||||
_frame_out->linesize[0] = ctx->width * -3;
|
||||
img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
|
||||
(AVPicture *)_frame, ctx->pix_fmt, ctx->width, ctx->height);
|
||||
// Time is in the far future. Seek forward, then read.
|
||||
// There's a danger here: because keyframes are spaced
|
||||
// unpredictably, trying to seek forward could actually
|
||||
// move us backward in the stream! This must be avoided.
|
||||
// So the rule is, try the seek. If it hurts us by moving
|
||||
// us backward, we increase the minimum threshold distance
|
||||
// for forward-seeking in the future.
|
||||
|
||||
double base = _packet_time;
|
||||
seek(time);
|
||||
if (_packet_time < base) {
|
||||
_min_fseek += (base - _packet_time);
|
||||
}
|
||||
while (_packet_time <= time) {
|
||||
fetch_frame();
|
||||
}
|
||||
}
|
||||
|
||||
_last_start = _next_start;
|
||||
read_ahead();
|
||||
export_frame(data, bgra);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
|
|
|||
|
|
@ -47,24 +47,24 @@ class EXPCL_PANDA_MOVIES FfmpegVideo : public MovieVideo {
|
|||
protected:
|
||||
INLINE double get_time_correction();
|
||||
INLINE void update_time_correction(double diff);
|
||||
void read_ahead();
|
||||
void fetch_packet(double default_time);
|
||||
void fetch_frame();
|
||||
void seek(double t);
|
||||
void export_frame(unsigned char *data, bool bgra);
|
||||
void cleanup();
|
||||
|
||||
static const int _time_correction_window = 4;
|
||||
double _time_corrections[_time_correction_window];
|
||||
int _time_correction_next;
|
||||
|
||||
Filename _filename;
|
||||
AVPacket *_packet;
|
||||
double _packet_time;
|
||||
AVFormatContext *_format_ctx;
|
||||
AVCodecContext *_video_ctx;
|
||||
AVCodecContext *_audio_ctx;
|
||||
int _video_index;
|
||||
int _audio_index;
|
||||
int _video_index;
|
||||
double _video_timebase;
|
||||
double _audio_timebase;
|
||||
AVFrame *_frame;
|
||||
AVFrame *_frame_out;
|
||||
int _samples_read;
|
||||
int _initial_dts;
|
||||
double _min_fseek;
|
||||
|
||||
public:
|
||||
static TypeHandle get_class_type() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue