Panda3D
ffmpegVirtualFile.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file ffmpegVirtualFile.cxx
10  * @author jyelon
11  * @date 2007-07-02
12  */
13 
14 #include "pandabase.h"
15 
16 #include "config_ffmpeg.h"
17 #include "ffmpegVirtualFile.h"
18 #include "virtualFileSystem.h"
19 
20 using std::streampos;
21 using std::streamsize;
22 
23 extern "C" {
24  #include <libavcodec/avcodec.h>
25  #include <libavformat/avformat.h>
26 }
27 
28 #ifndef AVSEEK_SIZE
29  #define AVSEEK_SIZE 0x10000
30 #endif
31 
32 /**
33  *
34  */
35 FfmpegVirtualFile::
36 FfmpegVirtualFile() :
37  _io_context(nullptr),
38  _format_context(nullptr),
39  _in(nullptr),
40  _owns_in(false),
41  _buffer_size(ffmpeg_read_buffer_size)
42 {
43 }
44 
45 /**
46  *
47  */
48 FfmpegVirtualFile::
49 ~FfmpegVirtualFile() {
50  close();
51 }
52 
53 /**
54  * Opens the movie file via Panda's VFS. Returns true on success, false on
55  * failure. If successful, use get_format_context() to get the open file
56  * handle.
57  */
59 open_vfs(const Filename &filename) {
60  close();
61 
62  if (ffmpeg_cat.is_debug()) {
63  ffmpeg_cat.debug()
64  << "ffmpeg open_vfs(" << filename << ")\n";
65  }
66 
68  Filename fname = filename;
69  fname.set_binary();
70  PT(VirtualFile) vfile = vfs->get_file(fname);
71  if (vfile == nullptr) {
72  return false;
73  }
74 
75  _in = vfile->open_read_file(true);
76  if (_in == nullptr) {
77  return false;
78  }
79 
80  _owns_in = true;
81  _start = 0;
82  _size = vfile->get_file_size(_in);
83 
84  // NOTE: The AVIO system owns the buffer after allocation and may realloc it
85  // internally. Therefore, when we're done with the buffer, we use
86  // _io_context->buffer to deallocate it rather than holding on to this
87  // pointer.
88  unsigned char *buffer = (unsigned char*) av_malloc(_buffer_size);
89  _io_context = avio_alloc_context(buffer, _buffer_size, 0, (void*) this,
90  &read_packet, nullptr, &seek);
91 
92  _format_context = avformat_alloc_context();
93  _format_context->pb = _io_context;
94 
95  // Now we can open the stream.
96  int result =
97  avformat_open_input(&_format_context, "", nullptr, nullptr);
98  if (result < 0) {
99  close();
100  return false;
101  }
102 
103  return true;
104 }
105 
106 /**
107  * Opens the movie file directly from a file on disk (does not go through the
108  * VFS). Returns true on success, false on failure. If successful, use
109  * get_format_context() to get the open file handle.
110  */
112 open_subfile(const SubfileInfo &info) {
113  close();
114 
115  Filename fname = info.get_filename();
116  fname.set_binary();
117  if (!fname.open_read(_file_in)) {
118  return false;
119  }
120  if (ffmpeg_cat.is_debug()) {
121  ffmpeg_cat.debug()
122  << "ffmpeg open_subfile(" << fname << ")\n";
123  }
124 
125  _in = &_file_in;
126  _owns_in = false;
127  _start = info.get_start();
128  _size = info.get_size();
129 
130  _in->seekg(_start);
131 
132  // NOTE: The AVIO system owns the buffer after allocation and may realloc it
133  // internally. Therefore, when we're done with the buffer, we use
134  // _io_context->buffer to deallocate it rather than holding on to this
135  // pointer.
136  unsigned char *buffer = (unsigned char*) av_malloc(_buffer_size);
137  _io_context = avio_alloc_context(buffer, _buffer_size, 0, (void*) this,
138  &read_packet, nullptr, &seek);
139 
140  _format_context = avformat_alloc_context();
141  _format_context->pb = _io_context;
142 
143  // Now we can open the stream.
144  int result =
145  avformat_open_input(&_format_context, fname.c_str(), nullptr, nullptr);
146  if (result < 0) {
147  close();
148  return false;
149  }
150 
151  return true;
152 }
153 
154 /**
155  * Explicitly closes the opened file. This is also called implicitly by the
156  * destructor if necessary.
157  */
159 close() {
160  if (_format_context != nullptr) {
161  avformat_close_input(&_format_context);
162  }
163 
164  if (_io_context != nullptr) {
165  if (_io_context->buffer != nullptr) {
166  av_free(_io_context->buffer);
167  }
168  av_free(_io_context);
169  _io_context = nullptr;
170  }
171 
172  if (_owns_in) {
173  nassertv(_in != nullptr);
175  _owns_in = false;
176  }
177  _in = nullptr;
178 }
179 
180 /**
181  * Should be called at startup to attach the appropriate hooks between Panda
182  * and FFMpeg.
183  */
186  static bool initialized = false;
187  if (initialized) {
188  return;
189  }
190 
191  // Here's a good place to call this global ffmpeg initialization function.
192  // However, ffmpeg (but not libav) deprecated this, hence this check.
193 #if LIBAVFORMAT_VERSION_MICRO < 100 || LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(58, 9, 100)
194  av_register_all();
195 #endif
196 
197  // And this one.
198  avformat_network_init();
199 
200  // Let's also register the logging to Panda's notify callback.
201  av_log_set_callback(&log_callback);
202 }
203 
204 /**
205  * A callback to read a virtual file.
206  */
207 int FfmpegVirtualFile::
208 read_packet(void *opaque, uint8_t *buf, int size) {
209  streampos ssize = (streampos)size;
210  FfmpegVirtualFile *self = (FfmpegVirtualFile *) opaque;
211  std::istream *in = self->_in;
212 
213  // Since we may be simulating a subset of the opened stream, don't allow it
214  // to read past the "end".
215  streampos remaining = self->_start + (streampos)self->_size - in->tellg();
216  if (remaining < ssize) {
217  if (remaining <= 0) {
218  return AVERROR_EOF;
219  }
220 
221  ssize = remaining;
222  }
223 
224  in->read((char *)buf, ssize);
225  streamsize gc = in->gcount();
226  in->clear();
227 
228  return (int)gc;
229 }
230 
231 /**
232  * A callback to change the read position on an istream.
233  */
234 int64_t FfmpegVirtualFile::
235 seek(void *opaque, int64_t pos, int whence) {
236  FfmpegVirtualFile *self = (FfmpegVirtualFile *) opaque;
237  std::istream *in = self->_in;
238 
239  switch (whence) {
240  case SEEK_SET:
241  in->seekg(self->_start + (streampos)pos, std::ios::beg);
242  break;
243 
244  case SEEK_CUR:
245  in->seekg(pos, std::ios::cur);
246  break;
247 
248  case SEEK_END:
249  // For seeks relative to the end, we actually compute the end based on
250  // _start + _size, and then use ios::beg.
251  in->seekg(self->_start + (streampos)self->_size + (streampos)pos, std::ios::beg);
252  break;
253 
254  case AVSEEK_SIZE:
255  return self->_size;
256 
257  default:
258  ffmpeg_cat.error()
259  << "Illegal parameter to seek in FfmpegVirtualFile\n";
260  in->clear();
261  return -1;
262  }
263 
264  in->clear();
265  return in->tellg() - self->_start;
266 }
267 
268 /**
269  * These callbacks are made when ffmpeg wants to write a log entry; it
270  * redirects into Panda's notify.
271  */
272 void FfmpegVirtualFile::
273 log_callback(void *ptr, int level, const char *fmt, va_list v1) {
274  NotifySeverity severity;
275 #ifdef AV_LOG_PANIC
276  if (level <= AV_LOG_PANIC) {
277  severity = NS_fatal;
278  } else
279 #endif
280  if (level <= AV_LOG_ERROR) {
281  severity = NS_error;
282 #ifdef AV_LOG_WARNING
283  } else if (level <= AV_LOG_WARNING) {
284  severity = NS_warning;
285 #endif
286  } else if (level <= AV_LOG_INFO) {
287  severity = NS_info;
288 #ifdef AV_LOG_VERBOSE
289  } else if (level <= AV_LOG_VERBOSE) {
290  severity = NS_debug;
291 #endif
292  } else /* level <= AV_LOG_DEBUG */ {
293  severity = NS_spam;
294  }
295 
296  if (ffmpeg_cat.is_on(severity)) {
297  static const size_t buffer_size = 4096;
298  char *buffer = (char *)alloca(buffer_size);
299  vsnprintf(buffer, buffer_size, fmt, v1);
300  nassertv(strlen(buffer) < buffer_size);
301  ffmpeg_cat.out(severity, true)
302  << buffer;
303  }
304 }
Filename::open_read
bool open_read(std::ifstream &stream) const
Opens the indicated ifstream for reading the file, if possible.
Definition: filename.cxx:1863
FfmpegVirtualFile::close
void close()
Explicitly closes the opened file.
Definition: ffmpegVirtualFile.cxx:159
pandabase.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
VirtualFileSystem::get_file
PointerTo< VirtualFile > get_file(const Filename &filename, bool status_only=false) const
Looks up the file by the indicated name in the file system.
Definition: virtualFileSystem.cxx:516
FfmpegVirtualFile
Enables ffmpeg to access panda's VFS.
Definition: ffmpegVirtualFile.h:34
FfmpegVirtualFile::open_vfs
bool open_vfs(const Filename &filename)
Opens the movie file via Panda's VFS.
Definition: ffmpegVirtualFile.cxx:59
FfmpegVirtualFile::register_protocol
static void register_protocol()
Should be called at startup to attach the appropriate hooks between Panda and FFMpeg.
Definition: ffmpegVirtualFile.cxx:185
VirtualFileSystem
A hierarchy of directories and files that appears to be one continuous file system,...
Definition: virtualFileSystem.h:40
config_ffmpeg.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
VirtualFileSystem::get_global_ptr
static VirtualFileSystem * get_global_ptr()
Returns the default global VirtualFileSystem.
Definition: virtualFileSystem.cxx:742
virtualFileSystem.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
FfmpegVirtualFile::open_subfile
bool open_subfile(const SubfileInfo &info)
Opens the movie file directly from a file on disk (does not go through the VFS).
Definition: ffmpegVirtualFile.cxx:112
VirtualFileSystem::close_read_file
static void close_read_file(std::istream *stream)
Closes a file opened by a previous call to open_read_file().
Definition: virtualFileSystem.cxx:867
VirtualFile
The abstract base class for a file or directory within the VirtualFileSystem.
Definition: virtualFile.h:35
SubfileInfo
This class records a particular byte sub-range within an existing file on disk.
Definition: subfileInfo.h:26
ffmpegVirtualFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::set_binary
void set_binary()
Indicates that the filename represents a binary file.
Definition: filename.I:414