Panda3D

ffmpegVirtualFile.cxx

00001 // Filename: ffmpegVirtualFile.cxx
00002 // Created by: jyelon (02Jul07)
00003 //
00004 ////////////////////////////////////////////////////////////////////
00005 //
00006 // PANDA 3D SOFTWARE
00007 // Copyright (c) Carnegie Mellon University.  All rights reserved.
00008 //
00009 // All use of this software is subject to the terms of the revised BSD
00010 // license.  You should have received a copy of this license along
00011 // with this source code in a file named "LICENSE."
00012 //
00013 ////////////////////////////////////////////////////////////////////
00014 
00015 #include "pandabase.h"
00016 
00017 #ifdef HAVE_FFMPEG
00018 
00019 #include "config_movies.h"
00020 #include "ffmpegVirtualFile.h"
00021 #include "virtualFileSystem.h"
00022 
00023 extern "C" {
00024   #include "libavcodec/avcodec.h"
00025   #include "libavformat/avformat.h"
00026 }
00027 
00028 #ifndef AVSEEK_SIZE
00029   #define AVSEEK_SIZE 0x10000
00030 #endif
00031 
00032 ////////////////////////////////////////////////////////////////////
00033 //     Function: FfmpegVirtualFile::Constructor
00034 //       Access: Public
00035 //  Description: 
00036 ////////////////////////////////////////////////////////////////////
00037 FfmpegVirtualFile::
00038 FfmpegVirtualFile() : 
00039   _format_context(NULL),
00040   _in(NULL),
00041   _owns_in(false)
00042 {
00043 }
00044 
00045 ////////////////////////////////////////////////////////////////////
00046 //     Function: FfmpegVirtualFile::Destructor
00047 //       Access: Public
00048 //  Description: 
00049 ////////////////////////////////////////////////////////////////////
00050 FfmpegVirtualFile::
00051 ~FfmpegVirtualFile() {
00052   close();
00053 }
00054 
00055 ////////////////////////////////////////////////////////////////////
00056 //     Function: FfmpegVirtualFile::Copy Constructor
00057 //       Access: Private
00058 //  Description: These objects are not meant to be copied.
00059 ////////////////////////////////////////////////////////////////////
00060 FfmpegVirtualFile::
00061 FfmpegVirtualFile(const FfmpegVirtualFile &copy) {
00062   nassertv(false);
00063 }
00064 
00065 ////////////////////////////////////////////////////////////////////
00066 //     Function: FfmpegVirtualFile::Copy Assignment Operator
00067 //       Access: Private
00068 //  Description: These objects are not meant to be copied.
00069 ////////////////////////////////////////////////////////////////////
00070 void FfmpegVirtualFile::
00071 operator = (const FfmpegVirtualFile &copy) {
00072   nassertv(false);
00073 }
00074 
00075 ////////////////////////////////////////////////////////////////////
00076 //     Function: FfmpegVirtualFile::open_vfs
00077 //       Access: Public
00078 //  Description: Opens the movie file via Panda's VFS.  Returns true
00079 //               on success, false on failure.  If successful, use
00080 //               get_format_context() to get the open file handle.
00081 ////////////////////////////////////////////////////////////////////
00082 bool FfmpegVirtualFile::
00083 open_vfs(const Filename &filename) {
00084   close();
00085 
00086   if (ffmpeg_cat.is_debug()) {
00087     ffmpeg_cat.debug()
00088       << "ffmpeg open_vfs(" << filename << ")\n";
00089   }
00090 
00091   VirtualFileSystem *vfs = VirtualFileSystem::get_global_ptr();
00092   Filename fname = filename;
00093   fname.set_binary();
00094   PT(VirtualFile) vfile = vfs->get_file(fname);
00095   if (vfile == NULL) {
00096     return false;
00097   }
00098 
00099   _in = vfile->open_read_file(true);
00100   if (_in == NULL) {
00101     return false;
00102   }
00103 
00104   _owns_in = true;
00105   _start = 0;
00106   _size = vfile->get_file_size(_in);
00107 
00108   // I tried to use av_open_input_stream(), but it (a) required a lot
00109   // of low-level stream analysis calls that really should be
00110   // automatic (and are automatic in av_open_input_file()), and (b)
00111   // was broken on the ffmpeg build I happened to grab.  Screw it,
00112   // clearly av_open_input_file() is the preferred and more
00113   // heavily-exercised interface.  So we'll continue to use url
00114   // synthesis as a hacky hook into this interface.
00115 
00116   // Nowadays we synthesize a "url" that references this pointer.
00117   ostringstream strm;
00118   strm << "pandavfs://" << (void *)this;
00119   string url = strm.str();
00120 
00121   // Now we can open the stream.
00122   int result = 
00123     av_open_input_file(&_format_context, url.c_str(), NULL, 0, NULL);
00124   if (result < 0) {
00125     close();
00126     return false;
00127   }
00128 
00129   return true;
00130 }
00131 
00132 ////////////////////////////////////////////////////////////////////
00133 //     Function: FfmpegVirtualFile::open_subfile
00134 //       Access: Public
00135 //  Description: Opens the movie file directly from a file on disk
00136 //               (does not go through the VFS).  Returns true on
00137 //               success, false on failure.  If successful, use
00138 //               get_format_context() to get the open file handle.
00139 ////////////////////////////////////////////////////////////////////
00140 bool FfmpegVirtualFile::
00141 open_subfile(const SubfileInfo &info) {
00142   close();
00143 
00144   Filename fname = info.get_filename();
00145   fname.set_binary();
00146   if (!fname.open_read(_file_in)) {
00147     return false;
00148   }
00149   if (ffmpeg_cat.is_debug()) {
00150     ffmpeg_cat.debug()
00151       << "ffmpeg open_subfile(" << fname << ")\n";
00152   }
00153 
00154   _in = &_file_in;
00155   _owns_in = false;
00156   _start = info.get_start();
00157   _size = info.get_size();
00158 
00159   _in->seekg(_start);
00160 
00161   // I tried to use av_open_input_stream(), but it (a) required a lot
00162   // of low-level ffmpeg calls that really shouldn't be part of the
00163   // public API (and which aren't necessary with av_open_input_file()
00164   // because they happen implicitly there), and (b) was completely
00165   // broken on the ffmpeg build I happened to grab.  Screw it; clearly
00166   // av_open_input_file() is the preferred and more heavily-exercised
00167   // interface.  So we'll use it, even though it requires a bit of a
00168   // hack.
00169 
00170   // The hack is that we synthesize a "url" that references this
00171   // pointer, then open that url.  This calls pandavfs_open(), which
00172   // decodes the pointer and stores it for future callbacks.
00173   ostringstream strm;
00174   strm << "pandavfs://" << (void *)this;
00175   string url = strm.str();
00176 
00177   // Now we can open the stream.
00178   int result = 
00179     av_open_input_file(&_format_context, url.c_str(), NULL, 0, NULL);
00180   if (result < 0) {
00181     close();
00182     return false;
00183   }
00184 
00185   return true;
00186 }
00187 
00188 ////////////////////////////////////////////////////////////////////
00189 //     Function: FfmpegVirtualFile::close
00190 //       Access: Public
00191 //  Description: Explicitly closes the opened file.  This is also
00192 //               called implicitly by the destructor if necessary.
00193 ////////////////////////////////////////////////////////////////////
00194 void FfmpegVirtualFile::
00195 close() {
00196   if (_format_context != NULL) {
00197     av_close_input_file(_format_context);
00198     _format_context = NULL;
00199   }
00200 
00201   if (_owns_in) {
00202     nassertv(_in != NULL);
00203     VirtualFileSystem::close_read_file(_in);
00204     _owns_in = false;
00205   }
00206   _in = NULL;
00207 }
00208 
00209 ////////////////////////////////////////////////////////////////////
00210 //     Function: FfmpegVirtualFile::register_protocol
00211 //       Access: Public, Static
00212 //  Description: Should be called at startup to attach the appropriate
00213 //               hooks between Panda and FFMpeg.
00214 ////////////////////////////////////////////////////////////////////
00215 void FfmpegVirtualFile::
00216 register_protocol() {
00217   static bool initialized = false;
00218   if (initialized) {
00219     return;
00220   }
00221 
00222   // Here's a good place to call this global ffmpeg initialization
00223   // function.
00224   av_register_all();
00225   
00226   // And this one.
00227 #if LIBAVFORMAT_VERSION_INT >= 0x351400
00228   avformat_network_init();
00229 #endif
00230 
00231   static URLProtocol protocol;
00232   protocol.name = "pandavfs";
00233   protocol.url_open  = pandavfs_open;
00234   protocol.url_read  = pandavfs_read;
00235 
00236 #if LIBAVFORMAT_VERSION_INT < 3425280
00237   protocol.url_write = (int (*)(URLContext *, unsigned char *, int))pandavfs_write;
00238 #else
00239   protocol.url_write = pandavfs_write;
00240 #endif
00241 
00242   protocol.url_seek  = pandavfs_seek;
00243   protocol.url_close = pandavfs_close;
00244 #if LIBAVFORMAT_VERSION_INT < 3415296
00245   ::register_protocol(&protocol);
00246 #elif LIBAVFORMAT_VERSION_MAJOR < 53
00247   av_register_protocol(&protocol);
00248 #else
00249   av_register_protocol2(&protocol, sizeof(protocol));
00250 #endif
00251 
00252   // Let's also register the logging to Panda's notify callback.
00253   av_log_set_callback(&log_callback);
00254 }
00255 
00256 ////////////////////////////////////////////////////////////////////
00257 //     Function: FfmpegVirtualFile::pandavfs_open
00258 //       Access: Private, Static
00259 //  Description: A callback to "open" a virtual file.  Actually, all
00260 //               this does is assign the pointer back to the
00261 //               FfmpegVirtualFile instance.
00262 ////////////////////////////////////////////////////////////////////
00263 int FfmpegVirtualFile::
00264 pandavfs_open(URLContext *h, const char *filename, int flags) {
00265   filename += 11; // Skip over "pandavfs://"
00266   istringstream strm(filename);
00267   void *ptr = 0;
00268   strm >> ptr;
00269 
00270   FfmpegVirtualFile *self = (FfmpegVirtualFile *)ptr;
00271   h->priv_data = self;
00272   return 0;
00273 }
00274 
00275 ////////////////////////////////////////////////////////////////////
00276 //     Function: FfmpegVirtualFile::pandavfs_read
00277 //       Access: Private, Static
00278 //  Description: A callback to read a virtual file.
00279 ////////////////////////////////////////////////////////////////////
00280 int FfmpegVirtualFile::
00281 pandavfs_read(URLContext *h, unsigned char *buf, int size) {
00282   FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data);
00283   istream *in = self->_in;
00284 
00285   // Since we may be simulating a subset of the opened stream, don't
00286   // allow it to read past the "end".
00287   streampos remaining = self->_start + (streampos)self->_size - in->tellg();
00288   if (remaining < size) {
00289     if (remaining <= 0) {
00290       return 0;
00291     }
00292 
00293     size = (int)remaining;
00294   }
00295 
00296   in->read((char *)buf, size);
00297   int gc = in->gcount();
00298   in->clear();
00299 
00300   return gc;
00301 }
00302 
00303 ////////////////////////////////////////////////////////////////////
00304 //     Function: FfmpegVirtualFile::pandavfs_write
00305 //       Access: Private, Static
00306 //  Description: A callback to write a virtual file.  Unimplemented,
00307 //               because we use ffmpeg for playback only, not for
00308 //               encoding video streams.
00309 ////////////////////////////////////////////////////////////////////
00310 int FfmpegVirtualFile::
00311 pandavfs_write(URLContext *h, const unsigned char *buf, int size) {
00312   ffmpeg_cat.warning()
00313     << "ffmpeg is trying to write to the VFS.\n";
00314   return -1;
00315 }
00316 
00317 ////////////////////////////////////////////////////////////////////
00318 //     Function: FfmpegVirtualFile::pandavfs_seek
00319 //       Access: Private, Static
00320 //  Description: A callback to change the read position on an istream.
00321 ////////////////////////////////////////////////////////////////////
00322 int64_t FfmpegVirtualFile::
00323 pandavfs_seek(URLContext *h, int64_t pos, int whence) {
00324   FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data);
00325   istream *in = self->_in;
00326 
00327   switch(whence) {
00328   case SEEK_SET: 
00329     in->seekg(self->_start + (streampos)pos, ios::beg); 
00330     break;
00331 
00332   case SEEK_CUR: 
00333     in->seekg(pos, ios::cur); 
00334     break;
00335 
00336   case SEEK_END: 
00337     // For seeks relative to the end, we actually compute the end
00338     // based on _start + _size, and then use ios::beg.
00339     in->seekg(self->_start + (streampos)self->_size + (streampos)pos, ios::beg); 
00340     break;
00341 
00342   case AVSEEK_SIZE: 
00343     return self->_size; 
00344 
00345   default:
00346     ffmpeg_cat.error() 
00347       << "Illegal parameter to seek in ffmpegVirtualFile\n";
00348     in->clear();
00349     return -1;
00350   }
00351 
00352   in->clear();
00353   return in->tellg() - self->_start;
00354 }
00355 
00356 ////////////////////////////////////////////////////////////////////
00357 //     Function: FfmpegVirtualFile::pandavfs_close
00358 //       Access: Private, Static
00359 //  Description: A hook to "close" a panda VFS file.  Actually it only
00360 //               clears the associated pointer.
00361 ////////////////////////////////////////////////////////////////////
00362 int FfmpegVirtualFile::
00363 pandavfs_close(URLContext *h) {
00364   //FfmpegVirtualFile *self = (FfmpegVirtualFile *)(h->priv_data);
00365   h->priv_data = 0;
00366   return 0;
00367 }
00368 
00369 ////////////////////////////////////////////////////////////////////
00370 //     Function: FfmpegVirtualFile::log_callback
00371 //       Access: Private, Static
00372 //  Description: These callbacks are made when ffmpeg wants to write a
00373 //               log entry; it redirects into Panda's notify.
00374 ////////////////////////////////////////////////////////////////////
00375 void FfmpegVirtualFile::
00376 log_callback(void *ptr, int level, const char *fmt, va_list v1) {
00377   NotifySeverity severity;
00378 #ifdef AV_LOG_PANIC
00379   if (level <= AV_LOG_PANIC) {
00380     severity = NS_fatal;
00381   } else
00382 #endif
00383   if (level <= AV_LOG_ERROR) {
00384     severity = NS_error;
00385 #ifdef AV_LOG_WARNING
00386   } else if (level <= AV_LOG_WARNING) {
00387     severity = NS_warning;
00388 #endif
00389   } else if (level <= AV_LOG_INFO) {
00390     severity = NS_info;
00391 #ifdef AV_LOG_VERBOSE
00392   } else if (level <= AV_LOG_VERBOSE) {
00393     severity = NS_debug;
00394 #endif
00395   } else /* level <= AV_LOG_DEBUG */ {
00396     severity = NS_spam;
00397   }
00398 
00399   if (ffmpeg_cat.is_on(severity)) {
00400     static const size_t buffer_size = 4096;
00401     char *buffer = (char *)alloca(buffer_size);
00402     vsnprintf(buffer, buffer_size, fmt, v1);
00403     nassertv(strlen(buffer) < buffer_size);
00404     ffmpeg_cat.out(severity, true)
00405       << buffer;
00406   }
00407 }
00408 
00409 #endif // HAVE_FFMPEG
 All Classes Functions Variables Enumerations