Vegastrike 0.5.1 rc1  1.0
Original sources for Vegastrike Evolved
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
vid_file.cpp
Go to the documentation of this file.
1 //
2 //C++ Implementation: vid_file
3 //
4 
5 #include "vid_file.h"
6 #include "vsfilesystem.h"
7 #include "config.h"
8 #include "ffmpeg_init.h"
9 
10 #include <string.h>
11 #include <math.h>
12 #include <utility>
13 
14 //define a 128k buffer for video streamers
15 #define BUFFER_SIZE ( 128*(1<<10) )
16 
17 #ifndef ENOENT
18 #define ENOENT (2)
19 #endif
20 #include <sys/types.h>
21 
22 /*
23  * FOLLOWING CODE IS ONLY INCLUDED IF YOU HAVE FFMPEG
24  * ********************************************
25  */
26 #ifdef HAVE_FFMPEG
27 #ifndef offset_t
28 #if (LIBAVCODEC_VERSION_MAJOR >= 52) || (LIBAVCODEC_VERSION_INT >= ( ( 51<<16)+(49<<8)+0 ) ) || defined (__amd64__) \
29  || defined (_M_AMD64) || defined (__x86_64) || defined (__x86_64__)
30 typedef int64_t offset_t;
31  #else
32 typedef int offset_t;
33  #endif
34 #endif
35 using namespace VSFileSystem;
36 
37 class VidFileImpl
38 {
39 private:
40  AVFormatContext *pFormatCtx;
41  AVCodecContext *pCodecCtx;
42  AVCodec *pCodec;
43  AVFrame *pFrameRGB;
44  AVFrame *pFrameYUV;
45  AVFrame *pNextFrameYUV;
46  AVStream *pStream;
47  int videoStreamIndex;
48  bool frameReady;
49 
50  uint8_t *packetBuffer;
51  size_t packetBufferSize;
52  AVPacket packet;
53 
55  size_t fbDimensionLimit;
56  bool fbForcePOT;
57 
58 #ifndef DEPRECATED_IMG_CONVERT
59  SwsContext *pSWSCtx;
60 #endif
61 
62  uint64_t fbPTS;
63  uint64_t sizePTS;
64  uint64_t prevPTS;
65 
66  void convertFrame()
67  {
68  if (frameReady) {
69 #ifdef DEPRECATED_IMG_CONVERT
70  img_convert(
71  (AVPicture*) pFrameRGB, PIX_FMT_RGB24,
72  (AVPicture*) pNextFrameYUV, pCodecCtx->pix_fmt,
73  pCodecCtx->width, pCodecCtx->height );
74 #else
75  sws_scale( pSWSCtx, pNextFrameYUV->data, pNextFrameYUV->linesize, 0,
76  pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize );
77 #endif
78  prevPTS = fbPTS;
79  fbPTS = pNextFrameYUV->pts;
80 
81  if (prevPTS > fbPTS)
82  prevPTS = fbPTS;
83 
84  std::swap( pNextFrameYUV, pFrameYUV );
85  }
86  }
87 
88  void nextFrame(bool skip=false) throw (VidFile::Exception)
89  {
90  int bytesDecoded;
91  int frameFinished;
92  //Decode packets until we have decoded a complete frame
93  while (true) {
94  if (!skip) {
95  //Work on the current packet until we have decoded all of it
96  while (packetBufferSize > 0 && packet.size > 0) {
97  //Decode the next chunk of data
98  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
99  bytesDecoded = avcodec_decode_video2(
100  pCodecCtx, pNextFrameYUV, &frameFinished,
101  &packet );
102  #else
103  bytesDecoded = avcodec_decode_video(
104  pCodecCtx, pNextFrameYUV, &frameFinished,
105  packetBuffer, packetBufferSize );
106  #endif
107  VSFileSystem::vs_dprintf(3, "dts %ld: Decoded %d bytes %s\n",
108  packet.dts,
109  bytesDecoded,
110  (frameFinished ? "Got frame" : "")
111  );
112  //Was there an error?
113  if (bytesDecoded <= 0) throw VidFile::FrameDecodeException( "Error decoding frame" );
114  //Crappy ffmpeg!
115  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
116  if (bytesDecoded > packet.size)
117  bytesDecoded = packet.size;
118  packet.size -= bytesDecoded;
119  packet.data += bytesDecoded;
120  #else
121  if (bytesDecoded > packetBufferSize)
122  bytesDecoded = packetBufferSize;
123  packetBufferSize -= bytesDecoded;
124  packetBuffer += bytesDecoded;
125  #endif
126  //Did we finish the current frame? Then we can return
127  if (frameFinished) {
128  pNextFrameYUV->pts = packet.dts;
129  frameReady = true;
130  return;
131  }
132  }
133  }
134  //Read the next packet, skipping all packets that aren't for this
135  //stream
136  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
137  packet.size = packetBufferSize;
138  packet.data = packetBuffer;
139  #endif
140  do {
141  //Free old packet
142  if (packet.data != NULL)
143  av_free_packet( &packet );
144  //Read new packet
145  if (av_read_frame( pFormatCtx, &packet ) < 0)
147  } while (packet.stream_index != videoStreamIndex);
148  packetBufferSize = packet.size;
149  packetBuffer = packet.data;
150 
151  if (skip)
152  break;
153  }
154  }
155 
156 public:
157  float frameRate;
158  float duration;
159  uint8_t *_frameBuffer;
160  uint8_t *frameBuffer;
161  offset_t frameBufferStride;
162  size_t frameBufferSize;
163  size_t width;
164  size_t height;
165 
166  VidFileImpl( size_t maxDimensions, bool forcePOT ) :
167  pFormatCtx( 0 )
168  , pCodecCtx( 0 )
169  , pCodec( 0 )
170  , pStream( 0 )
171  , pFrameRGB( 0 )
172  , pFrameYUV( 0 )
173  , pNextFrameYUV( 0 )
174  , frameBuffer( 0 )
175  , packetBuffer( 0 )
176  , packetBufferSize( 0 )
177  , frameReady( false )
178  , fbDimensionLimit( maxDimensions )
179  , fbForcePOT( forcePOT )
180  {
181  packet.data = 0;
182  }
183 
184  ~VidFileImpl()
185  {
186  //Free framebuffer
187  if (frameBuffer)
188  delete[] _frameBuffer;
189  if (pFrameRGB)
190  av_free( pFrameRGB );
191  if (pFrameYUV)
192  av_free( pFrameYUV );
193  if (pNextFrameYUV)
194  av_free( pNextFrameYUV );
195 #ifndef DEPRECATED_IMG_CONVERT
196  if (pSWSCtx)
197  sws_freeContext( pSWSCtx );
198 #endif
199  //Close the codec
200  if (pCodecCtx)
201  avcodec_close( pCodecCtx );
202  //Close the file
203  if (pFormatCtx)
204  av_close_input_file( pFormatCtx );
205  }
206 
207  void open( const std::string &path ) throw (VidFile::Exception)
208  {
209  if (pCodecCtx != 0) throw VidFile::Exception( "Already open" );
210  //Initialize libavcodec/libavformat if necessary
212 
213  //Open file
214  std::string npath = std::string( "vsfile:" )+path;
215  std::string errbase = std::string( "Cannot open URL \"" )+npath+"\"";
216  if ( ( 0 != av_open_input_file( &pFormatCtx, npath.c_str(), NULL, BUFFER_SIZE, NULL ) )
217  || ( 0 > av_find_stream_info( pFormatCtx ) ) ) throw VidFile::FileOpenException( errbase+" (wrong format or)" );
218  //Dump format info in case we want to know...
219  #ifdef VS_DEBUG
220  dump_format( pFormatCtx, 0, npath.c_str(), false );
221  #endif
222 
223  //Find first video stream
224  pCodecCtx = 0;
225  videoStreamIndex = -1;
226  VSFileSystem::vs_dprintf(2, "Loaded %s\n", path.c_str());
227  for (int i = 0; i < pFormatCtx->nb_streams; ++i) {
228  VSFileSystem::vs_dprintf(3, " Stream %d: type %s (%d) first dts %ld\n",
229  i,
230  ( (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO) ? "Video"
231  : ( (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) ? "Audio" : "unk" ) ),
232  pFormatCtx->streams[i]->codec->codec_type,
233  pFormatCtx->streams[i]->start_time
234  );
235  if ((pCodecCtx == 0) && (pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO))
236  pCodecCtx = (pStream = pFormatCtx->streams[videoStreamIndex = i])->codec;
237  }
238  if (pCodecCtx == 0) throw VidFile::FileOpenException( errbase+" (no video stream)" );
239  VSFileSystem::vs_dprintf(3, " Codec Timebase: %d/%d\n", pCodecCtx->time_base.num, pCodecCtx->time_base.den);
240 
241  //Find codec for video stream and open it
242  pCodec = avcodec_find_decoder( pCodecCtx->codec_id );
243  if (pCodec == 0) throw VidFile::UnsupportedCodecException( errbase+" (unsupported codec)" );
244  if (avcodec_open( pCodecCtx, pCodec ) < 0) throw VidFile::UnsupportedCodecException( errbase+" (unsupported codec)" );
245  pFrameYUV = avcodec_alloc_frame();
246  pNextFrameYUV = avcodec_alloc_frame();
247  if ( (pFrameYUV == 0) || (pNextFrameYUV == 0) ) throw VidFile::Exception(
248  "Problem during YUV framebuffer initialization" );
249  //Get some info
250  frameRate = float(pStream->r_frame_rate.num)/float(pStream->r_frame_rate.den);
251  duration = float(pStream->duration*pStream->time_base.num)/float(pStream->time_base.den);
252  VSFileSystem::vs_dprintf(3, " Framerate: %d/%d\n", pStream->r_frame_rate.num, pStream->r_frame_rate.den);
253  VSFileSystem::vs_dprintf(3, " Stream timebase: %d/%d\n", pStream->time_base.num, pStream->time_base.den);
254 
255  //Get POT dimensions
256  if (fbForcePOT) {
257  width = height = 1;
258  while (width < pCodecCtx->width && width <= (fbDimensionLimit/2)) width *= 2;
259  while (height < pCodecCtx->height && height <= (fbDimensionLimit/2)) height *= 2;
260  } else {
261  width = pCodecCtx->width;
262  height = pCodecCtx->height;
263  while ( (width > fbDimensionLimit) || (height > fbDimensionLimit) ) {
264  width /= 2;
265  height /= 2;
266  }
267  }
268  VSFileSystem::vs_dprintf(2, " playing at %dx%d\n", width, height);
269 
270  //Allocate RGB frame buffer
271  pFrameRGB = avcodec_alloc_frame();
272  if (pFrameRGB == 0) throw VidFile::Exception( "Problem during RGB framebuffer initialization" );
273  frameBufferSize = avpicture_get_size( PIX_FMT_RGB24, width, height );
274  _frameBuffer = new uint8_t[frameBufferSize];
275  if (_frameBuffer == 0) throw VidFile::Exception( "Problem during RGB framebuffer initialization" );
276  avpicture_fill( (AVPicture*) pFrameRGB, _frameBuffer, PIX_FMT_RGB24, width, height );
277  frameBuffer = pFrameRGB->data[0];
278  frameBufferSize = pFrameRGB->linesize[0]*height;
279  frameBufferStride = pFrameRGB->linesize[0];
280 
281  //Initialize timebase counters
282  prevPTS =
283  fbPTS =
284  pFrameYUV->pts =
285  pNextFrameYUV->pts = 0;
286 
287 #ifndef DEPRECATED_IMG_CONVERT
288  pSWSCtx = sws_getContext( pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
289  width, height, PIX_FMT_RGB24, SWS_LANCZOS|SWS_PRINT_INFO, NULL, NULL, NULL );
290 #endif
291  }
292 
293  bool seek( float time )
294  {
295  if (time < 0)
296  time = 0;
297 
298  //Translate float time to frametime
299  int64_t targetPTS = int64_t( floor( double(time)*pStream->time_base.den/pStream->time_base.num ) );
300  VSFileSystem::vs_dprintf(3, "Seeking to %.3fs pts %ld\n", time, targetPTS);
301  if ( (targetPTS >= prevPTS) && (targetPTS < pNextFrameYUV->pts) ) {
302  //same frame
303  if (targetPTS >= fbPTS) {
304  try {
305  prevPTS = fbPTS;
306  convertFrame();
307  nextFrame();
308  return true;
309  }
311  sizePTS = fbPTS+1; throw e;
312  }
313  }
314  return false;
315  } else {
316  if (targetPTS < fbPTS) {
317  //frame backwards
318  int64_t backPTS = targetPTS - 1 - pStream->time_base.den/pStream->time_base.num/2;
319  if (backPTS < 0)
320  backPTS = 0;
321 
322  VSFileSystem::vs_dprintf(3, "backseeking to %ld (at %ld)\n", backPTS, pNextFrameYUV->pts);
323  av_seek_frame( pFormatCtx, videoStreamIndex, backPTS, AVSEEK_FLAG_BACKWARD );
324 
325  prevPTS = backPTS;
326  nextFrame();
327  }
328  //frame forward
329  try {
330  // Try one frame, decoding
331  if (pNextFrameYUV->pts < targetPTS) {
332  prevPTS = pNextFrameYUV->pts;
333  nextFrame();
334  VSFileSystem::vs_dprintf(3, "decoding to %ld (at %ld-%ld)\n", targetPTS, prevPTS, pNextFrameYUV->pts);
335  }
336  // If we have to skip more frames, don't decode, only skip data
337  while (packet.dts < targetPTS) {
338  prevPTS = packet.dts;
339  nextFrame(true);
340  VSFileSystem::vs_dprintf(3, "skipping to %ld (at %ld-%ld)\n", targetPTS, prevPTS, packet.dts);
341  }
342  // we're close, decode now
343  while (pNextFrameYUV->pts < targetPTS) {
344  prevPTS = pNextFrameYUV->pts;
345  nextFrame();
346  VSFileSystem::vs_dprintf(3, "decoding to %ld (at %ld-%ld)\n", targetPTS, prevPTS, pNextFrameYUV->pts);
347  }
348  convertFrame();
349  nextFrame();
350  }
352  sizePTS = fbPTS+1; throw e;
353  }
354 
355  return true;
356  }
357  }
358 };
359 
360 #else /* !HAVE_FFMPEG */
362 {
363 private:
364  VidFileImpl(size_t, bool) {}
365 public:
366  //Avoid having to put ifdef's everywhere.
367  float frameRate, duration;
368  int width, height;
369  void *frameBuffer;
371  bool seek( float time )
372  {
373  return false;
374  }
375 };
376 
377 #endif /* !HAVE_FFMPEG */
378 /* ************************************ */
379 
380 VidFile::VidFile() throw () :
381  impl( NULL )
382 {}
383 
385 {
386  if (impl)
387  delete impl;
388 }
389 
390 bool VidFile::isOpen() const throw ()
391 {
392  return impl != NULL;
393 }
394 
395 void VidFile::open( const std::string &path, size_t maxDimension, bool forcePOT ) throw (Exception)
396 {
397 #ifdef HAVE_FFMPEG
398  if (!impl)
399  impl = new VidFileImpl( maxDimension, forcePOT );
400  if (impl)
401  impl->open( path );
402 #endif
403 }
404 
405 void VidFile::close() throw ()
406 {
407  if (impl) {
408  delete impl;
409  impl = 0;
410  }
411 }
412 
413 float VidFile::getFrameRate() const throw ()
414 {
415  return impl ? impl->frameRate : 0;
416 }
417 
418 float VidFile::getDuration() const throw ()
419 {
420  return impl ? impl->duration : 0;
421 }
422 
423 int VidFile::getWidth() const throw ()
424 {
425  return impl ? impl->width : 0;
426 }
427 
428 int VidFile::getHeight() const throw ()
429 {
430  return impl ? impl->height : 0;
431 }
432 
433 void* VidFile::getFrameBuffer() const throw ()
434 {
435  return impl ? impl->frameBuffer : 0;
436 }
437 
438 int VidFile::getFrameBufferStride() const throw ()
439 {
440  return impl ? impl->frameBufferStride : 0;
441 }
442 
443 bool VidFile::seek( float time ) throw (Exception)
444 {
445  return (impl != 0) && impl->seek( time );
446 }
447