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
FFStream.cpp
Go to the documentation of this file.
1 //
2 // C++ Implementation: Audio::OggStream
3 //
4 
5 #include "config.h"
6 
7 #ifdef HAVE_FFMPEG
8 
9 #include "FFStream.h"
10 
11 #include <utility>
12 #include <limits>
13 #include <math.h>
14 #include <assert.h>
15 
16 #include "vsfilesystem.h"
17 
18 #ifndef BUFFER_SIZE
19 // in samples
20 #define BUFFER_SIZE 4096
21 #endif
22 
23 #ifndef BUFFER_ALIGNMENT
24 #define BUFFER_ALIGNMENT 0x20
25 #endif
26 
27 #include "ffmpeg_init.h"
28 
29 #if (defined(AVCODEC_MAX_AUDIO_FRAME_SIZE) && ((AVCODEC_MAX_AUDIO_FRAME_SIZE) > (BUFFER_SIZE)))
30 
31  #undef BUFFER_SIZE
32  #define BUFFER_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE*3)/2)
33 
34 #endif
35 
36 using namespace std;
37 
38 namespace Audio {
39 
45  class PacketDecodeException : public Exception {
46  public:
47  PacketDecodeException() {}
48  PacketDecodeException(const CodecNotFoundException &other) : Exception(other) {}
49  };
50 
51 
52  namespace __impl {
53  struct FFData {
54  AVFormatContext *pFormatCtx;
55  AVCodecContext *pCodecCtx;
56  AVCodec *pCodec;
57  AVStream *pStream;
58  int streamIndex;
59 
60  uint8_t *packetBuffer;
61  size_t packetBufferSize;
62  AVPacket packet;
63 
64  size_t sampleSize;
65  size_t streamSize; // in samples
66 
67  void *sampleBufferBase;
68  void *sampleBufferAligned;
69  void *sampleBuffer;
70  size_t sampleBufferSize; // in samples
71  size_t sampleBufferAlloc; // in bytes
72  uint64_t sampleBufferStart; // in samples
73 
74  std::string filepath;
75  VSFileSystem::VSFileType filetype;
76  int audioStreamIndex;
77 
78  FFData(const std::string &path, VSFileSystem::VSFileType type, Format &fmt, int streamIdx) throw(Exception) :
79  pFormatCtx(0),
80  pCodecCtx(0),
81  pCodec(0),
82  pStream(0),
83  packetBuffer(0),
84  packetBufferSize(0),
85  sampleBufferBase(0),
86  filepath(path),
87  filetype(type),
88  audioStreamIndex(streamIdx)
89  {
90  packet.data = 0;
91 
92  char buf[(sizeof(type)+1)/2+1];
93  sprintf(buf, "%d", type);
94 
95  // Initialize libavcodec/libavformat if necessary
97 
98  // Open file
99  std::string npath = std::string("vsfile:") + path + "|" + buf;
100  std::string errbase = std::string("Cannot open URL \"") + npath + "\"";
101 
102  if ( (0 != av_open_input_file(&pFormatCtx, npath.c_str(), NULL, BUFFER_SIZE, NULL))
103  ||(0 > av_find_stream_info(pFormatCtx)) )
104  throw FileOpenException(errbase + " (wrong format or file not found)");
105 
106  // Dump format info in case we want to know...
107  #ifdef VS_DEBUG
108  dump_format(pFormatCtx, 0, npath.c_str(), false);
109  #endif
110 
111  // Find audio stream
112  pCodecCtx = 0;
113  streamIndex = -1;
114  for (unsigned int i=0; (pCodecCtx==0) && (i < pFormatCtx->nb_streams); ++i)
115  if ((pFormatCtx->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) && (streamIdx-- == 0))
116  pCodecCtx = (pStream = pFormatCtx->streams[streamIndex = i])->codec;
117  if (pCodecCtx == 0)
118  throw FileOpenException(errbase + " (wrong or no audio stream)");
119 
120  // Find codec for the audio stream and open it
121  pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
122  if(pCodec == 0)
123  throw CodecNotFoundException(errbase + " (unsupported codec)");
124 
125  if(avcodec_open(pCodecCtx, pCodec) < 0)
126  throw CodecNotFoundException(errbase + " (unsupported codec)");
127 
128  // Get some info
129  fmt.sampleFrequency = pCodecCtx->sample_rate;
130  fmt.channels = pCodecCtx->channels;
131  fmt.nativeOrder = 1; // always so for ffmpeg
132  switch (pCodecCtx->sample_fmt) {
133  case SAMPLE_FMT_U8: fmt.bitsPerSample = 8;
134  fmt.signedSamples = 0;
135  break;
136  case SAMPLE_FMT_S16: fmt.bitsPerSample = 16;
137  fmt.signedSamples = 1;
138  break;
139  #ifdef SAMPLE_FMT_S24
140  case SAMPLE_FMT_S24: fmt.bitsPerSample = 24;
141  fmt.signedSamples = 1;
142  break;
143  #endif
144  #ifdef SAMPLE_FMT_S32
145  case SAMPLE_FMT_S32: fmt.bitsPerSample = 32;
146  fmt.signedSamples = 1;
147  break;
148  #endif
149  default: throw CodecNotFoundException(errbase + " (unsupported audio format)");
150  }
151  sampleSize = (fmt.bitsPerSample + 7) / 8 * fmt.channels;
152  assert(sampleSize > 0);
153 
154  // Initialize timebase counter
155  sampleBufferStart = 0;
156  streamSize = 0;
157 
158  // Initialize sample buffer
159  sampleBufferBase = malloc(sampleSize * BUFFER_SIZE + BUFFER_ALIGNMENT);
160  ptrdiff_t offs = ((reinterpret_cast<ptrdiff_t>(sampleBufferBase)) & (BUFFER_ALIGNMENT-1));
161  sampleBufferAligned = ((char*)sampleBufferBase) + BUFFER_ALIGNMENT - offs;
162  sampleBufferAlloc = sampleSize * BUFFER_SIZE;
163  sampleBuffer = 0;
164  sampleBufferSize = 0;
165  }
166 
167  ~FFData()
168  {
169  // Free sample buffer
170  if (sampleBufferBase)
171  free(sampleBufferBase);
172 
173  // Close the codec
174  if (pCodecCtx)
175  avcodec_close(pCodecCtx);
176 
177  // Close the file
178  if (pFormatCtx)
179  av_close_input_file(pFormatCtx);
180  }
181 
182  bool saneTimeStamps() const throw()
183  {
184  return pStream->time_base.num != 0;
185  }
186 
187  int64_t timeToPts(double time) const throw()
188  {
189  return int64_t(floor(time * pStream->time_base.den / pStream->time_base.num));
190  }
191 
192  double ptsToTime(int64_t pts) const throw()
193  {
194  return double(pts) * pStream->time_base.num / pStream->time_base.den;
195  }
196 
197  bool hasFrame() const throw()
198  {
199  return sampleBuffer && sampleBufferSize;
200  }
201 
202  bool hasPacket() const throw()
203  {
204  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
205  return packetBuffer && packetBufferSize
206  && packet.data && packet.size;
207  #else
208  return packetBuffer && packetBufferSize;
209  #endif
210  }
211 
212  void readPacket() throw(EndOfStreamException)
213  {
214  // Read the next packet, skipping all packets that aren't for this stream
215  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
216  packet.size = packetBufferSize;
217  packet.data = packetBuffer;
218  #endif
219  do {
220  // Free old packet
221  if (packet.data != NULL)
222  av_free_packet( &packet );
223 
224  // Read new packet
225  if(av_read_frame(pFormatCtx, &packet) < 0)
226  throw EndOfStreamException();
227  } while(packet.stream_index != streamIndex);
228 
229  packetBufferSize = packet.size;
230  packetBuffer = packet.data;
231 
232  sampleBufferStart = int64_t(floor(ptsToTime(packet.dts) * pCodecCtx->sample_rate));
233  }
234 
235  void syncPts() throw(EndOfStreamException)
236  {
237  if (!hasPacket())
238  throw EndOfStreamException();
239  sampleBufferSize = 0;
240  sampleBufferStart = int64_t(floor(ptsToTime(packet.dts) * pCodecCtx->sample_rate));
241 
242  if (sampleBufferStart > streamSize)
243  streamSize = sampleBufferStart;
244  }
245 
246  void decodeFrame() throw(PacketDecodeException)
247  {
248  if (!hasPacket())
249  throw PacketDecodeException();
250 
251  int dataSize = sampleBufferAlloc;
252  int used =
253  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
254  avcodec_decode_audio3(
255  pCodecCtx,
256  (int16_t*)sampleBufferAligned, &dataSize,
257  &packet);
258  #else
259  avcodec_decode_audio2(
260  pCodecCtx,
261  (int16_t*)sampleBufferAligned, &dataSize,
262  packetBuffer, packetBufferSize);
263  #endif
264 
265  if (used < 0)
266  throw PacketDecodeException();
267 
268  #if (LIBAVCODEC_VERSION_MAJOR >= 53)
269 
270  if ((size_t)used > packet.size)
271  used = packet.size;
272 
273  (char*&)(packet.data) += used;
274  packet.size -= used;
275 
276  #else
277 
278  if ((size_t)used > packetBufferSize)
279  used = packetBufferSize;
280 
281  (char*&)packetBuffer += used;
282  packetBufferSize -= used;
283 
284  #endif
285 
286  if (dataSize < 0)
287  dataSize = 0;
288 
289  sampleBuffer = sampleBufferAligned;
290  sampleBufferStart += sampleBufferSize;
291  sampleBufferSize = dataSize / sampleSize;
292 
293  if (sampleBufferStart + sampleBufferSize > streamSize)
294  streamSize = sampleBufferStart + sampleBufferSize;
295  }
296  };
297  }
298 
299  FFStream::FFStream(const std::string& path, int streamIndex, VSFileSystem::VSFileType type) throw(Exception)
300  : Stream(path)
301  {
302  ffData = new __impl::FFData(path, type, getFormatInternal(), streamIndex);
303  }
304 
305  FFStream::~FFStream()
306  {
307  // destructor closes the file already
308  delete ffData;
309  }
310 
311  double FFStream::getLengthImpl() const throw(Exception)
312  {
313  return double(ffData->streamSize) / getFormat().sampleFrequency;
314  }
315 
316  double FFStream::getPositionImpl() const throw()
317  {
318  return double(ffData->sampleBufferStart) / getFormat().sampleFrequency;
319  }
320 
321  void FFStream::seekImpl(double position) throw(Exception)
322  {
323  if (position < 0)
324  position = 0;
325 
326  // Translate float time to frametime
327  uint64_t targetSample = uint64_t(position * getFormat().sampleFrequency);
328 
329  if ( (targetSample >= ffData->sampleBufferStart)
330  && (targetSample < ffData->sampleBufferStart + ffData->sampleBufferSize) )
331  {
332  // just skip data
333  int advance = int(targetSample - ffData->sampleBufferStart);
334  (char*&)ffData->sampleBuffer += ffData->sampleSize * advance;
335  ffData->sampleBufferStart += advance;
336  ffData->sampleBufferSize -= advance;
337  } else {
338  if (ffData->saneTimeStamps()) {
339  // rough seek
340  avcodec_flush_buffers(ffData->pCodecCtx);
341  av_seek_frame(ffData->pFormatCtx, ffData->streamIndex, ffData->timeToPts(position),
342  (targetSample < ffData->sampleBufferStart + ffData->sampleBufferSize) ? AVSEEK_FLAG_BACKWARD : 0);
343  ffData->syncPts();
344  } else if (targetSample < ffData->sampleBufferStart) {
345  // cannot seek but have to seek backwards, so...
346  // ...close the file and reopen (yack)
347  std::string path = ffData->filepath;
348  VSFileSystem::VSFileType type = ffData->filetype;
349  int streamIndex = ffData->audioStreamIndex;
350 
351  delete ffData;
352  ffData = new __impl::FFData(path, type, getFormatInternal(), streamIndex);
353  }
354 
355  // just skip data (big steps)
356  do {
357  nextBufferImpl();
358  } while (targetSample >= ffData->sampleBufferStart + ffData->sampleBufferSize);
359 
360  // just skip data (small steps)
361  int advance = int(targetSample - ffData->sampleBufferStart);
362  (char*&)ffData->sampleBuffer += ffData->sampleSize * advance;
363  ffData->sampleBufferStart += advance;
364  ffData->sampleBufferSize -= advance;
365  }
366  }
367 
368  void FFStream::getBufferImpl(void *&buffer, unsigned int &bufferSize) throw(Exception)
369  {
370  if (!ffData->hasFrame())
371  throw NoBufferException();
372 
373  buffer = ffData->sampleBuffer;
374  bufferSize = ffData->sampleSize * ffData->sampleBufferSize;
375  }
376 
377  void FFStream::nextBufferImpl() throw(Exception)
378  {
379  if (!ffData->hasPacket())
380  ffData->readPacket();
381  ffData->decodeFrame();
382  }
383 
384 
385 };
386 
387 #endif // HAVE_FFMPEG
388