diff --git a/dtool/Config.pp b/dtool/Config.pp index 0ab365538c..7364a22779 100644 --- a/dtool/Config.pp +++ b/dtool/Config.pp @@ -804,10 +804,11 @@ // Is FFMPEG installed, and where? #define FFMPEG_IPATH /usr/include/ffmpeg #define FFMPEG_LPATH -#define FFMPEG_LIBS $[if $[WINDOWS_PLATFORM],libavcodec.lib libavformat.lib libavutil.lib libgcc.lib libswscale.lib,avcodec avformat avutil swscale] +#define FFMPEG_LIBS $[if $[WINDOWS_PLATFORM],libavcodec.lib libavformat.lib libavutil.lib libgcc.lib libswscale.lib libswresample.lib,avcodec avformat avutil swscale swresample] #defer HAVE_FFMPEG $[libtest $[FFMPEG_LPATH],$[FFMPEG_LIBS]] // Define this if you compiled ffmpeg with libswscale enabled. #define HAVE_SWSCALE 1 +#define HAVE_SWRESAMPLE 1 // Is ODE installed, and where? #define ODE_IPATH diff --git a/dtool/LocalSetup.pp b/dtool/LocalSetup.pp index 219f324624..1564d044d4 100644 --- a/dtool/LocalSetup.pp +++ b/dtool/LocalSetup.pp @@ -187,11 +187,7 @@ #print - Did not find OpenCV #endif #if $[HAVE_FFMPEG] -#if $[HAVE_SWSCALE] -#print + FFMPEG, with libswscale -#else #print + FFMPEG -#endif #else #print - Did not find FFMPEG #endif @@ -428,6 +424,7 @@ $[cdefine OPENCV_VER_23] /* Define if we have FFMPEG installed and want to build for FFMPEG. */ $[cdefine HAVE_FFMPEG] $[cdefine HAVE_SWSCALE] +$[cdefine HAVE_SWRESAMPLE] /* Define if we have ODE installed and want to build for ODE. */ $[cdefine HAVE_ODE] diff --git a/panda/src/movies/config_movies.cxx b/panda/src/movies/config_movies.cxx index 7cf7908bd8..637263f934 100644 --- a/panda/src/movies/config_movies.cxx +++ b/panda/src/movies/config_movies.cxx @@ -81,6 +81,12 @@ ConfigVariableEnum ffmpeg_thread_priority PRC_DESC("The default thread priority at which to start ffmpeg decoder " "threads.")); +ConfigVariableInt ffmpeg_read_buffer_size +("ffmpeg-read-buffer-size", 4096, + PRC_DESC("The size in bytes of the buffer used when reading input files. " + "This is important for performance. A typical size is that of a " + "cache page, e.g. 4kb.")); + //////////////////////////////////////////////////////////////////// // Function: init_libmovies // Description: Initializes the library. This must be called at diff --git a/panda/src/movies/config_movies.h b/panda/src/movies/config_movies.h index 1c98f3f125..61236624fa 100644 --- a/panda/src/movies/config_movies.h +++ b/panda/src/movies/config_movies.h @@ -32,6 +32,7 @@ extern ConfigVariableBool ffmpeg_show_seek_frames; extern ConfigVariableBool ffmpeg_support_seek; extern ConfigVariableBool ffmpeg_global_lock; extern ConfigVariableEnum ffmpeg_thread_priority; +extern ConfigVariableInt ffmpeg_read_buffer_size; extern EXPCL_PANDA_MOVIES void init_libmovies(); diff --git a/panda/src/movies/ffmpegAudioCursor.cxx b/panda/src/movies/ffmpegAudioCursor.cxx index ae3670e392..0d2c02d223 100644 --- a/panda/src/movies/ffmpegAudioCursor.cxx +++ b/panda/src/movies/ffmpegAudioCursor.cxx @@ -18,16 +18,29 @@ #include "ffmpegAudio.h" extern "C" { + #include "libavutil/dict.h" + #include "libavutil/opt.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" } +#ifdef HAVE_SWRESAMPLE +extern "C" { + #include "libswresample/swresample.h" +} +#endif + TypeHandle FfmpegAudioCursor::_type_handle; #if LIBAVFORMAT_VERSION_MAJOR < 53 #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO #endif +#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE +// More recent versions of ffmpeg no longer define this. +#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000 +#endif + //////////////////////////////////////////////////////////////////// // Function: FfmpegAudioCursor::Constructor // Access: Protected @@ -42,7 +55,8 @@ FfmpegAudioCursor(FfmpegAudio *src) : _format_ctx(0), _audio_ctx(0), _buffer(0), - _buffer_alloc(0) + _buffer_alloc(0), + _resample_ctx(0) { if (!_ffvfile.open_vfs(_filename)) { cleanup(); @@ -62,8 +76,8 @@ FfmpegAudioCursor(FfmpegAudio *src) : } // Find the audio stream - for(int i = 0; i < (int)_format_ctx->nb_streams; i++) { - if(_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { + for (int i = 0; i < (int)_format_ctx->nb_streams; i++) { + if (_format_ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { _audio_index = i; _audio_ctx = _format_ctx->streams[i]->codec; _audio_timebase = av_q2d(_format_ctx->streams[i]->time_base); @@ -84,6 +98,8 @@ FfmpegAudioCursor(FfmpegAudio *src) : } #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0) + AVDictionary *opts = NULL; + av_dict_set(&opts, "request_sample_fmt", "s16", 0); if (avcodec_open2(_audio_ctx, pAudioCodec, NULL) < 0) { #else if (avcodec_open(_audio_ctx, pAudioCodec) < 0) { @@ -92,23 +108,61 @@ FfmpegAudioCursor(FfmpegAudio *src) : return; } +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(53, 8, 0) + av_dict_free(&opts); +#endif + + // Set up the resample context if necessary. + if (_audio_ctx->sample_fmt != AV_SAMPLE_FMT_S16) { +#ifdef HAVE_SWRESAMPLE +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 25, 0) + movies_cat.error() + << "Codec does not use signed 16-bit sample format. Upgrade libavcodec to 53.25.0 or higher.\n"; +#else + movies_cat.debug() + << "Codec does not use signed 16-bit sample format. Setting up swresample context.\n"; +#endif + + _resample_ctx = swr_alloc(); + av_opt_set_int(_resample_ctx, "in_channel_layout", _audio_ctx->channel_layout, 0); + av_opt_set_int(_resample_ctx, "out_channel_layout", _audio_ctx->channel_layout, 0); + av_opt_set_int(_resample_ctx, "in_sample_rate", _audio_ctx->sample_rate, 0); + av_opt_set_int(_resample_ctx, "out_sample_rate", _audio_ctx->sample_rate, 0); + av_opt_set_sample_fmt(_resample_ctx, "in_sample_fmt", _audio_ctx->sample_fmt, 0); + av_opt_set_sample_fmt(_resample_ctx, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + if (swr_init(_resample_ctx) != 0) { + movies_cat.error() + << "Failed to set up resample context.\n"; + _resample_ctx = NULL; + } +#else + movies_cat.error() + << "Codec does not use signed 16-bit sample format, but support for libswresample has not been enabled.\n"; +#endif + } + _length = (_format_ctx->duration * 1.0) / AV_TIME_BASE; _can_seek = true; _can_seek_fast = true; + _frame = av_frame_alloc(); + _packet = new AVPacket; _buffer_size = AVCODEC_MAX_AUDIO_FRAME_SIZE / 2; - _buffer_alloc = new PN_int16[_buffer_size + 128]; + _buffer_alloc = new PN_int16[_buffer_size + 64]; + + // Allocate enough space for 1024 samples per channel. if ((_packet == 0)||(_buffer_alloc == 0)) { cleanup(); return; } memset(_packet, 0, sizeof(AVPacket)); - // Align the buffer to a 16-byte boundary + // Align the buffer to a 64-byte boundary // The ffmpeg codec likes this, because it uses SSE/SSE2. _buffer = _buffer_alloc; - while (((size_t)_buffer) & 15) { + while (((size_t)_buffer) & 31) { _buffer += 1; } @@ -137,6 +191,11 @@ FfmpegAudioCursor:: //////////////////////////////////////////////////////////////////// void FfmpegAudioCursor:: cleanup() { + if (_frame) { + av_frame_free(&_frame); + _frame = NULL; + } + if (_packet) { if (_packet->data) { av_free_packet(_packet); @@ -161,6 +220,13 @@ cleanup() { _format_ctx = NULL; } +#ifdef HAVE_SWRESAMPLE + if (_resample_ctx) { + swr_free(&_resample_ctx); + _resample_ctx = NULL; + } +#endif + _audio_index = -1; } @@ -199,7 +265,6 @@ fetch_packet() { bool FfmpegAudioCursor:: reload_buffer() { - while (_buffer_head == _buffer_tail) { // If we're out of packets, generate silence. if (_packet->data == 0) { @@ -217,15 +282,44 @@ reload_buffer() { int len = avcodec_decode_audio2(_audio_ctx, _buffer, &bufsize, _packet_data, _packet_size); movies_debug("avcodec_decode_audio2 returned " << len); -#else - AVPacket pkt; - av_init_packet(&pkt); +#elif LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 25, 0) + // We should technically also consider resampling in this case, + // but whatever. Just upgrade your ffmpeg version if you get garbage. + AVPacket pkt; + av_init_packet(&pkt); pkt.data = _packet_data; pkt.size = _packet_size; int len = avcodec_decode_audio3(_audio_ctx, _buffer, &bufsize, &pkt); movies_debug("avcodec_decode_audio3 returned " << len); av_free_packet(&pkt); +#else + int got_frame; + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = _packet_data; + pkt.size = _packet_size; + int len = avcodec_decode_audio4(_audio_ctx, _frame, &got_frame, &pkt); + movies_debug("avcodec_decode_audio4 returned " << len); + av_free_packet(&pkt); + + bufsize = 0; + if (got_frame) { +#ifdef HAVE_SWRESAMPLE + if (_resample_ctx) { + // Resample the data to signed 16-bit sample format. + uint8_t* out[SWR_CH_MAX] = {(uint8_t*) _buffer, NULL}; + bufsize = swr_convert(_resample_ctx, out, _buffer_size / 2, (const uint8_t**)_frame->extended_data, _frame->nb_samples); + bufsize *= _audio_channels * 2; + } else #endif + { + bufsize = _frame->linesize[0]; + memcpy(_buffer, _frame->data[0], bufsize); + } + } + av_frame_unref(_frame); +#endif + if (len < 0) { return false; } else if (len == 0){ diff --git a/panda/src/movies/ffmpegAudioCursor.h b/panda/src/movies/ffmpegAudioCursor.h index e72c4da0ea..06f3ef674c 100644 --- a/panda/src/movies/ffmpegAudioCursor.h +++ b/panda/src/movies/ffmpegAudioCursor.h @@ -25,12 +25,20 @@ #include "pointerTo.h" #include "ffmpegVirtualFile.h" +extern "C" { + #include "libavcodec/avcodec.h" +} + class FfmpegAudio; struct AVFormatContext; struct AVCodecContext; struct AVStream; struct AVPacket; +#ifdef HAVE_SWRESAMPLE +struct SwrContext; +#endif + //////////////////////////////////////////////////////////////////// // Class : FfmpegAudioCursor // Description : A stream that generates a sequence of audio samples. @@ -61,12 +69,17 @@ protected: int _audio_index; double _audio_timebase; + AVFrame *_frame; PN_int16 *_buffer; int _buffer_size; PN_int16 *_buffer_alloc; int _buffer_head; int _buffer_tail; - + +#ifdef HAVE_SWRESAMPLE + SwrContext *_resample_ctx; +#endif + public: static TypeHandle get_class_type() { return _type_handle; diff --git a/panda/src/movies/ffmpegVirtualFile.cxx b/panda/src/movies/ffmpegVirtualFile.cxx index 50189e24bf..63b26c4b28 100644 --- a/panda/src/movies/ffmpegVirtualFile.cxx +++ b/panda/src/movies/ffmpegVirtualFile.cxx @@ -33,12 +33,15 @@ extern "C" { // Function: FfmpegVirtualFile::Constructor // Access: Public // Description: -//////////////////////////////////////////////////////////////////// +///////////////////////////////p///////////////////////////////////// FfmpegVirtualFile:: -FfmpegVirtualFile() : +FfmpegVirtualFile() : + _io_context(NULL), _format_context(NULL), _in(NULL), - _owns_in(false) + _owns_in(false), + _buffer(NULL), + _buffer_size(ffmpeg_read_buffer_size) { } @@ -105,28 +108,21 @@ open_vfs(const Filename &filename) { _start = 0; _size = vfile->get_file_size(_in); - // I tried to use av_open_input_stream(), but it (a) required a lot - // of low-level stream analysis calls that really should be - // automatic (and are automatic in av_open_input_file()), and (b) - // was broken on the ffmpeg build I happened to grab. Screw it, - // clearly av_open_input_file() is the preferred and more - // heavily-exercised interface. So we'll continue to use url - // synthesis as a hacky hook into this interface. + _buffer = (unsigned char*) av_malloc(_buffer_size); + _io_context = avio_alloc_context(_buffer, _buffer_size, 0, (void*) this, + &read_packet, 0, &seek); - // Nowadays we synthesize a "url" that references this pointer. - ostringstream strm; - strm << "pandavfs://" << (void *)this; - string url = strm.str(); + _format_context = avformat_alloc_context(); + _format_context->pb = _io_context; // Now we can open the stream. int result = #if LIBAVFORMAT_VERSION_INT > AV_VERSION_INT(53, 3, 0) - avformat_open_input(&_format_context, url.c_str(), NULL, NULL); + avformat_open_input(&_format_context, "", NULL, NULL); #else - av_open_input_file(&_format_context, url.c_str(), NULL, 0, NULL); + av_open_input_file(&_format_context, "", NULL, 0, NULL); #endif if (result < 0) { - _format_context = NULL; close(); return false; } @@ -163,31 +159,21 @@ open_subfile(const SubfileInfo &info) { _in->seekg(_start); - // I tried to use av_open_input_stream(), but it (a) required a lot - // of low-level ffmpeg calls that really shouldn't be part of the - // public API (and which aren't necessary with av_open_input_file() - // because they happen implicitly there), and (b) was completely - // broken on the ffmpeg build I happened to grab. Screw it; clearly - // av_open_input_file() is the preferred and more heavily-exercised - // interface. So we'll use it, even though it requires a bit of a - // hack. + _buffer = (unsigned char*) av_malloc(_buffer_size); + _io_context = avio_alloc_context(_buffer, _buffer_size, 0, (void*) this, + &read_packet, 0, &seek); - // The hack is that we synthesize a "url" that references this - // pointer, then open that url. This calls pandavfs_open(), which - // decodes the pointer and stores it for future callbacks. - ostringstream strm; - strm << "pandavfs://" << (void *)this; - string url = strm.str(); + _format_context = avformat_alloc_context(); + _format_context->pb = _io_context; // Now we can open the stream. int result = #if LIBAVFORMAT_VERSION_INT > AV_VERSION_INT(53, 3, 0) - avformat_open_input(&_format_context, url.c_str(), NULL, NULL); + avformat_open_input(&_format_context, fname.c_str(), NULL, NULL); #else - av_open_input_file(&_format_context, url.c_str(), NULL, 0, NULL); + av_open_input_file(&_format_context, fname.c_str(), NULL, 0, NULL); #endif if (result < 0) { - _format_context = NULL; close(); return false; } @@ -212,6 +198,16 @@ close() { #endif } + if (_io_context != NULL) { + av_free(_io_context); + _io_context = NULL; + } + + if (_buffer != NULL) { + av_free(_buffer); + _buffer = NULL; + } + if (_owns_in) { nassertv(_in != NULL); VirtualFileSystem::close_read_file(_in); @@ -236,65 +232,25 @@ register_protocol() { // Here's a good place to call this global ffmpeg initialization // function. av_register_all(); - + // And this one. #if LIBAVFORMAT_VERSION_INT >= 0x351400 avformat_network_init(); #endif - static URLProtocol protocol; - protocol.name = "pandavfs"; - protocol.url_open = pandavfs_open; - protocol.url_read = pandavfs_read; - -#if LIBAVFORMAT_VERSION_INT < 3425280 - protocol.url_write = (int (*)(URLContext *, unsigned char *, int))pandavfs_write; -#else - protocol.url_write = pandavfs_write; -#endif - - protocol.url_seek = pandavfs_seek; - protocol.url_close = pandavfs_close; -#if LIBAVFORMAT_VERSION_INT < 3415296 - ::register_protocol(&protocol); -#elif LIBAVFORMAT_VERSION_MAJOR < 53 - av_register_protocol(&protocol); -#else - av_register_protocol2(&protocol, sizeof(protocol)); -#endif - // Let's also register the logging to Panda's notify callback. av_log_set_callback(&log_callback); } //////////////////////////////////////////////////////////////////// -// Function: FfmpegVirtualFile::pandavfs_open -// Access: Private, Static -// Description: A callback to "open" a virtual file. Actually, all -// this does is assign the pointer back to the -// FfmpegVirtualFile instance. -//////////////////////////////////////////////////////////////////// -int FfmpegVirtualFile:: -pandavfs_open(URLContext *h, const char *filename, int flags) { - filename += 11; // Skip over "pandavfs://" - istringstream strm(filename); - void *ptr = 0; - strm >> ptr; - - FfmpegVirtualFile *self = (FfmpegVirtualFile *)ptr; - h->priv_data = self; - return 0; -} - -//////////////////////////////////////////////////////////////////// -// Function: FfmpegVirtualFile::pandavfs_read +// Function: FfmpegVirtualFile::read_packet // Access: Private, Static // Description: A callback to read a virtual file. //////////////////////////////////////////////////////////////////// int FfmpegVirtualFile:: -pandavfs_read(URLContext *h, unsigned char *buf, int size) { +read_packet(void *opaque, uint8_t *buf, int size) { streampos ssize = (streampos)size; - FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data); + FfmpegVirtualFile *self = (FfmpegVirtualFile *) opaque; istream *in = self->_in; // Since we may be simulating a subset of the opened stream, don't @@ -316,50 +272,36 @@ pandavfs_read(URLContext *h, unsigned char *buf, int size) { } //////////////////////////////////////////////////////////////////// -// Function: FfmpegVirtualFile::pandavfs_write -// Access: Private, Static -// Description: A callback to write a virtual file. Unimplemented, -// because we use ffmpeg for playback only, not for -// encoding video streams. -//////////////////////////////////////////////////////////////////// -int FfmpegVirtualFile:: -pandavfs_write(URLContext *h, const unsigned char *buf, int size) { - ffmpeg_cat.warning() - << "ffmpeg is trying to write to the VFS.\n"; - return -1; -} - -//////////////////////////////////////////////////////////////////// -// Function: FfmpegVirtualFile::pandavfs_seek +// Function: FfmpegVirtualFile::seek // Access: Private, Static // Description: A callback to change the read position on an istream. //////////////////////////////////////////////////////////////////// int64_t FfmpegVirtualFile:: -pandavfs_seek(URLContext *h, int64_t pos, int whence) { - FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data); +seek(void *opaque, int64_t pos, int whence) { + FfmpegVirtualFile *self = (FfmpegVirtualFile *) opaque; istream *in = self->_in; - switch(whence) { - case SEEK_SET: - in->seekg(self->_start + (streampos)pos, ios::beg); + switch (whence) { + case SEEK_SET: + in->seekg(self->_start + (streampos)pos, ios::beg); break; - case SEEK_CUR: - in->seekg(pos, ios::cur); + case SEEK_CUR: + in->seekg(pos, ios::cur); break; - case SEEK_END: + case SEEK_END: // For seeks relative to the end, we actually compute the end // based on _start + _size, and then use ios::beg. - in->seekg(self->_start + (streampos)self->_size + (streampos)pos, ios::beg); + in->seekg(self->_start + (streampos)self->_size + (streampos)pos, ios::beg); break; - case AVSEEK_SIZE: - return self->_size; + case AVSEEK_SIZE: + return self->_size; default: - ffmpeg_cat.error() - << "Illegal parameter to seek in ffmpegVirtualFile\n"; + ffmpeg_cat.error() + << "Illegal parameter to seek in FfmpegVirtualFile\n"; in->clear(); return -1; } @@ -368,19 +310,6 @@ pandavfs_seek(URLContext *h, int64_t pos, int whence) { return in->tellg() - self->_start; } -//////////////////////////////////////////////////////////////////// -// Function: FfmpegVirtualFile::pandavfs_close -// Access: Private, Static -// Description: A hook to "close" a panda VFS file. Actually it only -// clears the associated pointer. -//////////////////////////////////////////////////////////////////// -int FfmpegVirtualFile:: -pandavfs_close(URLContext *h) { - //FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data); - h->priv_data = 0; - return 0; -} - //////////////////////////////////////////////////////////////////// // Function: FfmpegVirtualFile::log_callback // Access: Private, Static diff --git a/panda/src/movies/ffmpegVirtualFile.h b/panda/src/movies/ffmpegVirtualFile.h index 098ef91632..4c60e93367 100644 --- a/panda/src/movies/ffmpegVirtualFile.h +++ b/panda/src/movies/ffmpegVirtualFile.h @@ -56,25 +56,25 @@ public: static void register_protocol(); private: - 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_write(URLContext *h, const unsigned char *buf, int size); - static int64_t pandavfs_seek(URLContext *h, int64_t pos, int whence); - static int pandavfs_close(URLContext *h); + // These are callbacks passed to ffmpeg and cannot change signature. + static int read_packet(void *opaque, uint8_t *buf, int buf_size); + static int64_t seek(void *opaque, int64_t offset, int whence); static void log_callback(void *ptr, int level, const char *fmt, va_list v1); private: + AVIOContext *_io_context; AVFormatContext *_format_context; streampos _start; streamsize _size; istream *_in; pifstream _file_in; bool _owns_in; + unsigned char *_buffer; + int _buffer_size; }; #include "ffmpegVirtualFile.I" #endif // HAVE_FFMPEG #endif // FFMPEGVIRTUALFILE_H -