diff --git a/doc/makepanda/makepanda.py b/doc/makepanda/makepanda.py index a704e1681f..225d11d3a4 100755 --- a/doc/makepanda/makepanda.py +++ b/doc/makepanda/makepanda.py @@ -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", '"etc"', '"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', diff --git a/panda/src/movies/ffmpegVideo.I b/panda/src/movies/ffmpegVideo.I index 35e2f7c13d..2a23f60d60 100644 --- a/panda/src/movies/ffmpegVideo.I +++ b/panda/src/movies/ffmpegVideo.I @@ -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; -} - diff --git a/panda/src/movies/ffmpegVideo.cxx b/panda/src/movies/ffmpegVideo.cxx index f565e371fe..42f5710739 100644 --- a/panda/src/movies/ffmpegVideo.cxx +++ b/panda/src/movies/ffmpegVideo.cxx @@ -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); } //////////////////////////////////////////////////////////////////// diff --git a/panda/src/movies/ffmpegVideo.h b/panda/src/movies/ffmpegVideo.h index fc05ccb6f8..e3259843e8 100644 --- a/panda/src/movies/ffmpegVideo.h +++ b/panda/src/movies/ffmpegVideo.h @@ -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() {