This commit is contained in:
rdb 2009-07-06 15:20:23 +00:00
parent a12727a957
commit 9e41cd84a3
4 changed files with 1692 additions and 1692 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,311 +1,311 @@
// Filename: ffmpegAudioCursor.cxx // Filename: ffmpegAudioCursor.cxx
// Created by: jyelon (01Aug2007) // Created by: jyelon (01Aug2007)
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// //
// PANDA 3D SOFTWARE // PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved. // Copyright (c) Carnegie Mellon University. All rights reserved.
// //
// All use of this software is subject to the terms of the revised BSD // All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along // license. You should have received a copy of this license along
// with this source code in a file named "LICENSE." // with this source code in a file named "LICENSE."
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
#include "ffmpegAudioCursor.h" #include "ffmpegAudioCursor.h"
extern "C" { extern "C" {
#include "libavcodec/avcodec.h" #include "libavcodec/avcodec.h"
#include "libavformat/avformat.h" #include "libavformat/avformat.h"
} }
TypeHandle FfmpegAudioCursor::_type_handle; TypeHandle FfmpegAudioCursor::_type_handle;
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::Constructor // Function: FfmpegAudioCursor::Constructor
// Access: Protected // Access: Protected
// Description: xxx // Description: xxx
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
FfmpegAudioCursor:: FfmpegAudioCursor::
FfmpegAudioCursor(FfmpegAudio *src) : FfmpegAudioCursor(FfmpegAudio *src) :
MovieAudioCursor(src), MovieAudioCursor(src),
_filename(src->_filename), _filename(src->_filename),
_packet(0), _packet(0),
_packet_data(0), _packet_data(0),
_format_ctx(0), _format_ctx(0),
_audio_ctx(0), _audio_ctx(0),
_buffer(0), _buffer(0),
_buffer_alloc(0) _buffer_alloc(0)
{ {
string url = "pandavfs:"; string url = "pandavfs:";
url += _filename; url += _filename;
if (av_open_input_file(&_format_ctx, url.c_str(), NULL, 0, NULL)!=0) { if (av_open_input_file(&_format_ctx, url.c_str(), NULL, 0, NULL)!=0) {
cleanup(); cleanup();
return; return;
} }
if (av_find_stream_info(_format_ctx)<0) { if (av_find_stream_info(_format_ctx)<0) {
cleanup(); cleanup();
return; return;
} }
// Find the audio stream // Find the audio stream
for(int i=0; i<_format_ctx->nb_streams; i++) { for(int i=0; i<_format_ctx->nb_streams; i++) {
if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO) { if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO) {
_audio_index = i; _audio_index = i;
_audio_ctx = _format_ctx->streams[i]->codec; _audio_ctx = _format_ctx->streams[i]->codec;
_audio_timebase = av_q2d(_format_ctx->streams[i]->time_base); _audio_timebase = av_q2d(_format_ctx->streams[i]->time_base);
_audio_rate = _audio_ctx->sample_rate; _audio_rate = _audio_ctx->sample_rate;
_audio_channels = _audio_ctx->channels; _audio_channels = _audio_ctx->channels;
} }
} }
if (_audio_ctx == 0) { if (_audio_ctx == 0) {
cleanup(); cleanup();
return; return;
} }
AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id); AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id);
if(pAudioCodec == 0) { if(pAudioCodec == 0) {
cleanup(); cleanup();
return; return;
} }
if(avcodec_open(_audio_ctx, pAudioCodec)<0) { if(avcodec_open(_audio_ctx, pAudioCodec)<0) {
cleanup(); cleanup();
return; return;
} }
_length = (_format_ctx->duration * 1.0) / AV_TIME_BASE; _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
_can_seek = true; _can_seek = true;
_can_seek_fast = true; _can_seek_fast = true;
_packet = new AVPacket; _packet = new AVPacket;
_buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / 2; _buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / 2;
_buffer_alloc = new PN_int16[_buffer_size + 128]; _buffer_alloc = new PN_int16[_buffer_size + 128];
if ((_packet == 0)||(_buffer_alloc == 0)) { if ((_packet == 0)||(_buffer_alloc == 0)) {
cleanup(); cleanup();
return; return;
} }
memset(_packet, 0, sizeof(AVPacket)); memset(_packet, 0, sizeof(AVPacket));
// Align the buffer to a 16-byte boundary // Align the buffer to a 16-byte boundary
// The ffmpeg codec likes this, because it uses SSE/SSE2. // The ffmpeg codec likes this, because it uses SSE/SSE2.
_buffer = _buffer_alloc; _buffer = _buffer_alloc;
while (((size_t)_buffer) & 15) { while (((size_t)_buffer) & 15) {
_buffer += 1; _buffer += 1;
} }
fetch_packet(); fetch_packet();
_initial_dts = _packet->dts; _initial_dts = _packet->dts;
_last_seek = 0; _last_seek = 0;
_samples_read = 0; _samples_read = 0;
_buffer_head = 0; _buffer_head = 0;
_buffer_tail = 0; _buffer_tail = 0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::Destructor // Function: FfmpegAudioCursor::Destructor
// Access: Protected, Virtual // Access: Protected, Virtual
// Description: xxx // Description: xxx
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
FfmpegAudioCursor:: FfmpegAudioCursor::
~FfmpegAudioCursor() { ~FfmpegAudioCursor() {
cleanup(); cleanup();
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::cleanup // Function: FfmpegAudioCursor::cleanup
// Access: Public // Access: Public
// Description: Reset to a standard inactive state. // Description: Reset to a standard inactive state.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegAudioCursor:: void FfmpegAudioCursor::
cleanup() { cleanup() {
if (_packet) { if (_packet) {
if (_packet->data) { if (_packet->data) {
av_free_packet(_packet); av_free_packet(_packet);
} }
delete _packet; delete _packet;
_packet = 0; _packet = 0;
} }
if (_buffer_alloc) { if (_buffer_alloc) {
delete[] _buffer_alloc; delete[] _buffer_alloc;
_buffer_alloc = 0; _buffer_alloc = 0;
_buffer = 0; _buffer = 0;
} }
if ((_audio_ctx)&&(_audio_ctx->codec)) { if ((_audio_ctx)&&(_audio_ctx->codec)) {
avcodec_close(_audio_ctx); avcodec_close(_audio_ctx);
} }
_audio_ctx = 0; _audio_ctx = 0;
if (_format_ctx) { if (_format_ctx) {
av_close_input_file(_format_ctx); av_close_input_file(_format_ctx);
_format_ctx = 0; _format_ctx = 0;
} }
_audio_ctx = 0; _audio_ctx = 0;
_audio_index = -1; _audio_index = -1;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::fetch_packet // Function: FfmpegAudioCursor::fetch_packet
// Access: Protected // Access: Protected
// Description: Fetches an audio packet and stores it in the // Description: Fetches an audio packet and stores it in the
// packet buffer. Also sets packet_size and packet_data. // packet buffer. Also sets packet_size and packet_data.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegAudioCursor:: void FfmpegAudioCursor::
fetch_packet() { fetch_packet() {
if (_packet->data) { if (_packet->data) {
av_free_packet(_packet); av_free_packet(_packet);
} }
while (av_read_frame(_format_ctx, _packet) >= 0) { while (av_read_frame(_format_ctx, _packet) >= 0) {
if (_packet->stream_index == _audio_index) { if (_packet->stream_index == _audio_index) {
_packet_size = _packet->size; _packet_size = _packet->size;
_packet_data = _packet->data; _packet_data = _packet->data;
return; return;
} }
av_free_packet(_packet); av_free_packet(_packet);
} }
_packet->data = 0; _packet->data = 0;
_packet_size = 0; _packet_size = 0;
_packet_data = 0; _packet_data = 0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::reload_buffer // Function: FfmpegAudioCursor::reload_buffer
// Access: Protected // Access: Protected
// Description: Reloads the audio buffer by decoding audio packets // Description: Reloads the audio buffer by decoding audio packets
// until one of those audio packets finally yields // until one of those audio packets finally yields
// some samples. If we encounter the end of the // some samples. If we encounter the end of the
// stream, we synthesize silence. // stream, we synthesize silence.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegAudioCursor:: void FfmpegAudioCursor::
reload_buffer() { reload_buffer() {
while (_buffer_head == _buffer_tail) { while (_buffer_head == _buffer_tail) {
// If we're out of packets, generate silence. // If we're out of packets, generate silence.
if (_packet->data == 0) { if (_packet->data == 0) {
_buffer_head = 0; _buffer_head = 0;
_buffer_tail = _buffer_size; _buffer_tail = _buffer_size;
memset(_buffer, 0, _buffer_size * 2); memset(_buffer, 0, _buffer_size * 2);
return; return;
} else if (_packet_size > 0) { } else if (_packet_size > 0) {
int bufsize = _buffer_size * 2; int bufsize = _buffer_size * 2;
#if LIBAVCODEC_VERSION_INT < 3414272 #if LIBAVCODEC_VERSION_INT < 3414272
#if LIBAVCODEC_VERSION_INT < 3349504 #if LIBAVCODEC_VERSION_INT < 3349504
int len = avcodec_decode_audio(_audio_ctx, _buffer, &bufsize, int len = avcodec_decode_audio(_audio_ctx, _buffer, &bufsize,
_packet_data, _packet_size); _packet_data, _packet_size);
movies_debug("avcodec_decode_audio returned " << len); movies_debug("avcodec_decode_audio returned " << len);
#else #else
int len = avcodec_decode_audio2(_audio_ctx, _buffer, &bufsize, int len = avcodec_decode_audio2(_audio_ctx, _buffer, &bufsize,
_packet_data, _packet_size); _packet_data, _packet_size);
movies_debug("avcodec_decode_audio2 returned " << len); movies_debug("avcodec_decode_audio2 returned " << len);
#endif #endif
#else #else
AVPacket pkt; AVPacket pkt;
av_init_packet(&pkt); av_init_packet(&pkt);
pkt.data = _packet_data; pkt.data = _packet_data;
pkt.size = _packet_size; pkt.size = _packet_size;
int len = avcodec_decode_audio3(_audio_ctx, _buffer, &bufsize, &pkt); int len = avcodec_decode_audio3(_audio_ctx, _buffer, &bufsize, &pkt);
movies_debug("avcodec_decode_audio3 returned " << len); movies_debug("avcodec_decode_audio3 returned " << len);
av_free_packet(&pkt); // Not sure about this av_free_packet(&pkt); // Not sure about this
#endif #endif
if (len <= 0) { if (len <= 0) {
break; break;
} }
_packet_data += len; _packet_data += len;
_packet_size -= len; _packet_size -= len;
if (bufsize > 0) { if (bufsize > 0) {
_buffer_head = 0; _buffer_head = 0;
_buffer_tail = (bufsize/2); _buffer_tail = (bufsize/2);
return; return;
} }
} else { } else {
fetch_packet(); fetch_packet();
} }
} }
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::seek // Function: FfmpegAudioCursor::seek
// Access: Protected // Access: Protected
// Description: Seeks to a target location. Afterward, the // Description: Seeks to a target location. Afterward, the
// packet_time is guaranteed to be less than or // packet_time is guaranteed to be less than or
// equal to the specified time. // equal to the specified time.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegAudioCursor:: void FfmpegAudioCursor::
seek(double t) { seek(double t) {
PN_int64 target_ts = (PN_int64)(t / _audio_timebase); PN_int64 target_ts = (PN_int64)(t / _audio_timebase);
if (target_ts < (PN_int64)(_initial_dts)) { if (target_ts < (PN_int64)(_initial_dts)) {
// Attempts to seek before the first packet will fail. // Attempts to seek before the first packet will fail.
target_ts = _initial_dts; target_ts = _initial_dts;
} }
if (av_seek_frame(_format_ctx, _audio_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) { if (av_seek_frame(_format_ctx, _audio_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) {
movies_cat.error() << "Seek failure. Shutting down movie.\n"; movies_cat.error() << "Seek failure. Shutting down movie.\n";
cleanup(); cleanup();
return; return;
} }
avcodec_close(_audio_ctx); avcodec_close(_audio_ctx);
AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id); AVCodec *pAudioCodec=avcodec_find_decoder(_audio_ctx->codec_id);
if(pAudioCodec == 0) { if(pAudioCodec == 0) {
cleanup(); cleanup();
return; return;
} }
if(avcodec_open(_audio_ctx, pAudioCodec)<0) { if(avcodec_open(_audio_ctx, pAudioCodec)<0) {
cleanup(); cleanup();
return; return;
} }
_buffer_head = 0; _buffer_head = 0;
_buffer_tail = 0; _buffer_tail = 0;
fetch_packet(); fetch_packet();
double ts = _packet->dts * _audio_timebase; double ts = _packet->dts * _audio_timebase;
if (t > ts) { if (t > ts) {
int skip = (int)((t-ts) * _audio_rate); int skip = (int)((t-ts) * _audio_rate);
read_samples(skip, 0); read_samples(skip, 0);
} }
_last_seek = t; _last_seek = t;
_samples_read = 0; _samples_read = 0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegAudioCursor::read_samples // Function: FfmpegAudioCursor::read_samples
// Access: Public, Virtual // Access: Public, Virtual
// Description: Read audio samples from the stream. N is the // Description: Read audio samples from the stream. N is the
// number of samples you wish to read. Your buffer // number of samples you wish to read. Your buffer
// must be equal in size to N * channels. // must be equal in size to N * channels.
// Multiple-channel audio will be interleaved. // Multiple-channel audio will be interleaved.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegAudioCursor:: void FfmpegAudioCursor::
read_samples(int n, PN_int16 *data) { read_samples(int n, PN_int16 *data) {
//movies_debug("here!!! FfmpegAudioCursor n="<<n); //movies_debug("here!!! FfmpegAudioCursor n="<<n);
int desired = n * _audio_channels; int desired = n * _audio_channels;
// give up after 100 tries to fetch data // give up after 100 tries to fetch data
int give_up_after = 100; int give_up_after = 100;
while (desired && give_up_after > 0) { while (desired && give_up_after > 0) {
if (_buffer_head == _buffer_tail) { if (_buffer_head == _buffer_tail) {
reload_buffer(); reload_buffer();
give_up_after --; give_up_after --;
movies_debug("reload_buffer will give up in "<<give_up_after); movies_debug("reload_buffer will give up in "<<give_up_after);
} }
int available = _buffer_tail - _buffer_head; int available = _buffer_tail - _buffer_head;
int ncopy = (desired > available) ? available : desired; int ncopy = (desired > available) ? available : desired;
if (ncopy) { if (ncopy) {
if (data != 0) { if (data != 0) {
memcpy(data, _buffer + _buffer_head, ncopy * 2); memcpy(data, _buffer + _buffer_head, ncopy * 2);
data += ncopy; data += ncopy;
} }
desired -= ncopy; desired -= ncopy;
_buffer_head += ncopy; _buffer_head += ncopy;
} }
} }
_samples_read += n; _samples_read += n;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#endif // HAVE_FFMPEG #endif // HAVE_FFMPEG

View File

@ -1,384 +1,384 @@
// Filename: ffmpegVideoCursor.cxx // Filename: ffmpegVideoCursor.cxx
// Created by: jyelon (01Aug2007) // Created by: jyelon (01Aug2007)
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// //
// PANDA 3D SOFTWARE // PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved. // Copyright (c) Carnegie Mellon University. All rights reserved.
// //
// All use of this software is subject to the terms of the revised BSD // All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along // license. You should have received a copy of this license along
// with this source code in a file named "LICENSE." // with this source code in a file named "LICENSE."
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
#include "ffmpegVideoCursor.h" #include "ffmpegVideoCursor.h"
#include "config_movies.h" #include "config_movies.h"
extern "C" { extern "C" {
#include "libavcodec/avcodec.h" #include "libavcodec/avcodec.h"
#include "libavformat/avformat.h" #include "libavformat/avformat.h"
#ifdef HAVE_SWSCALE #ifdef HAVE_SWSCALE
#include "libswscale/swscale.h" #include "libswscale/swscale.h"
#endif #endif
} }
#include "pStatCollector.h" #include "pStatCollector.h"
#include "pStatTimer.h" #include "pStatTimer.h"
TypeHandle FfmpegVideoCursor::_type_handle; TypeHandle FfmpegVideoCursor::_type_handle;
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::Constructor // Function: FfmpegVideoCursor::Constructor
// Access: Public // Access: Public
// Description: xxx // Description: xxx
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
FfmpegVideoCursor:: FfmpegVideoCursor::
FfmpegVideoCursor(FfmpegVideo *src) : FfmpegVideoCursor(FfmpegVideo *src) :
MovieVideoCursor(src), MovieVideoCursor(src),
_filename(src->_filename), _filename(src->_filename),
_format_ctx(0), _format_ctx(0),
_video_index(-1), _video_index(-1),
_video_ctx(0), _video_ctx(0),
_frame(0), _frame(0),
_frame_out(0), _frame_out(0),
_packet(0), _packet(0),
_min_fseek(3.0) _min_fseek(3.0)
{ {
string url = "pandavfs:"; string url = "pandavfs:";
url += _filename; url += _filename;
if (av_open_input_file(&_format_ctx, url.c_str(), NULL, 0, NULL)!=0) { if (av_open_input_file(&_format_ctx, url.c_str(), NULL, 0, NULL)!=0) {
cleanup(); cleanup();
return; return;
} }
if (av_find_stream_info(_format_ctx)<0) { if (av_find_stream_info(_format_ctx)<0) {
cleanup(); cleanup();
return; return;
} }
// Find the video stream // Find the video stream
for(int i=0; i<_format_ctx->nb_streams; i++) { for(int i=0; i<_format_ctx->nb_streams; i++) {
if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) { if(_format_ctx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
_video_index = i; _video_index = i;
_video_ctx = _format_ctx->streams[i]->codec; _video_ctx = _format_ctx->streams[i]->codec;
_video_timebase = av_q2d(_format_ctx->streams[i]->time_base); _video_timebase = av_q2d(_format_ctx->streams[i]->time_base);
} }
} }
if (_video_ctx == 0) { if (_video_ctx == 0) {
cleanup(); cleanup();
return; return;
} }
AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id); AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id);
if(pVideoCodec == 0) { if(pVideoCodec == 0) {
cleanup(); cleanup();
return; return;
} }
if(avcodec_open(_video_ctx, pVideoCodec)<0) { if(avcodec_open(_video_ctx, pVideoCodec)<0) {
cleanup(); cleanup();
return; return;
} }
_size_x = _video_ctx->width; _size_x = _video_ctx->width;
_size_y = _video_ctx->height; _size_y = _video_ctx->height;
_num_components = 3; // Don't know how to implement RGBA movies yet. _num_components = 3; // Don't know how to implement RGBA movies yet.
_length = (_format_ctx->duration * 1.0) / AV_TIME_BASE; _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE;
_can_seek = true; _can_seek = true;
_can_seek_fast = true; _can_seek_fast = true;
_packet = new AVPacket; _packet = new AVPacket;
_frame = avcodec_alloc_frame(); _frame = avcodec_alloc_frame();
_frame_out = avcodec_alloc_frame(); _frame_out = avcodec_alloc_frame();
if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) { if ((_packet == 0)||(_frame == 0)||(_frame_out == 0)) {
cleanup(); cleanup();
return; return;
} }
memset(_packet, 0, sizeof(AVPacket)); memset(_packet, 0, sizeof(AVPacket));
fetch_packet(0.0); fetch_packet(0.0);
_initial_dts = _packet->dts; _initial_dts = _packet->dts;
_packet_time = 0.0; _packet_time = 0.0;
_last_start = -1.0; _last_start = -1.0;
_next_start = 0.0; _next_start = 0.0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::Destructor // Function: FfmpegVideoCursor::Destructor
// Access: Public // Access: Public
// Description: xxx // Description: xxx
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
FfmpegVideoCursor:: FfmpegVideoCursor::
~FfmpegVideoCursor() { ~FfmpegVideoCursor() {
cleanup(); cleanup();
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::cleanup // Function: FfmpegVideoCursor::cleanup
// Access: Public // Access: Public
// Description: Reset to a standard inactive state. // Description: Reset to a standard inactive state.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
cleanup() { cleanup() {
if (_frame) { if (_frame) {
av_free(_frame); av_free(_frame);
_frame = 0; _frame = 0;
} }
if (_frame_out) { if (_frame_out) {
_frame_out->data[0] = 0; _frame_out->data[0] = 0;
av_free(_frame_out); av_free(_frame_out);
_frame_out = 0; _frame_out = 0;
} }
if (_packet) { if (_packet) {
if (_packet->data) { if (_packet->data) {
av_free_packet(_packet); av_free_packet(_packet);
} }
delete _packet; delete _packet;
_packet = 0; _packet = 0;
} }
if ((_video_ctx)&&(_video_ctx->codec)) { if ((_video_ctx)&&(_video_ctx->codec)) {
avcodec_close(_video_ctx); avcodec_close(_video_ctx);
} }
_video_ctx = 0; _video_ctx = 0;
if (_format_ctx) { if (_format_ctx) {
av_close_input_file(_format_ctx); av_close_input_file(_format_ctx);
_format_ctx = 0; _format_ctx = 0;
} }
_video_ctx = 0; _video_ctx = 0;
_video_index = -1; _video_index = -1;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::export_frame // Function: FfmpegVideoCursor::export_frame
// Access: Public, Virtual // Access: Public, Virtual
// Description: Exports the contents of the frame buffer into the // Description: Exports the contents of the frame buffer into the
// user's target buffer. // user's target buffer.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR"); static PStatCollector export_frame_collector("*:FFMPEG Convert Video to BGR");
void FfmpegVideoCursor:: void FfmpegVideoCursor::
export_frame(unsigned char *data, bool bgra, int bufx) { export_frame(unsigned char *data, bool bgra, int bufx) {
PStatTimer timer(export_frame_collector); PStatTimer timer(export_frame_collector);
if (bgra) { if (bgra) {
_frame_out->data[0] = data + ((_size_y - 1) * bufx * 4); _frame_out->data[0] = data + ((_size_y - 1) * bufx * 4);
_frame_out->linesize[0] = bufx * -4; _frame_out->linesize[0] = bufx * -4;
#ifdef HAVE_SWSCALE #ifdef HAVE_SWSCALE
struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y, struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
_video_ctx->pix_fmt, _size_x, _size_y, _video_ctx->pix_fmt, _size_x, _size_y,
PIX_FMT_BGRA, 2, NULL, NULL, NULL); PIX_FMT_BGRA, 2, NULL, NULL, NULL);
nassertv(convert_ctx != NULL); nassertv(convert_ctx != NULL);
sws_scale(convert_ctx, _frame->data, _frame->linesize, sws_scale(convert_ctx, _frame->data, _frame->linesize,
0, _size_y, _frame_out->data, _frame_out->linesize); 0, _size_y, _frame_out->data, _frame_out->linesize);
sws_freeContext(convert_ctx); sws_freeContext(convert_ctx);
#else #else
img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA, img_convert((AVPicture *)_frame_out, PIX_FMT_BGRA,
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y); (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
#endif #endif
} else { } else {
_frame_out->data[0] = data + ((_size_y - 1) * bufx * 3); _frame_out->data[0] = data + ((_size_y - 1) * bufx * 3);
_frame_out->linesize[0] = bufx * -3; _frame_out->linesize[0] = bufx * -3;
#ifdef HAVE_SWSCALE #ifdef HAVE_SWSCALE
struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y, struct SwsContext *convert_ctx = sws_getContext(_size_x, _size_y,
_video_ctx->pix_fmt, _size_x, _size_y, _video_ctx->pix_fmt, _size_x, _size_y,
PIX_FMT_BGR24, 2, NULL, NULL, NULL); PIX_FMT_BGR24, 2, NULL, NULL, NULL);
nassertv(convert_ctx != NULL); nassertv(convert_ctx != NULL);
sws_scale(convert_ctx, _frame->data, _frame->linesize, sws_scale(convert_ctx, _frame->data, _frame->linesize,
0, _size_y, _frame_out->data, _frame_out->linesize); 0, _size_y, _frame_out->data, _frame_out->linesize);
sws_freeContext(convert_ctx); sws_freeContext(convert_ctx);
#else #else
img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24, img_convert((AVPicture *)_frame_out, PIX_FMT_BGR24,
(AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y); (AVPicture *)_frame, _video_ctx->pix_fmt, _size_x, _size_y);
#endif #endif
} }
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::fetch_packet // Function: FfmpegVideoCursor::fetch_packet
// Access: Protected // Access: Protected
// Description: Fetches a video packet and stores it in the // Description: Fetches a video packet and stores it in the
// packet buffer. Sets packet_time to the packet's // packet buffer. Sets packet_time to the packet's
// timestamp. If a packet could not be read, the // timestamp. If a packet could not be read, the
// packet is cleared and the packet_time is set to // packet is cleared and the packet_time is set to
// the specified default value. // the specified default value.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
fetch_packet(double default_time) { fetch_packet(double default_time) {
if (_packet->data) { if (_packet->data) {
av_free_packet(_packet); av_free_packet(_packet);
} }
while (av_read_frame(_format_ctx, _packet) >= 0) { while (av_read_frame(_format_ctx, _packet) >= 0) {
if (_packet->stream_index == _video_index) { if (_packet->stream_index == _video_index) {
_packet_time = _packet->dts * _video_timebase; _packet_time = _packet->dts * _video_timebase;
return; return;
} }
av_free_packet(_packet); av_free_packet(_packet);
} }
_packet->data = 0; _packet->data = 0;
_packet_time = default_time; _packet_time = default_time;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::fetch_frame // Function: FfmpegVideoCursor::fetch_frame
// Access: Protected // Access: Protected
// Description: Fetches a frame from the stream and stores it in // Description: Fetches a frame from the stream and stores it in
// the frame buffer. Sets last_start and next_start // the frame buffer. Sets last_start and next_start
// to indicate the extents of the frame. // to indicate the extents of the frame.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
fetch_frame() { fetch_frame() {
int finished = 0; int finished = 0;
_last_start = _packet_time; _last_start = _packet_time;
while (!finished && _packet->data) { while (!finished && _packet->data) {
#if LIBAVCODEC_VERSION_INT < 3414272 #if LIBAVCODEC_VERSION_INT < 3414272
avcodec_decode_video(_video_ctx, _frame, avcodec_decode_video(_video_ctx, _frame,
&finished, _packet->data, _packet->size); &finished, _packet->data, _packet->size);
#else #else
avcodec_decode_video2(_video_ctx, _frame, &finished, _packet); avcodec_decode_video2(_video_ctx, _frame, &finished, _packet);
#endif #endif
fetch_packet(_last_start + 1.0); fetch_packet(_last_start + 1.0);
} }
_next_start = _packet_time; _next_start = _packet_time;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::seek // Function: FfmpegVideoCursor::seek
// Access: Protected // Access: Protected
// Description: Seeks to a target location. Afterward, the // Description: Seeks to a target location. Afterward, the
// packet_time is guaranteed to be less than or // packet_time is guaranteed to be less than or
// equal to the specified time. // equal to the specified time.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
seek(double t) { seek(double t) {
PN_int64 target_ts = (PN_int64)(t / _video_timebase); PN_int64 target_ts = (PN_int64)(t / _video_timebase);
if (target_ts < (PN_int64)(_initial_dts)) { if (target_ts < (PN_int64)(_initial_dts)) {
// Attempts to seek before the first packet will fail. // Attempts to seek before the first packet will fail.
target_ts = _initial_dts; target_ts = _initial_dts;
} }
if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) { if (av_seek_frame(_format_ctx, _video_index, target_ts, AVSEEK_FLAG_BACKWARD) < 0) {
if (t >= _packet_time) { if (t >= _packet_time) {
return; return;
} }
movies_cat.error() << "Seek failure. Shutting down movie.\n"; movies_cat.error() << "Seek failure. Shutting down movie.\n";
cleanup(); cleanup();
_packet_time = t; _packet_time = t;
return; return;
} }
avcodec_close(_video_ctx); avcodec_close(_video_ctx);
AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id); AVCodec *pVideoCodec=avcodec_find_decoder(_video_ctx->codec_id);
if(pVideoCodec == 0) { if(pVideoCodec == 0) {
cleanup(); cleanup();
return; return;
} }
if(avcodec_open(_video_ctx, pVideoCodec)<0) { if(avcodec_open(_video_ctx, pVideoCodec)<0) {
cleanup(); cleanup();
return; return;
} }
fetch_packet(t); fetch_packet(t);
if (_packet_time > t) { if (_packet_time > t) {
_packet_time = t; _packet_time = t;
} }
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::fetch_time // Function: FfmpegVideoCursor::fetch_time
// Access: Public, Virtual // Access: Public, Virtual
// Description: Advance until the specified time is in the // Description: Advance until the specified time is in the
// export buffer. // export buffer.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVideoCursor:: void FfmpegVideoCursor::
fetch_time(double time) { fetch_time(double time) {
if (time < _last_start) { if (time < _last_start) {
// Time is in the past. // Time is in the past.
seek(time); seek(time);
while (_packet_time <= time) { while (_packet_time <= time) {
fetch_frame(); fetch_frame();
} }
} else if (time < _next_start) { } else if (time < _next_start) {
// Time is in the present: already have the frame. // Time is in the present: already have the frame.
} else if (time < _next_start + _min_fseek) { } else if (time < _next_start + _min_fseek) {
// Time is in the near future. // Time is in the near future.
while ((_packet_time <= time) && (_packet->data)) { while ((_packet_time <= time) && (_packet->data)) {
fetch_frame(); fetch_frame();
} }
} else { } else {
// Time is in the far future. Seek forward, then read. // Time is in the far future. Seek forward, then read.
// There's a danger here: because keyframes are spaced // There's a danger here: because keyframes are spaced
// unpredictably, trying to seek forward could actually // unpredictably, trying to seek forward could actually
// move us backward in the stream! This must be avoided. // move us backward in the stream! This must be avoided.
// So the rule is, try the seek. If it hurts us by moving // So the rule is, try the seek. If it hurts us by moving
// us backward, we increase the minimum threshold distance // us backward, we increase the minimum threshold distance
// for forward-seeking in the future. // for forward-seeking in the future.
double base = _packet_time; double base = _packet_time;
seek(time); seek(time);
if (_packet_time < base) { if (_packet_time < base) {
_min_fseek += (base - _packet_time); _min_fseek += (base - _packet_time);
} }
while (_packet_time <= time) { while (_packet_time <= time) {
fetch_frame(); fetch_frame();
} }
} }
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::fetch_into_texture // Function: FfmpegVideoCursor::fetch_into_texture
// Access: Public, Virtual // Access: Public, Virtual
// Description: See MovieVideoCursor::fetch_into_texture. // Description: See MovieVideoCursor::fetch_into_texture.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static PStatCollector fetch_into_texture_pcollector("*:FFMPEG Video Decoding"); static PStatCollector fetch_into_texture_pcollector("*:FFMPEG Video Decoding");
void FfmpegVideoCursor:: void FfmpegVideoCursor::
fetch_into_texture(double time, Texture *t, int page) { fetch_into_texture(double time, Texture *t, int page) {
PStatTimer timer(fetch_into_texture_pcollector); PStatTimer timer(fetch_into_texture_pcollector);
nassertv(t->get_x_size() >= size_x()); nassertv(t->get_x_size() >= size_x());
nassertv(t->get_y_size() >= size_y()); nassertv(t->get_y_size() >= size_y());
nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4)); nassertv((t->get_num_components() == 3) || (t->get_num_components() == 4));
nassertv(t->get_component_width() == 1); nassertv(t->get_component_width() == 1);
nassertv(page < t->get_z_size()); nassertv(page < t->get_z_size());
PTA_uchar img = t->modify_ram_image(); PTA_uchar img = t->modify_ram_image();
unsigned char *data = img.p() + page * t->get_expected_ram_page_size(); unsigned char *data = img.p() + page * t->get_expected_ram_page_size();
// If there was an error at any point, synthesize black. // If there was an error at any point, synthesize black.
if (_format_ctx==0) { if (_format_ctx==0) {
if (data) { if (data) {
memset(data,0,t->get_x_size() * t->get_y_size() * t->get_num_components()); memset(data,0,t->get_x_size() * t->get_y_size() * t->get_num_components());
} }
_last_start = time; _last_start = time;
_next_start = time + 1.0; _next_start = time + 1.0;
return; return;
} }
fetch_time(time); fetch_time(time);
export_frame(data, (t->get_num_components()==4), t->get_x_size()); export_frame(data, (t->get_num_components()==4), t->get_x_size());
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVideoCursor::fetch_into_buffer // Function: FfmpegVideoCursor::fetch_into_buffer
// Access: Public, Virtual // Access: Public, Virtual
// Description: See MovieVideoCursor::fetch_into_buffer. // Description: See MovieVideoCursor::fetch_into_buffer.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static PStatCollector fetch_into_buffer_pcollector("*:FFMPEG Video Decoding"); static PStatCollector fetch_into_buffer_pcollector("*:FFMPEG Video Decoding");
void FfmpegVideoCursor:: void FfmpegVideoCursor::
fetch_into_buffer(double time, unsigned char *data, bool bgra) { fetch_into_buffer(double time, unsigned char *data, bool bgra) {
PStatTimer timer(fetch_into_buffer_pcollector); PStatTimer timer(fetch_into_buffer_pcollector);
// If there was an error at any point, synthesize black. // If there was an error at any point, synthesize black.
if (_format_ctx==0) { if (_format_ctx==0) {
if (data) { if (data) {
memset(data,0,size_x()*size_y()*(bgra?4:3)); memset(data,0,size_x()*size_y()*(bgra?4:3));
} }
_last_start = time; _last_start = time;
_next_start = time + 1.0; _next_start = time + 1.0;
return; return;
} }
fetch_time(time); fetch_time(time);
export_frame(data, bgra, _size_x); export_frame(data, bgra, _size_x);
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#endif // HAVE_FFMPEG #endif // HAVE_FFMPEG

View File

@ -1,176 +1,176 @@
// Filename: ffmpegVirtualFile.cxx // Filename: ffmpegVirtualFile.cxx
// Created by: jyelon (02Jul07) // Created by: jyelon (02Jul07)
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// //
// PANDA 3D SOFTWARE // PANDA 3D SOFTWARE
// Copyright (c) Carnegie Mellon University. All rights reserved. // Copyright (c) Carnegie Mellon University. All rights reserved.
// //
// All use of this software is subject to the terms of the revised BSD // All use of this software is subject to the terms of the revised BSD
// license. You should have received a copy of this license along // license. You should have received a copy of this license along
// with this source code in a file named "LICENSE." // with this source code in a file named "LICENSE."
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
#ifdef HAVE_FFMPEG #ifdef HAVE_FFMPEG
#include "pandabase.h" #include "pandabase.h"
#include "config_movies.h" #include "config_movies.h"
#include "ffmpegVirtualFile.h" #include "ffmpegVirtualFile.h"
#include "virtualFileSystem.h" #include "virtualFileSystem.h"
extern "C" { extern "C" {
#include "libavformat/avio.h" #include "libavformat/avio.h"
} }
#ifndef AVSEEK_SIZE #ifndef AVSEEK_SIZE
#define AVSEEK_SIZE 0x10000 #define AVSEEK_SIZE 0x10000
#endif #endif
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// These functions need to use C calling conventions. // These functions need to use C calling conventions.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
extern "C" { extern "C" {
static int pandavfs_open(URLContext *h, const char *filename, int flags); static int pandavfs_open(URLContext *h, const char *filename, int flags);
static int pandavfs_read(URLContext *h, unsigned char *buf, int size); static int pandavfs_read(URLContext *h, unsigned char *buf, int size);
static int pandavfs_write(URLContext *h, unsigned char *buf, int size); static int pandavfs_write(URLContext *h, unsigned char *buf, int size);
static PN_int64 pandavfs_seek(URLContext *h, PN_int64 pos, int whence); static PN_int64 pandavfs_seek(URLContext *h, PN_int64 pos, int whence);
static int pandavfs_close(URLContext *h); static int pandavfs_close(URLContext *h);
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: pandavfs_open // Function: pandavfs_open
// Access: Static Function // Access: Static Function
// Description: A hook to open a panda VFS file. // Description: A hook to open a panda VFS file.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static int static int
pandavfs_open(URLContext *h, const char *filename, int flags) { pandavfs_open(URLContext *h, const char *filename, int flags) {
if (flags != 0) { if (flags != 0) {
movies_cat.error() << "ffmpeg is trying to write to the VFS.\n"; movies_cat.error() << "ffmpeg is trying to write to the VFS.\n";
return -1; return -1;
} }
filename += 9; // Skip over "pandavfs:" filename += 9; // Skip over "pandavfs:"
VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr(); VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
istream *s = vfs->open_read_file(filename, true); istream *s = vfs->open_read_file(filename, true);
if (s == 0) { if (s == 0) {
return -1; return -1;
} }
// Test whether seek works. // Test whether seek works.
s->seekg(1, ios::beg); s->seekg(1, ios::beg);
int tel1 = s->tellg(); int tel1 = s->tellg();
s->seekg(0, ios::beg); s->seekg(0, ios::beg);
int tel2 = s->tellg(); int tel2 = s->tellg();
if (s->fail() || (tel1!=1) || (tel2!=0)) { if (s->fail() || (tel1!=1) || (tel2!=0)) {
movies_cat.error() << "cannot play movie (not seekable): " << h->filename << "\n"; movies_cat.error() << "cannot play movie (not seekable): " << h->filename << "\n";
delete s; delete s;
return -1; return -1;
} }
h->priv_data = s; h->priv_data = s;
return 0; return 0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: pandavfs_read // Function: pandavfs_read
// Access: Static Function // Access: Static Function
// Description: A hook to read a panda VFS file. // Description: A hook to read a panda VFS file.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static int static int
pandavfs_read(URLContext *h, unsigned char *buf, int size) { pandavfs_read(URLContext *h, unsigned char *buf, int size) {
istream *s = (istream*)(h->priv_data); istream *s = (istream*)(h->priv_data);
s->read((char*)buf, size); s->read((char*)buf, size);
int gc = s->gcount(); int gc = s->gcount();
s->clear(); s->clear();
return gc; return gc;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: pandavfs_write // Function: pandavfs_write
// Access: Static Function // Access: Static Function
// Description: A hook to write a panda VFS file. // Description: A hook to write a panda VFS file.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static int static int
pandavfs_write(URLContext *h, unsigned char *buf, int size) { pandavfs_write(URLContext *h, unsigned char *buf, int size) {
movies_cat.error() << "ffmpeg is trying to write to the VFS.\n"; movies_cat.error() << "ffmpeg is trying to write to the VFS.\n";
return -1; return -1;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: pandavfs_seek // Function: pandavfs_seek
// Access: Static Function // Access: Static Function
// Description: A hook to seek a panda VFS file. // Description: A hook to seek a panda VFS file.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static PN_int64 static PN_int64
pandavfs_seek(URLContext *h, PN_int64 pos, int whence) { pandavfs_seek(URLContext *h, PN_int64 pos, int whence) {
istream *s = (istream*)(h->priv_data); istream *s = (istream*)(h->priv_data);
switch(whence) { switch(whence) {
case SEEK_SET: s->seekg(pos, ios::beg); break; case SEEK_SET: s->seekg(pos, ios::beg); break;
case SEEK_CUR: s->seekg(pos, ios::cur); break; case SEEK_CUR: s->seekg(pos, ios::cur); break;
case SEEK_END: s->seekg(pos, ios::end); break; case SEEK_END: s->seekg(pos, ios::end); break;
case AVSEEK_SIZE: { case AVSEEK_SIZE: {
s->seekg(0, ios::cur); s->seekg(0, ios::cur);
int p = s->tellg(); int p = s->tellg();
s->seekg(-1, ios::end); s->seekg(-1, ios::end);
int size = s->tellg(); int size = s->tellg();
if (size < 0) { if (size < 0) {
movies_cat.error() << "Failed to determine filesize in ffmpegVirtualFile\n"; movies_cat.error() << "Failed to determine filesize in ffmpegVirtualFile\n";
s->clear(); s->clear();
return -1; return -1;
} }
size++; size++;
s->seekg(p, ios::beg); s->seekg(p, ios::beg);
s->clear(); s->clear();
return size; } return size; }
default: default:
movies_cat.error() << "Illegal parameter to seek in ffmpegVirtualFile\n"; movies_cat.error() << "Illegal parameter to seek in ffmpegVirtualFile\n";
s->clear(); s->clear();
return -1; return -1;
} }
s->clear(); s->clear();
int tl = s->tellg(); int tl = s->tellg();
return tl; return tl;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: pandavfs_close // Function: pandavfs_close
// Access: Static Function // Access: Static Function
// Description: A hook to close a panda VFS file. // Description: A hook to close a panda VFS file.
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
static int static int
pandavfs_close(URLContext *h) { pandavfs_close(URLContext *h) {
istream *s = (istream*)(h->priv_data); istream *s = (istream*)(h->priv_data);
delete s; delete s;
h->priv_data = 0; h->priv_data = 0;
return 0; return 0;
} }
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
// Function: FfmpegVirtualFile::register_protocol // Function: FfmpegVirtualFile::register_protocol
// Access: Public, Static // Access: Public, Static
// Description: Enables ffmpeg to access panda's VFS. // Description: Enables ffmpeg to access panda's VFS.
// //
// After calling this method, ffmpeg will be // After calling this method, ffmpeg will be
// able to open "URLs" that look like this: // able to open "URLs" that look like this:
// //
// pandavfs:/c/mygame/foo.avi // pandavfs:/c/mygame/foo.avi
// //
//////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////
void FfmpegVirtualFile:: void FfmpegVirtualFile::
register_protocol() { register_protocol() {
static bool initialized = false; static bool initialized = false;
if (initialized) { if (initialized) {
return; return;
} }
static URLProtocol protocol; static URLProtocol protocol;
protocol.name = "pandavfs"; protocol.name = "pandavfs";
protocol.url_open = pandavfs_open; protocol.url_open = pandavfs_open;
protocol.url_read = pandavfs_read; protocol.url_read = pandavfs_read;
protocol.url_write = pandavfs_write; protocol.url_write = pandavfs_write;
protocol.url_seek = pandavfs_seek; protocol.url_seek = pandavfs_seek;
protocol.url_close = pandavfs_close; protocol.url_close = pandavfs_close;
#if LIBAVFORMAT_VERSION_INT < 3415296 #if LIBAVFORMAT_VERSION_INT < 3415296
::register_protocol(&protocol); ::register_protocol(&protocol);
#else #else
av_register_protocol(&protocol); av_register_protocol(&protocol);
#endif #endif
} }
#endif // HAVE_FFMPEG #endif // HAVE_FFMPEG