Added support for seeking

This commit is contained in:
Josh Yelon 2007-08-14 23:53:00 +00:00
parent 828d1d9bce
commit 1593012d5a
4 changed files with 185 additions and 193 deletions

View File

@ -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',

View File

@ -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;
}

View File

@ -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);
}
////////////////////////////////////////////////////////////////////

View File

@ -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() {