Uff, this should bring Panda up to date with the latest version of ffmpeg. We now use AVIOContext instead of URLProtocol, avcodec_decode_audio4 instead of 3, and add support for libswresample to resample the input data to 16-bits signed int since some codecs now give float data. If this breaks anyone's build, please, just consider updating your ffmpeg version.

This commit is contained in:
rdb 2013-08-22 18:20:10 +00:00
parent 90770727c0
commit 03e96d8c4a
8 changed files with 182 additions and 141 deletions

View File

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

View File

@ -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]

View File

@ -81,6 +81,12 @@ ConfigVariableEnum<ThreadPriority> 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

View File

@ -32,6 +32,7 @@ extern ConfigVariableBool ffmpeg_show_seek_frames;
extern ConfigVariableBool ffmpeg_support_seek;
extern ConfigVariableBool ffmpeg_global_lock;
extern ConfigVariableEnum<ThreadPriority> ffmpeg_thread_priority;
extern ConfigVariableInt ffmpeg_read_buffer_size;
extern EXPCL_PANDA_MOVIES void init_libmovies();

View File

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

View File

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

View File

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

View File

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