aboutsummaryrefslogtreecommitdiffstats
path: root/mediaplugin/src/plugins
diff options
context:
space:
mode:
authortobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2010-11-05 09:31:03 +0000
committertobigun <tobigun@b956fd51-792f-4845-bead-9b4dfca2ff2c>2010-11-05 09:31:03 +0000
commit4b808ab7f4b12551f5737bb666361efd79f62a3a (patch)
tree121a00d5ecf189837a7f49438722864ed260d09d /mediaplugin/src/plugins
parent3a079aafc4f98dc83ea36858bbca071342beb03d (diff)
downloadusdx-4b808ab7f4b12551f5737bb666361efd79f62a3a.tar.gz
usdx-4b808ab7f4b12551f5737bb666361efd79f62a3a.tar.xz
usdx-4b808ab7f4b12551f5737bb666361efd79f62a3a.zip
media plugins
git-svn-id: svn://svn.code.sf.net/p/ultrastardx/svn/branches/experimental@2710 b956fd51-792f-4845-bead-9b4dfca2ff2c
Diffstat (limited to 'mediaplugin/src/plugins')
-rw-r--r--mediaplugin/src/plugins/media/core/logger.cpp31
-rw-r--r--mediaplugin/src/plugins/media/core/plugin_core.cpp40
-rw-r--r--mediaplugin/src/plugins/media/core/util.cpp30
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.cpp74
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.h132
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.cpp705
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.h257
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.cpp412
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.h136
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.cpp82
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.h30
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.cpp661
-rw-r--r--mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.h115
-rw-r--r--mediaplugin/src/plugins/media/include/core/begin_pack.h36
-rw-r--r--mediaplugin/src/plugins/media/include/core/end_pack.h35
-rw-r--r--mediaplugin/src/plugins/media/include/core/logger.h73
-rw-r--r--mediaplugin/src/plugins/media/include/core/plugin_audio_convert.h69
-rw-r--r--mediaplugin/src/plugins/media/include/core/plugin_audio_decode.h51
-rw-r--r--mediaplugin/src/plugins/media/include/core/plugin_core.h222
-rw-r--r--mediaplugin/src/plugins/media/include/core/plugin_video_decode.h53
-rw-r--r--mediaplugin/src/plugins/media/include/core/util.h292
21 files changed, 3536 insertions, 0 deletions
diff --git a/mediaplugin/src/plugins/media/core/logger.cpp b/mediaplugin/src/plugins/media/core/logger.cpp
new file mode 100644
index 00000000..fbdeb6a1
--- /dev/null
+++ b/mediaplugin/src/plugins/media/core/logger.cpp
@@ -0,0 +1,31 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "core/logger.h"
+
+#ifdef __cplusplus
+
+Logger Logger::_instance;
+
+#endif /* __cplusplus */
diff --git a/mediaplugin/src/plugins/media/core/plugin_core.cpp b/mediaplugin/src/plugins/media/core/plugin_core.cpp
new file mode 100644
index 00000000..751b93fa
--- /dev/null
+++ b/mediaplugin/src/plugins/media/core/plugin_core.cpp
@@ -0,0 +1,40 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "core/plugin_core.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const pluginCore_t *pluginCore;
+
+BOOL pluginInitCore(const pluginCore_t *core) {
+ pluginCore = core;
+ return TRUE;
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/mediaplugin/src/plugins/media/core/util.cpp b/mediaplugin/src/plugins/media/core/util.cpp
new file mode 100644
index 00000000..02487521
--- /dev/null
+++ b/mediaplugin/src/plugins/media/core/util.cpp
@@ -0,0 +1,30 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "core/util.h"
+
+PLUGIN_CALL int threadMainRoutine(void *data) {
+ Thread *thread = (Thread *)data;
+ return thread->run();
+}
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.cpp b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.cpp
new file mode 100644
index 00000000..ccbc5400
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.cpp
@@ -0,0 +1,74 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "ffmpeg_audio_convert.h"
+
+/************************************
+ * C Interface
+ ************************************/
+
+#define ConvertStreamObj(ptr) reinterpret_cast<FFmpegAudioConvertStream*>(ptr)
+
+static BOOL PLUGIN_CALL ffmpegAudioConverter_init() {
+ return TRUE;
+}
+
+static BOOL PLUGIN_CALL ffmpegAudioConverter_finalize() {
+ return TRUE;
+}
+
+static audioConvertStream_t* PLUGIN_CALL ffmpegAudioConverter_open(audioFormatInfo_t *inputFormat, audioFormatInfo_t *outputFormat) {
+ return (audioConvertStream_t*)FFmpegAudioConvertStream::open(inputFormat, outputFormat);
+}
+
+static void PLUGIN_CALL ffmpegAudioConverter_close(audioConvertStream_t *stream) {
+ delete ConvertStreamObj(stream);
+}
+
+static int PLUGIN_CALL ffmpegAudioConverter_convert(audioConvertStream_t *stream, uint8_t *input, uint8_t *output, int *numSamples) {
+ return ConvertStreamObj(stream)->convert(input, output, *numSamples);
+}
+
+static int PLUGIN_CALL ffmpegAudioConverter_getOutputBufferSize(audioConvertStream_t *stream, int inputSize) {
+ return ConvertStreamObj(stream)->getOutputBufferSize(inputSize);
+}
+
+static double PLUGIN_CALL ffmpegAudioConverter_getRatio(audioConvertStream_t *stream) {
+ return ConvertStreamObj(stream)->getRatio();
+}
+
+/************************************
+ * Module information
+ ************************************/
+
+const audioConverterInfo_t audioConverterInfo = {
+ 70,
+ ffmpegAudioConverter_init,
+ ffmpegAudioConverter_finalize,
+ ffmpegAudioConverter_open,
+ ffmpegAudioConverter_close,
+ ffmpegAudioConverter_convert,
+ ffmpegAudioConverter_getOutputBufferSize,
+ ffmpegAudioConverter_getRatio
+};
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.h b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.h
new file mode 100644
index 00000000..f2b44e48
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_convert.h
@@ -0,0 +1,132 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _FFMPEG_AUDIO_CONVERT_H_
+#define _FFMPEG_AUDIO_CONVERT_H_
+
+#include "ffmpeg_core.h"
+#include "core/plugin_audio_convert.h"
+
+extern const audioConverterInfo_t audioConverterInfo;
+
+// Note: FFmpeg seems to be using "kaiser windowed sinc" for resampling, so
+// the quality should be good.
+class FFmpegAudioConvertStream : public AudioConvertStream {
+private:
+ ReSampleContext *_resampleContext;
+ double _ratio;
+
+protected:
+ FFmpegAudioConvertStream(const AudioFormatInfo &srcFormatInfo, const AudioFormatInfo &dstFormatInfo) :
+ AudioConvertStream(srcFormatInfo, dstFormatInfo),
+ _resampleContext(NULL),
+ _ratio(0) {}
+
+ bool init() {
+ // Note: FFmpeg does not support resampling for more than 2 input channels
+
+ if (_srcFormatInfo.getFormat() != asfS16) {
+ logger.error("Unsupported format", "TAudioConverter_FFmpeg.Init");
+ return false;
+ }
+
+ if (_srcFormatInfo.getFormat() != _dstFormatInfo.getFormat()) {
+ logger.error("Incompatible formats", "TAudioConverter_FFmpeg.Init");
+ return false;
+ }
+
+#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52,15,0)
+ // use same values as the deprecated audio_resample_init()
+ const int TAPS = 16;
+ _resampleContext = av_audio_resample_init(
+ _dstFormatInfo.getChannels(),
+ _srcFormatInfo.getChannels(),
+ lround(_dstFormatInfo.getSampleRate()),
+ lround(_srcFormatInfo.getSampleRate()),
+ SAMPLE_FMT_S16, SAMPLE_FMT_S16,
+ TAPS, 10, 0, 0.8);
+#else
+ _resampleContext = audio_resample_init(
+ dstFormatInfo.getChannels(),
+ srcFormatInfo.getChannels(),
+ lround(dstFormatInfo.getSampleRate()),
+ lround(srcFormatInfo.getSampleRate()));
+#endif
+
+ if (!_resampleContext) {
+ logger.error("audio_resample_init() failed", "TAudioConverter_FFmpeg.Init");
+ return false;
+ }
+
+ // calculate ratio
+ _ratio = ((double)_dstFormatInfo.getChannels() / _srcFormatInfo.getChannels()) *
+ (_dstFormatInfo.getSampleRate() / _srcFormatInfo.getSampleRate());
+
+ return true;
+ }
+
+public:
+ virtual ~FFmpegAudioConvertStream() {
+ if (_resampleContext)
+ audio_resample_close(_resampleContext);
+ }
+
+ static FFmpegAudioConvertStream* open(const AudioFormatInfo &srcFormatInfo, const AudioFormatInfo &dstFormatInfo) {
+ FFmpegAudioConvertStream *converter = new FFmpegAudioConvertStream(srcFormatInfo, dstFormatInfo);
+ if (!converter->init()) {
+ delete converter;
+ return 0;
+ }
+ return converter;
+ }
+
+ virtual int convert(uint8_t *inputBuffer, uint8_t *outputBuffer, int &inputSize) {
+ if (inputSize <= 0) {
+ // avoid div-by-zero in audio_resample()
+ return (inputSize == 0) ? 0 : -1;
+ }
+
+ int inputSampleCount = inputSize / _srcFormatInfo.getFrameSize();
+ int outputSampleCount = audio_resample(_resampleContext,
+ (short*)outputBuffer, (short*)inputBuffer, inputSampleCount);
+ if (outputSampleCount == -1) {
+ logger.error("audio_resample() failed", "TAudioConverter_FFmpeg.Convert");
+ return -1;
+ }
+ return outputSampleCount * _dstFormatInfo.getFrameSize();
+ }
+
+ /**
+ * Destination/Source size ratio
+ */
+ virtual double getRatio() {
+ return _ratio;
+ }
+
+ virtual int getOutputBufferSize(int inputSize) {
+ return ceil(inputSize * getRatio());
+ }
+};
+
+#endif /* _FFMPEG_AUDIO_CONVERT_H_ */
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.cpp b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.cpp
new file mode 100644
index 00000000..da49891c
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.cpp
@@ -0,0 +1,705 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+/*******************************************************************************
+ *
+ * This code is primarily based upon -
+ * http://www.dranger.com/ffmpeg/ffmpegtutorial_all.html
+ *
+ * and tutorial03.c
+ *
+ * http://www.inb.uni-luebeck.de/~boehme/using_libavcodec.html
+ *
+ *******************************************************************************/
+
+#include "ffmpeg_audio_decode.h"
+#include <string>
+#include <sstream>
+#include <cmath>
+
+// show FFmpeg specific debug output
+//#define DEBUG_FFMPEG_DECODE
+
+// FFmpeg is very verbose and shows a bunch of errors.
+// Those errors (they can be considered as warnings by us) can be ignored
+// as they do not give any useful information.
+// There is no solution to fix this except for turning them off.
+//#define ENABLE_FFMPEG_ERROR_OUTPUT
+
+#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
+
+/* FFmpegDecodeStream */
+
+FFmpegAudioDecodeStream::FFmpegAudioDecodeStream() :
+ _eofState(false),
+ _errorState(false),
+ _quitRequest(false),
+ _parserLocked(false),
+ _parserPauseRequestCount(0),
+ _seekRequest(false),
+ _seekFlags(0),
+ _seekPos(0),
+ _seekFlush(false),
+ _loop(false),
+ _formatCtx(NULL),
+ _codecCtx(NULL),
+ _codec(NULL),
+ _audioStreamIndex(-1),
+ _audioStream(NULL),
+ _audioStreamPos(0.0),
+ _decoderLocked(false),
+ _decoderPauseRequestCount(0),
+ _audioPaketSilence(0),
+ _audioBufferPos(0),
+ _audioBufferSize(0),
+ _filename("")
+{
+ memset(&_audioPaket, 0, sizeof(_audioPaket));
+ memset(&_audioPaketTemp, 0, sizeof(_audioPaketTemp));
+}
+
+FFmpegAudioDecodeStream* FFmpegAudioDecodeStream::open(const IPath &filename) {
+ FFmpegAudioDecodeStream *stream = new FFmpegAudioDecodeStream();
+ if (!stream->_open(filename)) {
+ delete stream;
+ return 0;
+ }
+ return stream;
+}
+
+bool FFmpegAudioDecodeStream::_open(const IPath &filename) {
+ _filename = filename;
+ if (!filename.isFile()) {
+ logger.error("Audio-file does not exist: '" + filename.toNative() + "'", "UAudio_FFmpeg");
+ return false;
+ }
+
+ // use custom 'ufile' protocol for UTF-8 support
+ if (av_open_input_file(&_formatCtx,
+ ("ufile:" + filename.toUTF8()).c_str(), NULL, 0, NULL) != 0)
+ {
+ logger.error("av_open_input_file failed: '" + filename.toNative() + "'", "UAudio_FFmpeg");
+ return false;
+ }
+
+ // generate PTS values if they do not exist
+ _formatCtx->flags |= AVFMT_FLAG_GENPTS;
+
+ // retrieve stream information
+ if (av_find_stream_info(_formatCtx) < 0) {
+ logger.error("av_find_stream_info failed: '" + filename.toNative() + "'", "UAudio_FFmpeg");
+ return false;
+ }
+
+ // FIXME: hack used by ffplay. Maybe should not use url_feof() to test for the end
+ _formatCtx->pb->eof_reached = 0;
+
+#ifdef DEBUG_FFMPEG_DECODE
+ dump_format(_formatCtx, 0, filename.toNative(), 0);
+#endif
+
+ _audioStreamIndex = ffmpegCore->findAudioStreamIndex(_formatCtx);
+ if (_audioStreamIndex < 0) {
+ logger.error("FindAudioStreamIndex: No Audio-stream found '" + filename.toNative() + "'", "UAudio_FFmpeg");
+ return false;
+ }
+
+ //std::stringstream s;
+ //s << _audioStreamIndex;
+ //logger.status("AudioStreamIndex is: " + s.str(), "UAudio_FFmpeg");
+
+ _audioStream = _formatCtx->streams[_audioStreamIndex];
+ _audioStreamPos = 0;
+ _codecCtx = _audioStream->codec;
+
+#ifdef REQUEST_CHANNELS
+ // TODO: should we use this or not? Should we allow 5.1 channel audio?
+ #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51,42,0)
+ if (_codecCtx->channels > 0)
+ _codecCtx->request_channels = std::min(2, _codecCtx->channels);
+ else
+ _codecCtx->request_channels = 2;
+ #endif
+#endif
+
+ _codec = avcodec_find_decoder(_codecCtx->codec_id);
+ if (!_codec) {
+ logger.error("Unsupported codec!", "UAudio_FFmpeg");
+ _codecCtx = NULL;
+ return false;
+ }
+
+ // set debug options
+ _codecCtx->debug_mv = 0;
+ _codecCtx->debug = 0;
+
+ // detect bug-workarounds automatically
+ _codecCtx->workaround_bugs = FF_BUG_AUTODETECT;
+ // error resilience strategy (careful/compliant/agressive/very_aggressive)
+ //CodecCtx->error_resilience = FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+ // allow non spec compliant speedup tricks.
+ //CodecCtx->flags2 |= CODEC_FLAG2_FAST;
+
+ // Note: avcodec_open() and avcodec_close() are not thread-safe and will
+ // fail if called concurrently by different threads.
+ int avResult;
+ {
+ MediaCore_FFmpeg::AVCodecLock codecLock;
+ avResult = avcodec_open(_codecCtx, _codec);
+ }
+ if (avResult < 0) {
+ logger.error("avcodec_open failed!", "UAudio_FFmpeg");
+ return false;
+ }
+
+ // now initialize the audio-format
+
+ audioSampleFormat_t sampleFormat;
+ if (!ffmpegCore->convertFFmpegToAudioFormat(_codecCtx->sample_fmt, &sampleFormat)) {
+ // try standard format
+ sampleFormat = asfS16;
+ }
+ if (_codecCtx->channels > 255) {
+ logger.status("Error: _codecCtx->channels > 255", "TFFmpegDecodeStream.Open");
+ }
+ _formatInfo = AudioFormatInfo(
+ _codecCtx->channels,
+ _codecCtx->sample_rate,
+ sampleFormat
+ );
+
+ // finally start the decode thread
+ start();
+
+ return true;
+}
+
+void FFmpegAudioDecodeStream::close() {
+ // wake threads waiting for packet-queue data
+ // Note: normally, there are no waiting threads. If there were waiting
+ // ones, they would block the audio-callback thread.
+ _packetQueue.abort();
+
+ // send quit request (to parse-thread etc)
+ {
+ Mutex::RegionLock lock(_stateLock);
+ _quitRequest = true;
+ _parserIdleCond.broadcast();
+ }
+
+ // abort parse-thread
+ // and wait until it terminates
+ wait();
+
+ // Close the codec
+ if (_codecCtx) {
+ // avcodec_close() is not thread-safe
+ MediaCore_FFmpeg::AVCodecLock codecLock;
+ avcodec_close(_codecCtx);
+ }
+
+ // Close the video file
+ if (_formatCtx) {
+ av_close_input_file(_formatCtx);
+ }
+}
+
+double FFmpegAudioDecodeStream::getLength() {
+ // do not forget to consider the start_time value here
+ // there is a type size mismatch warnign because start_time and duration are cint64.
+ // So, in principle there could be an overflow when doing the sum.
+ return (double)(_formatCtx->start_time + _formatCtx->duration) / AV_TIME_BASE;
+}
+
+double FFmpegAudioDecodeStream::getPosition() {
+ DecoderPauser decoderPauser(this);
+
+ // ReadData() does not return all of the buffer retrieved by DecodeFrame().
+ // Determine the size of the unused part of the decode-buffer.
+ double bufferSizeSec = (double)(_audioBufferSize - _audioBufferPos) /
+ _formatInfo.getBytesPerSec();
+
+ // subtract the size of unused buffer-data from the audio clock.
+ return _audioStreamPos - bufferSizeSec;
+}
+
+void FFmpegAudioDecodeStream::setPosition(double time) {
+ setPositionIntern(time, true, true);
+}
+
+/********************************************
+* Parser section
+********************************************/
+
+void FFmpegAudioDecodeStream::setPositionIntern(double time, bool flush, bool blocking) {
+ // - Pause the parser first to prevent it from putting obsolete packages
+ // into the queue after the queue was flushed and before seeking is done.
+ // Otherwise we will hear fragments of old data, if the stream was seeked
+ // in stopped mode and resumed afterwards (applies to non-blocking mode only).
+ // - Pause the decoder to avoid race-condition that might occur otherwise.
+ // - Last lock the state lock because we are manipulating some shared state-vars.
+ {
+ ParserPauser parserPauser(this);
+ DecoderPauser decoderPauser(this);
+ Mutex::RegionLock lock(_stateLock);
+
+ _eofState = false;
+ _errorState = false;
+
+ // do not seek if we are already at the correct position.
+ // This is important especially for seeking to position 0 if we already are
+ // at the beginning. Although seeking with AVSEEK_FLAG_BACKWARD for pos 0 works,
+ // it is still a bit choppy (although much better than w/o AVSEEK_FLAG_BACKWARD).
+ if (time == _audioStreamPos)
+ return;
+
+ // configure seek parameters
+ _seekPos = time;
+ _seekFlush = flush;
+ _seekFlags = AVSEEK_FLAG_ANY;
+ _seekRequest = true;
+
+ // Note: the BACKWARD-flag seeks to the first position <= the position
+ // searched for. Otherwise e.g. position 0 might not be seeked correct.
+ // For some reason ffmpeg sometimes doesn't use position 0 but the key-frame
+ // following. In streams with few key-frames (like many flv-files) the next
+ // key-frame after 0 might be 5secs ahead.
+ if (time <= _audioStreamPos)
+ _seekFlags |= AVSEEK_FLAG_BACKWARD;
+
+ // send a reuse signal in case the parser was stopped (e.g. because of an EOF)
+ _parserIdleCond.signal();
+ }
+
+ // in blocking mode, wait until seeking is done
+ if (blocking) {
+ Mutex::RegionLock lock(_stateLock);
+ while (_seekRequest)
+ _seekFinishedCond.wait(_stateLock);
+ }
+}
+
+int FFmpegAudioDecodeStream::run() {
+ // reuse thread as long as the stream is not terminated
+ while (parseLoop()) {
+ // wait for reuse or destruction of stream
+ Mutex::RegionLock lock(_stateLock);
+ while (!(_seekRequest || _quitRequest)) {
+ _parserIdleCond.wait(_stateLock);
+ }
+ }
+ return 0;
+}
+
+/**
+* Parser main loop.
+* Will not return until parsing of the stream is finished.
+* Reasons for the parser to return are:
+* - the end-of-file is reached
+* - an error occured
+* - the stream was quited (received a quit-request)
+* Returns true if the stream can be resumed or false if the stream has to
+* be terminated.
+*/
+bool FFmpegAudioDecodeStream::parseLoop() {
+ AVPacket packet;
+ while (true) {
+ ParserLock parserLock(this);
+ if (isQuit()) {
+ return false;
+ }
+
+ // handle seek-request (Note: no need to lock SeekRequest here)
+ if (_seekRequest) {
+ // first try: seek on the audio stream
+ int64_t seekTarget = llround(_seekPos / av_q2d(_audioStream->time_base));
+ // duration of silence at start of stream
+ double startSilence = 0;
+ if (seekTarget < _audioStream->start_time)
+ startSilence = (double)(_audioStream->start_time - seekTarget) * av_q2d(_audioStream->time_base);
+ int errorCode = av_seek_frame(_formatCtx, _audioStreamIndex, seekTarget, _seekFlags);
+
+ if (errorCode < 0) {
+ // second try: seek on the default stream (necessary for flv-videos and some ogg-files)
+ seekTarget = llround(_seekPos * AV_TIME_BASE);
+ startSilence = 0;
+ if (seekTarget < _formatCtx->start_time)
+ startSilence = (double)(_formatCtx->start_time - seekTarget) / AV_TIME_BASE;
+ errorCode = av_seek_frame(_formatCtx, -1, seekTarget, _seekFlags);
+ }
+
+ // pause decoder and lock state (keep the lock-order to avoid deadlocks).
+ // Note that the decoder does not block in the packet-queue in seeking state,
+ // so locking the decoder here does not cause a dead-lock.
+ {
+ DecoderPauser pause(this);
+ Mutex::RegionLock lock(_stateLock);
+
+ if (errorCode < 0) {
+ // seeking failed
+ _errorState = true;
+ std::stringstream s;
+ s << "Seek Error in '" << _formatCtx->filename << "'";
+ logger.error(s.str(), "UAudioDecoder_FFmpeg");
+ } else {
+ if (_seekFlush) {
+ // flush queue (we will send a Flush-Packet when seeking is finished)
+ _packetQueue.flush();
+
+ // flush the decode buffers
+ _audioBufferSize = 0;
+ _audioBufferPos = 0;
+ _audioPaketSilence = 0;
+ memset(&_audioPaketTemp, 0, sizeof(_audioPaketTemp));
+ flushCodecBuffers();
+
+ // Set preliminary stream position. The position will be set to
+ // the correct value as soon as the first packet is decoded.
+ _audioStreamPos = _seekPos;
+ } else {
+ // request avcodec buffer flush
+ _packetQueue.putStatus(PKT_STATUS_FLAG_FLUSH, NULL);
+ }
+
+ // fill the gap between position 0 and start_time with silence
+ // but not if we are in loop mode
+ if (startSilence > 0 && !_loop) {
+ // pointer for the EMPTY status packet
+ double *startSilencePtr = (double*)malloc(sizeof(startSilence));
+ *startSilencePtr = startSilence;
+ _packetQueue.putStatus(PKT_STATUS_FLAG_EMPTY, startSilencePtr);
+ }
+ }
+ _seekRequest = false;
+ _seekFinishedCond.broadcast();
+ }
+ }
+
+ if (_packetQueue.getSize() > MAX_AUDIOQ_SIZE) {
+ Thread::sleep(10);
+ continue;
+ }
+
+ if (av_read_frame(_formatCtx, &packet) < 0) {
+ // failed to read a frame, check reason
+ ByteIOContext *byteIOCtx;
+#if LIBAVFORMAT_VERSION_MAJOR >= 52
+ byteIOCtx = _formatCtx->pb;
+#else
+ byteIOCtx = &_formatCtx->pb;
+#endif
+
+ // check for end-of-file (eof is not an error)
+ if (url_feof(byteIOCtx) != 0) {
+ if (getLoop()) {
+ // rewind stream (but do not flush)
+ setPositionIntern(0, false, false);
+ continue;
+ } else {
+ // signal end-of-file
+ _packetQueue.putStatus(PKT_STATUS_FLAG_EOF, NULL);
+ return true;
+ }
+ }
+
+ // check for errors
+ if (url_ferror(byteIOCtx) != 0) {
+ // an error occured -> abort and wait for repositioning or termination
+ _packetQueue.putStatus(PKT_STATUS_FLAG_ERROR, NULL);
+ return true;
+ }
+
+ // url_feof() does not detect an EOF for some files
+ // so we have to do it this way.
+ if ((_formatCtx->file_size != 0) && (byteIOCtx->pos >= _formatCtx->file_size)) {
+ _packetQueue.putStatus(PKT_STATUS_FLAG_EOF, NULL);
+ return true;
+ }
+
+ // unknown error occured, exit
+ _packetQueue.putStatus(PKT_STATUS_FLAG_ERROR, NULL);
+ return true;
+ }
+
+ if (packet.stream_index == _audioStreamIndex) {
+ _packetQueue.put(&packet);
+ } else {
+ av_free_packet(&packet);
+ }
+ }
+}
+
+/********************************************
+* Decoder section
+********************************************/
+
+void FFmpegAudioDecodeStream::flushCodecBuffers() {
+ // if no flush operation is specified, avcodec_flush_buffers will not do anything.
+ if (_codecCtx->codec->flush) {
+ // flush buffers used by avcodec_decode_audio, etc.
+ avcodec_flush_buffers(_codecCtx);
+ } else {
+ // we need a Workaround to avoid plopping noise with ogg-vorbis and
+ // mp3 (in older versions of FFmpeg).
+ // We will just reopen the codec.
+ MediaCore_FFmpeg::AVCodecLock codecLock;
+ avcodec_close(_codecCtx);
+ avcodec_open(_codecCtx, _codec);
+ }
+}
+
+int FFmpegAudioDecodeStream::decodeFrame(uint8_t *buffer, int bufferSize) {
+ if (isEOF())
+ return -1;
+
+ while (true) {
+ // for titles with start_time > 0 we have to generate silence
+ // until we reach the pts of the first data packet.
+ if (_audioPaketSilence > 0) {
+ int dataSize = std::min(_audioPaketSilence, bufferSize);
+ memset(buffer, 0, dataSize);
+ _audioPaketSilence -= dataSize;
+ _audioStreamPos += dataSize / _formatInfo.getBytesPerSec();
+ return dataSize;
+ }
+
+ // read packet data
+ while (_audioPaketTemp.size > 0) {
+ // size of output data decoded by FFmpeg
+ int dataSize = bufferSize;
+
+ int paketDecodedSize; // size of packet data used for decoding
+ paketDecodedSize = avcodec_decode_audio3(_codecCtx, (int16_t*)buffer,
+ &dataSize, &_audioPaketTemp);
+
+ if (paketDecodedSize < 0) {
+ // if error, skip frame
+#ifdef DEBUG_FFMPEG_DECODE
+ logger.status("Skip audio frame", "");
+#endif
+ _audioPaketTemp.size = 0;
+ break;
+ }
+
+ _audioPaketTemp.data += paketDecodedSize;
+ _audioPaketTemp.size -= paketDecodedSize;
+
+ // check if avcodec_decode_audio returned data, otherwise fetch more frames
+ if (dataSize <= 0)
+ continue;
+
+ // update stream position by the amount of fetched data
+ _audioStreamPos += dataSize / _formatInfo.getBytesPerSec();
+
+ // we have data, return it and come back for more later
+ return dataSize;
+ }
+
+ // free old packet data
+ if (_audioPaket.data)
+ av_free_packet(&_audioPaket);
+
+ // do not block queue on seeking (to avoid deadlocks on the DecoderLock)
+ bool blockQueue = !isSeeking();
+
+ // request a new packet and block if none available.
+ // If this fails, the queue was aborted.
+ if (_packetQueue.get(&_audioPaket, blockQueue) <= 0)
+ return -1;
+
+ // handle Status-packet
+ if (_audioPaket.data == STATUS_PACKET) {
+ _audioPaket.data = NULL;
+ memset(&_audioPaketTemp, 0, sizeof(_audioPaketTemp));
+
+ switch (_audioPaket.flags) {
+ case PKT_STATUS_FLAG_FLUSH:
+ // just used if SetPositionIntern was called without the flush flag.
+ flushCodecBuffers();
+ break;
+ case PKT_STATUS_FLAG_EOF: // end-of-file
+ // ignore EOF while seeking
+ if (!isSeeking())
+ setEOF(true);
+ // buffer contains no data
+ return -1;
+ case PKT_STATUS_FLAG_ERROR:
+ setError(true);
+ logger.status("I/O Error", "TFFmpegDecodeStream.DecodeFrame");
+ return -1;
+ case PKT_STATUS_FLAG_EMPTY: {
+ double silenceDuration = *(double*) (_packetQueue.getStatusInfo(&_audioPaket));
+ _audioPaketSilence = lround(silenceDuration * _formatInfo.getSampleRate()) *
+ _formatInfo.getFrameSize();
+ _packetQueue.freeStatusInfo(&_audioPaket);
+ break;
+ }
+ default:
+ logger.status("Unknown status", "TFFmpegDecodeStream.DecodeFrame");
+ }
+
+ continue;
+ }
+
+ _audioPaketTemp.data = _audioPaket.data;
+ _audioPaketTemp.size = _audioPaket.size;
+
+ // if available, update the stream position to the presentation time of this package
+ if (_audioPaket.pts != (int64_t)AV_NOPTS_VALUE) {
+#ifdef DEBUG_FFMPEG_DECODE
+ double tmpPos = _audioStreamPos;
+#endif
+ _audioStreamPos = av_q2d(_audioStream->time_base) * _audioPaket.pts;
+#ifdef DEBUG_FFMPEG_DECODE
+ stringstream s;
+ s << "Timestamp: " << _audioStreamPos << " "
+ << "(Calc: " << tmpPos << "), "
+ << "Diff: " << (_audioStreamPos-TmpPos);
+ logger.status(s.str(), "");
+#endif
+ }
+ }
+}
+
+int FFmpegAudioDecodeStream::readData(uint8_t *buffer, int bufferSize) {
+ // set number of bytes to copy to the output buffer
+ int bufferPos = 0;
+
+ {
+ DecoderLock decoderLock(this);
+
+ // leave if end-of-file is reached
+ if (isEOF()) {
+ return -1;
+ }
+
+ // copy data to output buffer
+ while (bufferPos < bufferSize) {
+ // check if we need more data
+ if (_audioBufferPos >= _audioBufferSize) {
+ _audioBufferPos = 0;
+
+ // we have already sent all our data; get more
+ _audioBufferSize = decodeFrame(_audioBuffer, AUDIO_BUFFER_SIZE);
+ if (_audioBufferSize < 0) {
+ // error or EOF occurred
+ return bufferPos;
+ }
+ }
+
+ // calc number of new bytes in the decode-buffer (number of bytes to copy)
+ int copyByteCount = _audioBufferSize - _audioBufferPos;
+ // number of bytes left (remain) to read
+ int remainByteCount = bufferSize - bufferPos;
+ // resize copy-count if more bytes available than needed (remaining bytes are used the next time)
+ if (copyByteCount > remainByteCount)
+ copyByteCount = remainByteCount;
+
+ memcpy(&buffer[bufferPos], &_audioBuffer[_audioBufferPos], copyByteCount);
+ bufferPos += copyByteCount;
+ _audioBufferPos += copyByteCount;
+ }
+ }
+ return bufferSize;
+}
+
+/************************************
+ * C Interface
+ ************************************/
+
+#define DecodeStreamObj(ptr) reinterpret_cast<PluginDecodeStream*>(ptr)
+
+static BOOL PLUGIN_CALL ffmpegAudioDecoder_init() {
+ return TRUE;
+}
+
+static BOOL PLUGIN_CALL ffmpegAudioDecoder_finalize() {
+ return TRUE;
+}
+
+static audioDecodeStream_t* PLUGIN_CALL ffmpegAudioDecoder_open(const char *filename) {
+ return (audioDecodeStream_t*)FFmpegAudioDecodeStream::open(filename);
+}
+
+static void PLUGIN_CALL ffmpegAudioDecoder_close(audioDecodeStream_t *stream) {
+ delete DecodeStreamObj(stream);
+}
+
+static double PLUGIN_CALL ffmpegAudioDecoder_getLength(audioDecodeStream_t *stream) {
+ return DecodeStreamObj(stream)->getLength();
+}
+
+static void PLUGIN_CALL ffmpegAudioDecoder_getAudioFormatInfo(audioDecodeStream_t *stream, audioFormatInfo_t *info) {
+ DecodeStreamObj(stream)->getAudioFormatInfo().toCStruct(info);
+}
+
+static double PLUGIN_CALL ffmpegAudioDecoder_getPosition(audioDecodeStream_t *stream) {
+ return DecodeStreamObj(stream)->getPosition();
+}
+
+static void PLUGIN_CALL ffmpegAudioDecoder_setPosition(audioDecodeStream_t *stream, double time) {
+ DecodeStreamObj(stream)->setPosition(time);
+}
+
+static BOOL PLUGIN_CALL ffmpegAudioDecoder_getLoop(audioDecodeStream_t *stream) {
+ return (BOOL)DecodeStreamObj(stream)->getLoop();
+}
+
+static void PLUGIN_CALL ffmpegAudioDecoder_setLoop(audioDecodeStream_t *stream, BOOL enabled) {
+ DecodeStreamObj(stream)->setLoop(enabled);
+}
+
+static BOOL PLUGIN_CALL ffmpegAudioDecoder_isEOF(audioDecodeStream_t *stream) {
+ return (BOOL)DecodeStreamObj(stream)->isEOF();
+}
+
+static BOOL PLUGIN_CALL ffmpegAudioDecoder_isError(audioDecodeStream_t *stream) {
+ return (BOOL)DecodeStreamObj(stream)->isError();
+}
+
+static int PLUGIN_CALL ffmpegAudioDecoder_readData(audioDecodeStream_t *stream, uint8_t *buffer, int bufferSize) {
+ return DecodeStreamObj(stream)->readData(buffer, bufferSize);
+}
+
+/************************************
+ * Module information
+ ************************************/
+
+const audioDecoderInfo_t audioDecoderInfo = {
+ 50,
+ ffmpegAudioDecoder_init,
+ ffmpegAudioDecoder_finalize,
+ ffmpegAudioDecoder_open,
+ ffmpegAudioDecoder_close,
+ ffmpegAudioDecoder_getLength,
+ ffmpegAudioDecoder_getAudioFormatInfo,
+ ffmpegAudioDecoder_getPosition,
+ ffmpegAudioDecoder_setPosition,
+ ffmpegAudioDecoder_getLoop,
+ ffmpegAudioDecoder_setLoop,
+ ffmpegAudioDecoder_isEOF,
+ ffmpegAudioDecoder_isError,
+ ffmpegAudioDecoder_readData
+};
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.h b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.h
new file mode 100644
index 00000000..c0d1b288
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_audio_decode.h
@@ -0,0 +1,257 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _FFMPEG_AUDIO_DECODE_H_
+#define _FFMPEG_AUDIO_DECODE_H_
+
+#include "ffmpeg_core.h"
+#include "core/plugin_audio_decode.h"
+
+// TODO: The factor 3/2 might not be necessary as we do not need extra
+// space for synchronizing as in the tutorial.
+#define AUDIO_BUFFER_SIZE ((AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2)
+
+extern const audioDecoderInfo_t audioDecoderInfo;
+
+class FFmpegAudioDecodeStream : public PluginDecodeStream, private Thread {
+private:
+ Mutex _stateLock;
+
+ bool _eofState; // end-of-stream flag (locked by StateLock)
+ bool _errorState; // error flag (locked by StateLock)
+
+ bool _quitRequest; // (locked by StateLock)
+ Condition _parserIdleCond;
+
+ // parser pause/resume data
+ bool _parserLocked;
+ int _parserPauseRequestCount;
+ Condition _parserUnlockedCond;
+ Condition _parserResumeCond;
+
+ bool _seekRequest; // (locked by StateLock)
+ int _seekFlags; // (locked by StateLock)
+ double _seekPos; // stream position to seek for (in secs) (locked by StateLock)
+ bool _seekFlush; // true if the buffers should be flushed after seeking (locked by StateLock)
+ Condition _seekFinishedCond;
+
+ bool _loop; // (locked by StateLock)
+
+ PacketQueue _packetQueue;
+
+ AudioFormatInfo _formatInfo;
+
+ // FFmpeg specific data
+ AVFormatContext *_formatCtx;
+ AVCodecContext *_codecCtx;
+ AVCodec *_codec;
+
+ int _audioStreamIndex;
+ AVStream *_audioStream;
+ double _audioStreamPos; // stream position in seconds (locked by DecoderLock)
+
+ // decoder pause/resume data
+ bool _decoderLocked;
+ int _decoderPauseRequestCount;
+ Condition _decoderUnlockedCond;
+ Condition _decoderResumeCond;
+
+ // state-vars for DecodeFrame (locked by DecoderLock)
+ AVPacket _audioPaket;
+ AVPacket _audioPaketTemp;
+ int _audioPaketSilence; // number of bytes of silence to return
+
+ // state-vars for AudioCallback (locked by DecoderLock)
+ int _audioBufferPos;
+ int _audioBufferSize;
+ DECLARE_ALIGNED(16, uint8_t, _audioBuffer[AUDIO_BUFFER_SIZE]);
+
+ IPath _filename;
+
+private:
+ FFmpegAudioDecodeStream();
+
+ void setPositionIntern(double time, bool flush, bool blocking);
+
+ void setEOF(bool state) {
+ Mutex::RegionLock lock(_stateLock);
+ _eofState = state;
+ }
+
+ void setError(bool state) {
+ Mutex::RegionLock lock(_stateLock);
+ _errorState = state;
+ }
+
+ bool isSeeking() {
+ Mutex::RegionLock lock(_stateLock);
+ return _seekRequest;
+ }
+
+ bool isQuit() {
+ Mutex::RegionLock lock(_stateLock);
+ return _quitRequest;
+ }
+
+ bool parseLoop();
+
+ int decodeFrame(uint8_t *buffer, int bufferSize);
+ void flushCodecBuffers();
+
+ bool _open(const IPath &filename);
+ void close();
+
+public:
+ virtual ~FFmpegAudioDecodeStream() {
+ close();
+ }
+
+ static FFmpegAudioDecodeStream* open(const IPath &filename);
+
+ virtual double getLength();
+
+ virtual const AudioFormatInfo &getAudioFormatInfo() {
+ return _formatInfo;
+ }
+
+ virtual double getPosition();
+ virtual void setPosition(double time);
+
+ virtual bool getLoop() {
+ Mutex::RegionLock lock(_stateLock);
+ return _loop;
+ }
+
+ virtual void setLoop(bool Enabled) {
+ Mutex::RegionLock lock(_stateLock);
+ _loop = Enabled;
+ }
+
+ virtual bool isEOF() {
+ Mutex::RegionLock lock(_stateLock);
+ return _eofState;
+ }
+
+ virtual bool isError() {
+ Mutex::RegionLock lock(_stateLock);
+ return _errorState;
+ }
+
+ virtual int readData(uint8_t *buffer, int bufferSize);
+
+public:
+ int run();
+
+public:
+ class ParserLock {
+ private:
+ FFmpegAudioDecodeStream *_stream;
+ public:
+ // Note: pthreads wakes threads waiting on a mutex in the order of their
+ // priority and not in FIFO order. SDL does not provide any option to
+ // control priorities. This might (and already did) starve threads waiting
+ // on the mutex (e.g. SetPosition) making usdx look like it was froozen.
+ // Instead of simply locking the critical section we set a ParserLocked flag
+ // instead and give priority to the threads requesting the parser to pause.
+ ParserLock(FFmpegAudioDecodeStream *stream) : _stream(stream) {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ while (_stream->_parserPauseRequestCount > 0)
+ _stream->_parserResumeCond.wait(_stream->_stateLock);
+ _stream->_parserLocked = true;
+ }
+
+ ~ParserLock() {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ _stream->_parserLocked = false;
+ _stream->_parserUnlockedCond.broadcast();
+ }
+ };
+
+ class ParserPauser {
+ private:
+ FFmpegAudioDecodeStream *_stream;
+ public:
+ ParserPauser(FFmpegAudioDecodeStream *stream) : _stream(stream) {
+ if (Thread::getCurrentThreadID() == _stream->getThreadID())
+ return;
+
+ {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ ++_stream->_parserPauseRequestCount;
+ while (_stream->_parserLocked)
+ _stream->_parserUnlockedCond.wait(_stream->_stateLock);
+ }
+ }
+
+ ~ParserPauser() {
+ if (Thread::getCurrentThreadID() == _stream->getThreadID())
+ return;
+
+ {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ --_stream->_parserPauseRequestCount;
+ _stream->_parserResumeCond.signal();
+ }
+ }
+ };
+
+ class DecoderLock {
+ private:
+ FFmpegAudioDecodeStream *_stream;
+ public:
+ // prioritize pause requests
+ DecoderLock(FFmpegAudioDecodeStream *stream) : _stream(stream) {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ while (_stream->_decoderPauseRequestCount > 0)
+ _stream->_decoderResumeCond.wait(_stream->_stateLock);
+ _stream->_decoderLocked = true;
+ }
+
+ ~DecoderLock() {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ _stream->_decoderLocked = false;
+ _stream->_decoderUnlockedCond.broadcast();
+ }
+ };
+
+ class DecoderPauser {
+ private:
+ FFmpegAudioDecodeStream *_stream;
+ public:
+ DecoderPauser(FFmpegAudioDecodeStream *stream) : _stream(stream) {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ ++_stream->_decoderPauseRequestCount;
+ while (_stream->_decoderLocked)
+ _stream->_decoderUnlockedCond.wait(_stream->_stateLock);
+ }
+
+ ~DecoderPauser() {
+ Mutex::RegionLock lock(_stream->_stateLock);
+ --_stream->_decoderPauseRequestCount;
+ _stream->_decoderResumeCond.signal();
+ }
+ };
+};
+
+#endif /* _FFMPEG_AUDIO_DECODE_H_ */
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.cpp b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.cpp
new file mode 100644
index 00000000..6f48c199
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.cpp
@@ -0,0 +1,412 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "ffmpeg_core.h"
+#include "core/logger.h"
+#include <sstream>
+
+const uint8_t* STATUS_PACKET = (uint8_t*)"STATUS_PACKET";
+
+static int CDECL ffmpegStreamOpen(URLContext *h, const char *filename, int flags);
+static int CDECL ffmpegStreamRead(URLContext *h, uint8_t *buf, int size);
+static int CDECL ffmpegStreamWrite(URLContext *h, const unsigned char *buf, int size);
+static int64_t CDECL ffmpegStreamSeek(URLContext *h, int64_t pos, int whence);
+static int CDECL ffmpegStreamClose(URLContext *h);
+
+#define UNICODE_PROTOCOL_NAME "ufile"
+#define UNICODE_PROTOCOL_PREFIX UNICODE_PROTOCOL_NAME ":"
+
+std::string hexVerToStr(unsigned version) {
+ unsigned major = (version >> 16) & 0xFF;
+ unsigned minor = (version >> 8) & 0xFF;
+ unsigned release = version & 0xFF;
+ std::stringstream s;
+ s << major << "." << minor << "." << release;
+ return s.str();
+}
+
+void checkVersions() {
+ unsigned libVersion;
+ unsigned headerVersion;
+
+ #ifdef LIBAVCODEC_VERSION_INT
+ libVersion = avcodec_version();
+ headerVersion = LIBAVCODEC_VERSION_INT;
+ if (libVersion != headerVersion) {
+ logger.error("libavcodec header (" + hexVerToStr(headerVersion)
+ + ") and " + "DLL (" + hexVerToStr(libVersion)
+ + ") versions do not match.", "");
+ }
+ #endif
+
+ #if defined(LIBAVFORMAT_VERSION_INT) && \
+ (LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,20,0))
+ libVersion = avformat_version();
+ headerVersion = LIBAVFORMAT_VERSION_INT;
+ if (libVersion != headerVersion) {
+ logger.error("libavformat header (" + hexVerToStr(headerVersion)
+ + ") and " + "DLL (" + hexVerToStr(libVersion)
+ + ") versions do not match.", "");
+ }
+ #endif
+
+ #if defined(LIBAVUTIL_VERSION_INT) && \
+ (LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(49,8,0))
+ libVersion = avutil_version();
+ headerVersion = LIBAVUTIL_VERSION_INT;
+ if (libVersion != headerVersion) {
+ logger.error("libavutil header (" + hexVerToStr(headerVersion) + ") and "
+ + "DLL (" + hexVerToStr(libVersion)
+ + ") versions do not match.", "");
+ }
+ #endif
+
+ #if defined(LIBSWSCALE_VERSION_INT) && \
+ (LIBSWSCALE_VERSION_INT >= AV_VERSION_INT(0,6,1))
+ libVersion = swscale_version();
+ headerVersion = LIBSWSCALE_VERSION_INT;
+ if (libVersion != headerVersion) {
+ logger.error("libswscale header (" + hexVerToStr(headerVersion)
+ + ") and " + "DLL (" + hexVerToStr(libVersion)
+ + ") versions do not match.", "");
+ }
+ #endif
+}
+
+MediaCore_FFmpeg::MediaCore_FFmpeg() {
+ checkVersions();
+
+ memset(&utf8FileProtocol, 0, sizeof(URLProtocol));
+ utf8FileProtocol.name = UNICODE_PROTOCOL_NAME;
+ utf8FileProtocol.url_open = ffmpegStreamOpen;
+ utf8FileProtocol.url_read = ffmpegStreamRead;
+ utf8FileProtocol.url_write = ffmpegStreamWrite;
+ utf8FileProtocol.url_seek = ffmpegStreamSeek;
+ utf8FileProtocol.url_close = ffmpegStreamClose;
+
+ av_register_protocol2(&utf8FileProtocol, sizeof(URLProtocol));
+}
+
+MediaCore_FFmpeg::~MediaCore_FFmpeg() {
+}
+
+std::string MediaCore_FFmpeg::getErrorString(int errorNum) const {
+ switch (errorNum) {
+ case AVERROR_IO:
+ return "AVERROR_IO";
+ case AVERROR_NUMEXPECTED:
+ return "AVERROR_NUMEXPECTED";
+ case AVERROR_INVALIDDATA:
+ return "AVERROR_INVALIDDATA";
+ case AVERROR_NOMEM:
+ return "AVERROR_NOMEM";
+ case AVERROR_NOFMT:
+ return "AVERROR_NOFMT";
+ case AVERROR_NOTSUPP:
+ return "AVERROR_NOTSUPP";
+ case AVERROR_NOENT:
+ return "AVERROR_NOENT";
+ case AVERROR_PATCHWELCOME:
+ return "AVERROR_PATCHWELCOME";
+ default:
+ return "AVERROR_#" + errorNum;
+ }
+}
+
+/*
+ @param(formatCtx is a PAVFormatContext returned from av_open_input_file )
+ @param(firstVideoStream is an OUT value of type integer, this is the index of the video stream)
+ @param(firstAudioStream is an OUT value of type integer, this is the index of the audio stream)
+ @returns(@true on success, @false otherwise)
+ */
+bool MediaCore_FFmpeg::findStreamIDs(AVFormatContext *formatCtx,
+ int *firstVideoStream, int *firstAudioStream) const
+{
+ // find the first video stream
+ *firstAudioStream = -1;
+ *firstVideoStream = -1;
+
+ for (unsigned i = 0; i < formatCtx->nb_streams; ++i) {
+ AVStream *stream = formatCtx->streams[i];
+
+ if ((stream->codec->codec_type == AVMEDIA_TYPE_VIDEO) && (*firstVideoStream < 0)) {
+ *firstVideoStream = i;
+ }
+
+ if ((stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) && (*firstAudioStream < 0)) {
+ *firstAudioStream = i;
+ }
+ }
+
+ // return true if either an audio- or video-stream was found
+ return (*firstAudioStream > -1) || (*firstVideoStream > -1);
+}
+
+int MediaCore_FFmpeg::findAudioStreamIndex(AVFormatContext *formatCtx) const {
+ // find the first audio stream
+ int streamIndex = -1;
+ for (unsigned i = 0; i < formatCtx->nb_streams; ++i) {
+ AVStream *stream = formatCtx->streams[i];
+ if (stream->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
+ streamIndex = i;
+ break;
+ }
+ }
+ return streamIndex;
+}
+
+bool MediaCore_FFmpeg::convertFFmpegToAudioFormat(SampleFormat ffmpegFormat, audioSampleFormat_t *format) const {
+ switch (ffmpegFormat) {
+ case SAMPLE_FMT_U8:
+ *format = asfU8;
+ break;
+ case SAMPLE_FMT_S16:
+ *format = asfS16;
+ break;
+ case SAMPLE_FMT_S32:
+ *format = asfS32;
+ break;
+ case SAMPLE_FMT_FLT:
+ *format = asfFloat;
+ break;
+ case SAMPLE_FMT_DBL:
+ *format = asfDouble;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+/**
+ * UTF-8 Filename wrapper based on:
+ * http://www.mail-archive.com/libav-user@mplayerhq.hu/msg02460.html
+ */
+
+int CDECL ffmpegStreamOpen(URLContext *h, const char *filename, int flags) {
+ // check for protocol prefix ("ufile:") and strip it
+ const std::string protPrefix(UNICODE_PROTOCOL_PREFIX);
+ std::string utf8Filename(filename);
+ if (utf8Filename.compare(0, protPrefix.size(), protPrefix) == 0)
+ utf8Filename.erase(0, protPrefix.size());
+
+ int mode;
+ switch (flags) {
+ case URL_RDWR:
+ mode = FILE_OPEN_MODE_READ_WRITE;
+ break;
+ case URL_WRONLY:
+ mode = FILE_OPEN_MODE_WRITE;
+ break;
+ case URL_RDONLY:
+ mode = FILE_OPEN_MODE_READ;
+ }
+
+ fileStream_t *stream = pluginCore->fileOpen(utf8Filename.c_str(), mode);
+ if (!stream)
+ return AVERROR_NOENT;
+ h->priv_data = stream;
+ return 0;
+}
+
+int CDECL ffmpegStreamRead(URLContext *h, uint8_t *buf, int size) {
+ fileStream_t *stream = (fileStream_t*)h->priv_data;
+ return pluginCore->fileRead(stream, buf, size);
+}
+
+int CDECL ffmpegStreamWrite(URLContext *h, const uint8_t *buf, int size) {
+ fileStream_t *stream = (fileStream_t*)h->priv_data;
+ return pluginCore->fileWrite(stream, buf, size);
+}
+
+int64_t CDECL ffmpegStreamSeek(URLContext *h, int64_t pos, int whence) {
+ fileStream_t *stream = (fileStream_t*)h->priv_data;
+ switch (whence) {
+ case AVSEEK_SIZE:
+ return pluginCore->fileSize(stream);
+ default:
+ return pluginCore->fileSeek(stream, pos, whence);
+ }
+}
+
+int CDECL ffmpegStreamClose(URLContext *h) {
+ fileStream_t *stream = (fileStream_t*)h->priv_data;
+ pluginCore->fileClose(stream);
+ return 0;
+}
+
+/* PacketQueue */
+
+PacketQueue::PacketQueue() :
+ _firstListEntry(NULL),
+ _lastListEntry(NULL),
+ _packetCount(0),
+ _size(0),
+ _abortRequest(false)
+{}
+
+PacketQueue::~PacketQueue() {
+ flush();
+}
+
+void PacketQueue::abort() {
+ Mutex::RegionLock lock(_mutex);
+ _abortRequest = true;
+ _condition.broadcast();
+}
+
+bool PacketQueue::isAborted() {
+ Mutex::RegionLock lock(_mutex);
+ return _abortRequest;
+}
+
+int PacketQueue::put(AVPacket *packet) {
+ if (!packet)
+ return -1;
+
+ if (packet->data != STATUS_PACKET) {
+ if (av_dup_packet(packet) < 0)
+ return -1;
+ }
+
+ AVPacketList *currentListEntry = (AVPacketList*) av_malloc(sizeof(AVPacketList));
+ if (!currentListEntry)
+ return -1;
+
+ currentListEntry->pkt = *packet;
+ currentListEntry->next = NULL;
+
+ {
+ Mutex::RegionLock lock(_mutex);
+
+ if (!_lastListEntry)
+ _firstListEntry = currentListEntry;
+ else
+ _lastListEntry->next = currentListEntry;
+
+ _lastListEntry = currentListEntry;
+ ++_packetCount;
+
+ _size += currentListEntry->pkt.size;
+ _condition.signal();
+ }
+
+ return 0;
+}
+
+/**
+ * Adds a status packet (EOF, Flush, etc.) to the end of the queue.
+ * StatusInfo can be used to pass additional information to the decoder.
+ * Only assign nil or a valid pointer to data allocated with Getmem() to
+ * StatusInfo because the pointer will be disposed with Freemem() on a call
+ * to Flush(). If the packet is removed from the queue it is the decoder's
+ * responsibility to free the StatusInfo data with FreeStatusInfo().
+ */
+int PacketQueue::putStatus(int statusFlag, void *statusInfo) {
+ // create temp. package
+ AVPacket *tempPacket = (AVPacket*) av_malloc(sizeof(AVPacket));
+ if (!tempPacket)
+ return -1;
+
+ // init package
+ av_init_packet(tempPacket);
+ tempPacket->data = const_cast<uint8_t*> (STATUS_PACKET);
+ tempPacket->flags = statusFlag;
+ tempPacket->priv = statusInfo;
+ // put a copy of the package into the queue
+ const int result = put(tempPacket);
+ // data has been copied -> delete temp. package
+ av_free(tempPacket);
+ return result;
+}
+
+void PacketQueue::freeStatusInfo(AVPacket *packet) {
+ if (packet->priv)
+ free(packet->priv);
+}
+
+void* PacketQueue::getStatusInfo(AVPacket *packet) {
+ return packet->priv;
+}
+
+int PacketQueue::get(AVPacket *packet, bool blocking) {
+ const int WAIT_TIMEOUT = 10; // timeout in ms
+
+ {
+ Mutex::RegionLock lock(_mutex);
+ while (true) {
+ if (_abortRequest)
+ return -1;
+
+ AVPacketList *currentListEntry = _firstListEntry;
+ if (currentListEntry) {
+ _firstListEntry = currentListEntry->next;
+ if (!_firstListEntry)
+ _lastListEntry = NULL;
+ --_packetCount;
+
+ _size -= currentListEntry->pkt.size;
+ *packet = currentListEntry->pkt;
+ av_free(currentListEntry);
+
+ return 1;
+ } else if (!blocking) {
+ return 0;
+ } else {
+ // block until a new package arrives,
+ // but do not wait till infinity to avoid deadlocks
+ if (_condition.waitTimeout(_mutex, WAIT_TIMEOUT) == MUTEX_TIMEDOUT) {
+ return 0;
+ }
+ }
+ }
+ }
+}
+
+int PacketQueue::getSize() {
+ Mutex::RegionLock lock(_mutex);
+ return _size;
+}
+
+void PacketQueue::flush() {
+ Mutex::RegionLock lock(_mutex);
+
+ AVPacketList *tempListEntry;
+ AVPacketList *currentListEntry = _firstListEntry;
+ while (currentListEntry) {
+ tempListEntry = currentListEntry->next;
+ // free status data
+ if (currentListEntry->pkt.data == STATUS_PACKET)
+ freeStatusInfo(&currentListEntry->pkt);
+ // free packet data
+ av_free_packet(&currentListEntry->pkt);
+ // Note: param must be a pointer to a pointer!
+ av_freep(&currentListEntry);
+ currentListEntry = tempListEntry;
+ }
+ _lastListEntry = NULL;
+ _firstListEntry = NULL;
+ _packetCount = 0;
+ _size = 0;
+}
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.h b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.h
new file mode 100644
index 00000000..9b55d5e4
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_core.h
@@ -0,0 +1,136 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _FFMPEG_CORE_H_
+#define _FFMPEG_CORE_H_
+
+#ifndef __STDC_CONSTANT_MACROS
+#define __STDC_CONSTANT_MACROS
+#endif
+#include <inttypes.h>
+#include <string>
+extern "C" {
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+#include <libavformat/avio.h>
+#include <libavutil/avutil.h>
+#include <libavutil/mathematics.h>
+#include <libavutil/rational.h> // used for av_rescale_q
+#include <libswscale/swscale.h>
+}
+#include "core/util.h"
+
+class PacketQueue {
+private:
+ AVPacketList *_firstListEntry;
+ AVPacketList *_lastListEntry;
+ int _packetCount;
+ Mutex _mutex;
+ Condition _condition;
+ int _size;
+ bool _abortRequest;
+public:
+ PacketQueue();
+ ~PacketQueue();
+
+ int put(AVPacket *packet);
+ int putStatus(int statusFlag, void *statusInfo);
+ void freeStatusInfo(AVPacket *packet);
+ void* getStatusInfo(AVPacket *packet);
+ int get(AVPacket *packet, bool blocking);
+ int getSize();
+ void flush();
+ void abort();
+ bool isAborted();
+};
+
+extern const uint8_t* STATUS_PACKET;
+enum {
+ PKT_STATUS_FLAG_EOF = 1, // signal end-of-file
+ PKT_STATUS_FLAG_FLUSH, // request the decoder to flush its avcodec decode buffers
+ PKT_STATUS_FLAG_ERROR, // signal an error state
+ PKT_STATUS_FLAG_EMPTY // request the decoder to output empty data (silence or black frames)
+};
+
+class MediaCore_FFmpeg;
+
+// Note: singleton pattern does not work here as static vars
+// are initialized before the plugin itself is initialized.
+extern MediaCore_FFmpeg *ffmpegCore;
+
+class MediaCore_FFmpeg {
+private:
+ URLProtocol utf8FileProtocol;
+ Mutex _codecLock;
+public:
+ MediaCore_FFmpeg();
+ ~MediaCore_FFmpeg();
+
+ std::string getErrorString(int errorNum) const;
+ bool findStreamIDs(AVFormatContext *formatCtx, int *firstVideoStream, int *firstAudioStream) const;
+ int findAudioStreamIndex(AVFormatContext *formatCtx) const;
+ bool convertFFmpegToAudioFormat(SampleFormat ffmpegFormat, audioSampleFormat_t *format) const;
+
+public:
+ class AVCodecLock {
+ public:
+ AVCodecLock() {
+ ffmpegCore->_codecLock.lock();
+ }
+
+ ~AVCodecLock() {
+ ffmpegCore->_codecLock.unlock();
+ }
+ };
+};
+
+// FFmpeg compatibility with older versions
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52,64,0)
+#define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO
+#define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52,26,0)
+#define avcodec_decode_video2(avctx, picture, got_picture_ptr, avpkt) \
+ avcodec_decode_video((avctx), (picture), (got_picture_ptr), \
+ (avpkt)->data, (avpkt)->size)
+#endif
+
+#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(51,30,0)
+#define avcodec_decode_audio3(avctx, samples, frame_size_ptr, avpkt) \
+ avcodec_decode_audio((avctx), (samples), (frame_size_ptr), \
+ (avpkt)->data, (avpkt)->size)
+#elif LIBAVCODEC_VERSION_INT < AV_VERSION_INT(52,26,0)
+#define avcodec_decode_audio3(avctx, samples, frame_size_ptr, avpkt) \
+ avcodec_decode_audio2((avctx), (samples), (frame_size_ptr), \
+ (avpkt)->data, (avpkt)->size)
+#endif
+
+#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(52,69,0)
+#define av_register_protocol2(prot, size) \
+ av_register_protocol(prot)
+#endif
+
+#endif /* _FFMPEG_CORE_H_ */
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.cpp b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.cpp
new file mode 100644
index 00000000..35451388
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.cpp
@@ -0,0 +1,82 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#include "ffmpeg_core.h"
+#include "core/plugin_core.h"
+#include "ffmpeg_plugin.h"
+#include "ffmpeg_audio_decode.h"
+#include "ffmpeg_audio_convert.h"
+#include "ffmpeg_video_decode.h"
+
+MediaCore_FFmpeg *ffmpegCore;
+
+DLL_EXPORT const pluginInfo_t* PLUGIN_CALL Plugin_register(const pluginCore_t *core) {
+ pluginInitCore(core);
+ return &pluginInfo;
+}
+
+static BOOL PLUGIN_CALL Plugin_initialize() {
+ static bool initialized = false;
+ if (initialized)
+ return TRUE;
+ initialized = TRUE;
+
+ //logger.status("InitializeDecoder", "Plugin_initialize");
+
+ av_register_all();
+
+ ffmpegCore = new MediaCore_FFmpeg();
+
+ // Do not show uninformative error messages by default.
+ // FFmpeg prints all error-infos on the console by default what
+ // is very confusing as the playback of the files is correct.
+ // We consider these errors to be internal to FFMpeg. They can be fixed
+ // by the FFmpeg guys only and do not provide any useful information in
+ // respect to USDX.
+#ifndef ENABLE_FFMPEG_ERROR_OUTPUT
+#if LIBAVUTIL_VERSION_MAJOR >= 50
+ av_log_set_level(AV_LOG_FATAL);
+#else
+ // FATAL and ERROR share one log-level, so we have to use QUIET
+ av_log_set_level(AV_LOG_QUIET);
+#endif //LIBAVUTIL_VERSION_MAJOR
+#endif //ENABLE_FFMPEG_ERROR_OUTPUT
+
+ return TRUE;
+}
+
+static BOOL PLUGIN_CALL Plugin_finalize() {
+ delete ffmpegCore;
+ return TRUE;
+}
+
+const pluginInfo_t pluginInfo = {
+ MAKE_VERSION(0, 0, 0),
+ "FFmpeg",
+ Plugin_initialize,
+ Plugin_finalize,
+ &audioDecoderInfo,
+ &audioConverterInfo,
+ &videoDecoderInfo
+};
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.h b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.h
new file mode 100644
index 00000000..7263bada
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_plugin.h
@@ -0,0 +1,30 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _FFMPEG_PLUGIN_H_
+#define _FFMPEG_PLUGIN_H_
+
+extern const pluginInfo_t pluginInfo;
+
+#endif /* _FFMPEG_PLUGIN_H_ */
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.cpp b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.cpp
new file mode 100644
index 00000000..c582c771
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.cpp
@@ -0,0 +1,661 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+
+/*
+ * based on 'An ffmpeg and SDL Tutorial' (http://www.dranger.com/ffmpeg/)
+ */
+
+#include "ffmpeg_video_decode.h"
+#include <sstream>
+
+// These are called whenever we allocate a frame buffer.
+// We use this to store the global_pts in a frame at the time it is allocated.
+int CDECL ptsGetBuffer(AVCodecContext *codecCtx, AVFrame *frame) {
+ int result = avcodec_default_get_buffer(codecCtx, frame);
+ int64_t *videoPktPts = (int64_t*)codecCtx->opaque;
+ if (videoPktPts) {
+ // Note: we must copy the pts instead of passing a pointer, because the packet
+ // (and with it the pts) might change before a frame is returned by av_decode_video.
+ int64_t *pts = (int64_t*)av_malloc(sizeof(int64_t));
+ *pts = *videoPktPts;
+ frame->opaque = pts;
+ }
+ return result;
+}
+
+void CDECL ptsReleaseBuffer(AVCodecContext *codecCtx, AVFrame *frame) {
+ if (frame)
+ av_freep(&frame->opaque);
+ avcodec_default_release_buffer(codecCtx, frame);
+}
+
+/*
+ * TVideoDecoder_FFmpeg
+ */
+
+FFmpegVideoDecodeStream::FFmpegVideoDecodeStream() :
+ _opened(false),
+ _eof(false),
+ _loop(false),
+ _stream(NULL),
+ _streamIndex(-1),
+ _formatContext(NULL),
+ _codecContext(NULL),
+ _codec(NULL),
+ _avFrame(NULL),
+ _avFrameRGB(NULL),
+ _frameBuffer(NULL),
+ _frameTexValid(false),
+#ifdef USE_SWSCALE
+ _swScaleContext(NULL),
+#endif
+ _aspect(0),
+ _frameDuration(0),
+ _frameTime(0),
+ _loopTime(0) {}
+
+FFmpegVideoDecodeStream* FFmpegVideoDecodeStream::open(const IPath &filename) {
+ FFmpegVideoDecodeStream *stream = new FFmpegVideoDecodeStream();
+ if (!stream->_open(filename)) {
+ delete stream;
+ return 0;
+ }
+ return stream;
+}
+
+bool FFmpegVideoDecodeStream::_open(const IPath &filename) {
+ std::stringstream ss;
+
+ // use custom 'ufile' protocol for UTF-8 support
+ int errnum = av_open_input_file(&_formatContext,
+ ("ufile:" + filename.toUTF8()).c_str(), NULL, 0, NULL);
+ if (errnum != 0) {
+ logger.error("Failed to open file '" + filename.toNative() + "' ("
+ + ffmpegCore->getErrorString(errnum) + ")",
+ "VideoDecodeStream_FFmpeg::Open");
+ return false;
+ }
+
+ // update video info
+ if (av_find_stream_info(_formatContext) < 0) {
+ logger.error("No stream info found", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ ss.str("");
+ ss << "VideoStreamIndex: " << _streamIndex;
+ logger.info(ss.str(), "VideoPlayback_ffmpeg.Open");
+
+ // find video stream
+ int audioStreamIndex;
+ ffmpegCore->findStreamIDs(_formatContext, &_streamIndex, &audioStreamIndex);
+ if (_streamIndex < 0) {
+ logger.error("No video stream found", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ _stream = _formatContext->streams[_streamIndex];
+ _codecContext = _stream->codec;
+
+ _codec = avcodec_find_decoder(_codecContext->codec_id);
+ if (!_codec) {
+ logger.error("No matching codec found", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ // set debug options
+ _codecContext->debug_mv = 0;
+ _codecContext->debug = 0;
+
+ // detect bug-workarounds automatically
+ _codecContext->workaround_bugs = FF_BUG_AUTODETECT;
+ // error resilience strategy (careful/compliant/agressive/very_aggressive)
+ //fCodecContext->error_resilience = FF_ER_CAREFUL; //FF_ER_COMPLIANT;
+ // allow non spec compliant speedup tricks.
+ //fCodecContext->flags2 = fCodecContext->flags2 | CODEC_FLAG2_FAST;
+
+ // Note: avcodec_open() and avcodec_close() are not thread-safe and will
+ // fail if called concurrently by different threads.
+ {
+ MediaCore_FFmpeg::AVCodecLock codecLock;
+ errnum = avcodec_open(_codecContext, _codec);
+ }
+ if (errnum < 0) {
+ logger.error("No matching codec found", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ // register custom callbacks for pts-determination
+ _codecContext->get_buffer = ptsGetBuffer;
+ _codecContext->release_buffer = ptsReleaseBuffer;
+
+#ifdef DEBUG_DISPLAY
+ ss.str("");
+ ss << "Found a matching Codec: " << _codecContext->codec->name << std::endl
+ << std::endl
+ << " Width = " << _codecContext->width
+ << ", Height=" << _codecContext->height << std::endl
+ << " Aspect : " << _codecContext->sample_aspect_ratio.num << "/"
+ << _codecContext->sample_aspect_ratio.den << std::endl
+ << " Framerate : " << _codecContext->time_base.num << "/"
+ << _codecContext->time_base.den;
+ logger.status(ss.str(), "");
+#endif
+
+ // allocate space for decoded frame and rgb frame
+ _avFrame = avcodec_alloc_frame();
+ _avFrameRGB = avcodec_alloc_frame();
+ _frameBuffer = (uint8_t*) av_malloc(avpicture_get_size(PIXEL_FMT_FFMPEG,
+ _codecContext->width, _codecContext->height));
+
+ if (!_avFrame || !_avFrameRGB || !_frameBuffer) {
+ logger.error("Failed to allocate buffers", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ // TODO: pad data for OpenGL to GL_UNPACK_ALIGNMENT
+ // (otherwise video will be distorted if width/height is not a multiple of the alignment)
+ errnum = avpicture_fill((AVPicture*)_avFrameRGB, _frameBuffer,
+ PIXEL_FMT_FFMPEG, _codecContext->width, _codecContext->height);
+ if (errnum < 0) {
+ logger.error("avpicture_fill failed: " + ffmpegCore->getErrorString(errnum),
+ "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+
+ // calculate some information for video display
+ _aspect = av_q2d(_codecContext->sample_aspect_ratio);
+ if (_aspect == 0) {
+ _aspect = (double)_codecContext->width / _codecContext->height;
+ } else {
+ _aspect *= (double)_codecContext->width / _codecContext->height;
+ }
+
+ _frameDuration = 1.0 / av_q2d(_stream->r_frame_rate);
+
+ // hack to get reasonable framerate (for divx and others)
+ if (_frameDuration < 0.02) { // 0.02 <-> 50 fps
+ _frameDuration = av_q2d(_stream->r_frame_rate);
+ while (_frameDuration > 50.0)
+ _frameDuration /= 10.0;
+ _frameDuration = 1.0 / _frameDuration;
+ }
+
+ ss.str("");
+ ss << "Framerate: " << (int)(1.0 / _frameDuration) << "fps";
+ logger.info(ss.str(), "VideoPlayback_ffmpeg.Open");
+
+#ifdef USE_SWSCALE
+ // if available get a SWScale-context -> faster than the deprecated img_convert().
+ // SWScale has accelerated support for PIX_FMT_RGB32/PIX_FMT_BGR24/PIX_FMT_BGR565/PIX_FMT_BGR555.
+ // Note: PIX_FMT_RGB32 is a BGR- and not an RGB-format (maybe a bug)!!!
+ // The BGR565-formats (GL_UNSIGNED_SHORT_5_6_5) is way too slow because of its
+ // bad OpenGL support. The BGR formats have MMX(2) implementations but no speed-up
+ // could be observed in comparison to the RGB versions.
+ _swScaleContext = sws_getCachedContext(NULL,
+ _codecContext->width, _codecContext->height, _codecContext->pix_fmt,
+ _codecContext->width, _codecContext->height, PIXEL_FMT_FFMPEG,
+ SWS_FAST_BILINEAR,
+ NULL, NULL, NULL);
+ if (!_swScaleContext) {
+ logger.error("Failed to get swscale context", "VideoPlayback_ffmpeg.Open");
+ close();
+ return false;
+ }
+#endif
+
+ _opened = true;
+ return true;
+}
+
+void FFmpegVideoDecodeStream::close() {
+ if (_frameBuffer)
+ av_free(_frameBuffer);
+ if (_avFrameRGB)
+ av_free(_avFrameRGB);
+ if (_avFrame)
+ av_free(_avFrame);
+
+ _avFrame = NULL;
+ _avFrameRGB = NULL;
+ _frameBuffer = NULL;
+
+ if (_codecContext) {
+ // avcodec_close() is not thread-safe
+ MediaCore_FFmpeg::AVCodecLock codecLock;
+ avcodec_close(_codecContext);
+ }
+
+ if (_formatContext)
+ av_close_input_file(_formatContext);
+
+ _codecContext = NULL;
+ _formatContext = NULL;
+
+ _opened = false;
+}
+
+void FFmpegVideoDecodeStream::synchronizeTime(AVFrame *frame, double &pts) {
+ if (pts != 0) {
+ // if we have pts, set video clock to it
+ _frameTime = pts;
+ } else {
+ // if we aren't given a pts, set it to the clock
+ pts = _frameTime;
+ }
+ // update the video clock
+ double frameDelay = av_q2d(_codecContext->time_base);
+ // if we are repeating a frame, adjust clock accordingly
+ frameDelay = frameDelay + frame->repeat_pict * (frameDelay * 0.5);
+ _frameTime = _frameTime + frameDelay;
+}
+
+/**
+ * Decode a new frame from the video stream.
+ * The decoded frame is stored in fAVFrame. fFrameTime is updated to the new frame's
+ * time.
+ * @param pts will be updated to the presentation time of the decoded frame.
+ * returns true if a frame could be decoded. False if an error or EOF occured.
+ */
+bool FFmpegVideoDecodeStream::decodeFrame() {
+ int64_t videoPktPts;
+ ByteIOContext *pbIOCtx;
+ int errnum;
+ AVPacket packet;
+ double pts;
+
+ if (_eof)
+ return false;
+
+ // read packets until we have a finished frame (or there are no more packets)
+ int frameFinished = 0;
+ while (frameFinished == 0) {
+ errnum = av_read_frame(_formatContext, &packet);
+ if (errnum < 0) {
+ // failed to read a frame, check reason
+
+#if LIBAVFORMAT_VERSION_MAJOR >= 52
+ pbIOCtx = _formatContext->pb;
+#else
+ pbIOCtx = &_formatContext->pb;
+#endif
+
+ // check for end-of-file (EOF is not an error)
+ if (url_feof(pbIOCtx) != 0) {
+ _eof = true;
+ return false;
+ }
+
+ // check for errors
+ if (url_ferror(pbIOCtx) != 0) {
+ logger.error("Video decoding file error", "TVideoPlayback_FFmpeg.DecodeFrame");
+ return false;
+ }
+
+ // url_feof() does not detect an EOF for some mov-files (e.g. deluxe.mov)
+ // so we have to do it this way.
+ if ((_formatContext->file_size != 0) &&
+ (pbIOCtx->pos >= _formatContext->file_size))
+ {
+ _eof = true;
+ return false;
+ }
+
+ // error occured, log and exit
+ logger.error("Video decoding error", "TVideoPlayback_FFmpeg.DecodeFrame");
+ return false;
+ }
+
+ // if we got a packet from the video stream, then decode it
+ if (packet.stream_index == _streamIndex) {
+ // save pts to be stored in pFrame in first call of PtsGetBuffer()
+ videoPktPts = packet.pts;
+ // FIXME: is the pointer valid when it is used?
+ _codecContext->opaque = &videoPktPts;
+
+ // decode packet
+ avcodec_decode_video2(_codecContext, _avFrame, &frameFinished, &packet);
+
+ // reset opaque data
+ _codecContext->opaque = NULL;
+
+ // update pts
+ if (packet.dts != (int64_t)AV_NOPTS_VALUE) {
+ pts = packet.dts;
+ } else if (_avFrame->opaque &&
+ (*((int64_t*)_avFrame->opaque) != (int64_t)AV_NOPTS_VALUE))
+ {
+ pts = *((int64_t*)_avFrame->opaque);
+ } else {
+ pts = 0;
+ }
+
+ if (_stream->start_time != (int64_t)AV_NOPTS_VALUE)
+ pts -= _stream->start_time;
+
+ pts *= av_q2d(_stream->time_base);
+
+ // synchronize time on each complete frame
+ if (frameFinished != 0)
+ synchronizeTime(_avFrame, pts);
+ }
+
+ // free the packet from av_read_frame
+ av_free_packet(&packet);
+ }
+
+ return true;
+}
+
+#ifdef DEBUG_FRAMES
+void spawnGoldenRec(double x, double y, int screen, uint8_t live, int startFrame,
+ int recArrayIndex, unsigned player)
+{
+ //GoldenRec.Spawn(x, y, screen, live, startFrame, recArrayIndex, ColoredStar, player)
+}
+#endif
+
+uint8_t* FFmpegVideoDecodeStream::getFrame(long double time) {
+ const long double SKIP_FRAME_DIFF = 0.010; // start skipping if we are >= 10ms too late
+ std::stringstream ss;
+
+ if (!_opened)
+ return NULL;
+
+ /*
+ * Synchronization - begin
+ */
+
+ // requested stream position (relative to the last loop's start)
+ long double currentTime;
+ if (_loop)
+ currentTime = time - _loopTime;
+ else
+ currentTime = time;
+
+ // check if current texture still contains the active frame
+ if (_frameTexValid) {
+ // time since the last frame was returned
+ long double timeDiff = currentTime - _frameTime;
+
+#ifdef DEBUG_DISPLAY
+ ss.str("");
+ ss << "time: " << floor(time*1000) << std::endl
+ << "VideoTime: " << floor(_frameTime*1000) << std::endl
+ << "TimeBase: " << floor(_frameDuration*1000) << std::endl
+ << "timeDiff: " << floor(timeDiff*1000);
+ logger.status(ss.str(), "");
+#endif
+
+ // check if time has reached the next frame
+ if (timeDiff < _frameDuration) {
+#ifdef DEBUG_FRAMES
+ // frame delay debug display
+ spawnGoldenRec(200, 15, 1, 16, 0, -1, 0x00ff00);
+#endif
+
+#ifdef DEBUG_DISPLAY
+ ss.str("");
+ ss << "not getting new frame" << std::endl
+ << "time: " << floor(time*1000) << std::endl
+ << "VideoTime: " << floor(_frameTime*1000) << std::endl
+ << "TimeBase: " << floor(_frameDuration*1000) << std::endl
+ << "timeDiff: " << floor(timeDiff*1000);
+ logger.status(ss.str(), "");
+#endif
+
+ // we do not need a new frame now
+ return NULL;
+ }
+ }
+
+ // fetch new frame (updates fFrameTime)
+ bool success = decodeFrame();
+ long double timeDiff = currentTime - _frameTime;
+
+ // check if we have to skip frames
+ // Either if we are one frame behind or if the skip threshold has been reached.
+ // Do not skip if the difference is less than fFrameDuration as there is no next frame.
+ // Note: We assume that fFrameDuration is the length of one frame.
+ if (timeDiff >= std::max(_frameDuration, SKIP_FRAME_DIFF)) {
+#ifdef DEBUG_FRAMES
+ //frame drop debug display
+ spawnGoldenRec(200, 55, 1, 16, 0, -1, 0xff0000);
+#endif
+#ifdef DEBUG_DISPLAY
+ ss.str("");
+ ss << "skipping frames" << std::endl
+ << "TimeBase: " << floor(_frameDuration*1000) << std::endl
+ << "timeDiff: " << floor(timeDiff*1000);
+ logger.status(ss.str(), "");
+#endif
+
+ // update video-time
+ int dropFrameCount = (int)(timeDiff / _frameDuration);
+ _frameTime = _frameTime + dropFrameCount * _frameDuration;
+
+ // skip frames
+ for (int i = 1; i <= dropFrameCount; ++i)
+ success = decodeFrame();
+ }
+
+ // check if we got an EOF or error
+ if (!success) {
+ if (_loop) {
+ // we have to loop, so rewind
+ setPosition(0);
+ // record the start-time of the current loop, so we can
+ // determine the position in the stream (fFrameTime-fLoopTime) later.
+ _loopTime = time;
+ }
+ return NULL;
+ }
+
+ /*
+ * Synchronization - end
+ */
+
+ // TODO: support for pan&scan
+ //if (_avFrame->pan_scan) {
+ // printf("PanScan: %d/%d", _avFrame->pan_scan->width, _avFrame->pan_scan->height);
+ //}
+
+ // otherwise we convert the pixeldata from YUV to RGB
+ int errnum;
+#ifdef USE_SWSCALE
+ errnum = sws_scale(_swScaleContext,
+ (uint8_t**)_avFrame->data, _avFrame->linesize,
+ 0, _codecContext->height,
+ (uint8_t**)_avFrameRGB->data, _avFrameRGB->linesize);
+#else
+ // img_convert from lib/ffmpeg/avcodec.pas is actually deprecated.
+ // If ./configure does not find SWScale then this gives the error
+ // that the identifier img_convert is not known or similar.
+ // I think this should be removed, but am not sure whether there should
+ // be some other replacement or a warning, Therefore, I leave it for now.
+ // April 2009, mischi
+ errnum = img_convert((AVPicture*)_avFrameRGB, PIXEL_FMT_FFMPEG,
+ (AVPicture*)_avFrame, _codecContext->pix_fmt,
+ _codecContext->width, _codecContext->height);
+#endif
+
+ if (errnum < 0) {
+ logger.error("Image conversion failed", "TVideoPlayback_ffmpeg.GetFrame");
+ return NULL;
+ }
+
+ if (!_frameTexValid)
+ _frameTexValid = true;
+
+ return _avFrameRGB->data[0];
+}
+
+void FFmpegVideoDecodeStream::setLoop(bool enable) {
+ _loop = enable;
+ _loopTime = 0;
+}
+
+bool FFmpegVideoDecodeStream::getLoop() {
+ return _loop;
+}
+
+/**
+ * Sets the stream's position.
+ * The stream is set to the first keyframe with timestamp <= Time.
+ * Note that fFrameTime is set to Time no matter if the actual position seeked to is
+ * at Time or the time of a preceding keyframe. fFrameTime will be updated to the
+ * actual frame time when GetFrame() is called the next time.
+ * @param Time new position in seconds
+ */
+void FFmpegVideoDecodeStream::setPosition(double time) {
+ int seekFlags;
+
+ if (!_opened)
+ return;
+
+ if (time < 0)
+ time = 0;
+
+ // TODO: handle fLoop-times
+ //time %= videoDuration;
+
+ // Do not use the AVSEEK_FLAG_ANY here. It will seek to any frame, even
+ // non keyframes (P-/B-frames). It will produce corrupted video frames as
+ // FFmpeg does not use the information of the preceding I-frame.
+ // The picture might be gray or green until the next keyframe occurs.
+ // Instead seek the first keyframe smaller than the requested time
+ // (AVSEEK_FLAG_BACKWARD). As this can be some seconds earlier than the
+ // requested time, let the sync in GetFrame() do its job.
+ seekFlags = AVSEEK_FLAG_BACKWARD;
+
+ _frameTime = time;
+ _eof = false;
+ _frameTexValid = false;
+
+ if (av_seek_frame(_formatContext, _streamIndex,
+ llround(time / av_q2d(_stream->time_base)), seekFlags) < 0)
+ {
+ logger.error("av_seek_frame() failed", "TVideoPlayback_ffmpeg.SetPosition");
+ return;
+ }
+
+ avcodec_flush_buffers(_codecContext);
+}
+
+double FFmpegVideoDecodeStream::getPosition() {
+ return _frameTime;
+}
+
+int FFmpegVideoDecodeStream::getFrameWidth() {
+ return _codecContext->width;
+}
+
+int FFmpegVideoDecodeStream::getFrameHeight() {
+ return _codecContext->height;
+}
+
+double FFmpegVideoDecodeStream::getFrameAspect() {
+ return _aspect;
+}
+
+/************************************
+ * C Interface
+ ************************************/
+
+#define VideoDecodeStreamObj(ptr) reinterpret_cast<FFmpegVideoDecodeStream*>(ptr)
+
+static BOOL PLUGIN_CALL ffmpegVideoDecoder_init() {
+ return TRUE;
+}
+
+static BOOL PLUGIN_CALL ffmpegVideoDecoder_finalize() {
+ return TRUE;
+}
+
+static videoDecodeStream_t* PLUGIN_CALL ffmpegVideoDecoder_open(const char *filename) {
+ return (videoDecodeStream_t*)FFmpegVideoDecodeStream::open(filename);
+}
+
+static void PLUGIN_CALL ffmpegVideoDecoder_close(videoDecodeStream_t *stream) {
+ delete VideoDecodeStreamObj(stream);
+}
+
+static void PLUGIN_CALL ffmpegVideoDecoder_setLoop(videoDecodeStream_t *stream, BOOL enable) {
+ VideoDecodeStreamObj(stream)->setLoop(enable);
+}
+
+static BOOL PLUGIN_CALL ffmpegVideoDecoder_getLoop(videoDecodeStream_t *stream) {
+ return (BOOL)VideoDecodeStreamObj(stream)->getLoop();
+}
+
+static void PLUGIN_CALL ffmpegVideoDecoder_setPosition(videoDecodeStream_t *stream, double time) {
+ VideoDecodeStreamObj(stream)->setPosition(time);
+}
+
+static double PLUGIN_CALL ffmpegVideoDecoder_getPosition(videoDecodeStream_t *stream) {
+ return VideoDecodeStreamObj(stream)->getPosition();
+}
+
+static int PLUGIN_CALL ffmpegVideoDecoder_getFrameWidth(videoDecodeStream_t *stream) {
+ return VideoDecodeStreamObj(stream)->getFrameWidth();
+}
+
+static int PLUGIN_CALL ffmpegVideoDecoder_getFrameHeight(videoDecodeStream_t *stream) {
+ return VideoDecodeStreamObj(stream)->getFrameHeight();
+}
+
+static double PLUGIN_CALL ffmpegVideoDecoder_getFrameAspect(videoDecodeStream_t *stream) {
+ return VideoDecodeStreamObj(stream)->getFrameAspect();
+}
+
+static uint8_t* PLUGIN_CALL ffmpegVideoDecoder_getFrame(videoDecodeStream_t *stream, long double time) {
+ return VideoDecodeStreamObj(stream)->getFrame(time);
+}
+
+/************************************
+ * Module information
+ ************************************/
+
+const videoDecoderInfo_t videoDecoderInfo = {
+ 80,
+ ffmpegVideoDecoder_init,
+ ffmpegVideoDecoder_finalize,
+ ffmpegVideoDecoder_open,
+ ffmpegVideoDecoder_close,
+ ffmpegVideoDecoder_setLoop,
+ ffmpegVideoDecoder_getLoop,
+ ffmpegVideoDecoder_setPosition,
+ ffmpegVideoDecoder_getPosition,
+ ffmpegVideoDecoder_getFrameWidth,
+ ffmpegVideoDecoder_getFrameHeight,
+ ffmpegVideoDecoder_getFrameAspect,
+ ffmpegVideoDecoder_getFrame
+};
diff --git a/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.h b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.h
new file mode 100644
index 00000000..7abe1257
--- /dev/null
+++ b/mediaplugin/src/plugins/media/ffmpeg/ffmpeg_video_decode.h
@@ -0,0 +1,115 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _FFMPEG_VIDEO_DECODE_H_
+#define _FFMPEG_VIDEO_DECODE_H_
+
+#include "ffmpeg_core.h"
+#include "core/plugin_video_decode.h"
+
+#define USE_SWSCALE
+
+// uncomment if you want to see the debug stuff
+//#define DEBUG_DISPLAY
+//#define DEBUG_FRAMES
+
+// use BGR-format for accelerated colorspace conversion with swscale
+#ifdef USE_SWSCALE
+# define PIXEL_FMT_BGR
+#endif
+
+#define PIXEL_FMT_BGR
+
+#ifdef PIXEL_FMT_BGR
+# define PIXEL_FMT_FFMPEG PIX_FMT_BGR24
+# define PIXEL_FMT_SIZE 3
+// looks strange on linux
+//# define PIXEL_FMT_FFMPEG PIX_FMT_BGR32;
+//# define PIXEL_FMT_SIZE 4;
+#else
+// looks strange on linux:
+# define PIXEL_FMT_FFMPEG PIX_FMT_RGB24
+# define PIXEL_FMT_SIZE 3
+#endif
+
+extern const videoDecoderInfo_t videoDecoderInfo;
+
+class FFmpegVideoDecodeStream : public VideoDecodeStream {
+private:
+ bool _opened; //**< stream successfully opened
+ bool _eof; //**< end-of-file state
+
+ bool _loop; //**< looping enabled
+
+ AVStream *_stream;
+ int _streamIndex;
+ AVFormatContext *_formatContext;
+ AVCodecContext *_codecContext;
+ AVCodec *_codec;
+
+ AVFrame *_avFrame;
+ AVFrame *_avFrameRGB;
+
+ uint8_t *_frameBuffer; //**< stores a FFmpeg video frame
+ bool _frameTexValid; //**< if true, fFrameTex contains the current frame
+
+#ifdef USE_SWSCALE
+ SwsContext *_swScaleContext;
+#endif
+
+ double _aspect; //**< width/height ratio
+
+ long double _frameDuration; //**< duration of a video frame in seconds (= 1/fps)
+ long double _frameTime; //**< video time position (absolute)
+ long double _loopTime; //**< start time of the current loop
+
+ FFmpegVideoDecodeStream();
+
+ bool decodeFrame();
+ void synchronizeTime(AVFrame *frame, double &pts);
+
+ bool _open(const IPath &filename);
+ void close();
+
+public:
+ virtual ~FFmpegVideoDecodeStream() {
+ close();
+ }
+
+ static FFmpegVideoDecodeStream* open(const IPath &filename);
+
+ virtual void setLoop(bool enable);
+ virtual bool getLoop();
+
+ virtual void setPosition(double time);
+ virtual double getPosition();
+
+ virtual int getFrameWidth();
+ virtual int getFrameHeight();
+
+ virtual double getFrameAspect();
+ virtual uint8_t* getFrame(long double time);
+};
+
+#endif /* _FFMPEG_VIDEO_DECODE_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/begin_pack.h b/mediaplugin/src/plugins/media/include/core/begin_pack.h
new file mode 100644
index 00000000..504f2f75
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/begin_pack.h
@@ -0,0 +1,36 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _BEGIN_PACK_H_
+#define _BEGIN_PACK_H_
+
+/*
+ * begin structure packing with 4 byte alignment.
+ */
+#ifdef _MSC_VER
+#pragma warning(disable: 4103) \
+#pragma pack(push,4)
+#endif
+
+#endif /* _BEGIN_PACK_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/end_pack.h b/mediaplugin/src/plugins/media/include/core/end_pack.h
new file mode 100644
index 00000000..f6ee748b
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/end_pack.h
@@ -0,0 +1,35 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _END_PACK_H_
+#define _END_PACK_H_
+
+/*
+ * end structure packing at previous byte alignment
+ */
+#if defined(_MSC_VER)
+#pragma pack(pop)
+#endif
+
+#endif /* _END_PACK_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/logger.h b/mediaplugin/src/plugins/media/include/core/logger.h
new file mode 100644
index 00000000..c9924b83
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/logger.h
@@ -0,0 +1,73 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _LOGGER_H_
+#define _LOGGER_H_
+
+#include "plugin_core.h"
+#include <string>
+
+#ifdef __cplusplus
+
+#define logger (Logger::getInstance())
+class Logger {
+private:
+ static Logger _instance;
+
+ Logger() {}
+ ~Logger() {}
+
+public:
+ static const Logger &getInstance() {
+ return _instance;
+ }
+
+ void log(log_level level, const std::string &msg, const std::string &context) const {
+ pluginCore->log(level, msg.c_str(), context.c_str());
+ }
+
+ void info(const std::string &msg, const std::string &context) const {
+ log(INFO, msg, context);
+ }
+
+ void status(const std::string &msg, const std::string &context) const {
+ log(STATUS, msg, context);
+ }
+
+ void warn(const std::string &msg, const std::string &context) const {
+ log(WARN, msg, context);
+ }
+
+ void error(const std::string &msg, const std::string &context) const {
+ log(ERROR, msg, context);
+ }
+
+ void critical(const std::string &msg, const std::string &context) const {
+ log(CRITICAL, msg, context);
+ }
+};
+
+#endif /* __cplusplus */
+
+#endif /* _LOGGER_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/plugin_audio_convert.h b/mediaplugin/src/plugins/media/include/core/plugin_audio_convert.h
new file mode 100644
index 00000000..e3a31b26
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/plugin_audio_convert.h
@@ -0,0 +1,69 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _PLUGIN_AUDIO_CONVERT_H_
+#define _PLUGIN_AUDIO_CONVERT_H_
+
+#include "util.h"
+#include "plugin_core.h"
+#include "logger.h"
+
+#ifdef __cplusplus
+
+class AudioConvertStream {
+protected:
+ AudioFormatInfo _srcFormatInfo;
+ AudioFormatInfo _dstFormatInfo;
+
+ AudioConvertStream(const AudioFormatInfo &srcFormatInfo, const AudioFormatInfo &dstFormatInfo) {
+ _srcFormatInfo = srcFormatInfo;
+ _dstFormatInfo = dstFormatInfo;
+ }
+
+public:
+ virtual ~AudioConvertStream() {};
+
+ /**
+ * Converts the InputBuffer and stores the result in OutputBuffer.
+ * If the result is not -1, inputSize will be set to the actual number of
+ * input-buffer bytes used.
+ * Returns the number of bytes written to the output-buffer or -1 if an error occured.
+ */
+ virtual int convert(uint8_t *inputBuffer, uint8_t *outputBuffer, int &inputSize) = 0;
+
+ /**
+ * Destination/Source size ratio
+ */
+ virtual double getRatio() = 0;
+
+ /**
+ * Size of an output buffer needed to store the result of a converted buffer with
+ * an input size of inputSize bytes.
+ */
+ virtual int getOutputBufferSize(int inputSize) = 0;
+};
+
+#endif /* __cplusplus */
+
+#endif /* _PLUGIN_AUDIO_CONVERT_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/plugin_audio_decode.h b/mediaplugin/src/plugins/media/include/core/plugin_audio_decode.h
new file mode 100644
index 00000000..2385f77c
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/plugin_audio_decode.h
@@ -0,0 +1,51 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _PLUGIN_AUDIO_DECODE_H_
+#define _PLUGIN_AUDIO_DECODE_H_
+
+#include "util.h"
+#include "plugin_core.h"
+#include "logger.h"
+
+#ifdef __cplusplus
+
+class PluginDecodeStream {
+public:
+ virtual ~PluginDecodeStream() {};
+
+ virtual double getLength() = 0;
+ virtual const AudioFormatInfo &getAudioFormatInfo() = 0;
+ virtual double getPosition() = 0;
+ virtual void setPosition(double time) = 0;
+ virtual bool getLoop() = 0;
+ virtual void setLoop(bool Enabled) = 0;
+ virtual bool isEOF() = 0;
+ virtual bool isError() = 0;
+ virtual int readData(uint8_t *buffer, int bufferSize) = 0;
+};
+
+#endif /* __cplusplus */
+
+#endif /* _PLUGIN_AUDIO_DECODE_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/plugin_core.h b/mediaplugin/src/plugins/media/include/core/plugin_core.h
new file mode 100644
index 00000000..f0cb19da
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/plugin_core.h
@@ -0,0 +1,222 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _PLUGIN_CORE_H_
+#define _PLUGIN_CORE_H_
+
+#include "inttypes.h"
+
+/*
+ * C Interface
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* declaration for export */
+#ifndef DLL_EXPORT
+# if defined(__WIN32__)
+# define DLL_EXPORT __declspec(dllexport)
+# else
+# if defined(__GNUC__) && __GNUC__ >= 4
+# define DLL_EXPORT __attribute__ ((visibility("default")))
+# else
+# define DLL_EXPORT
+# endif
+# endif
+#endif /* DLL_EXPORT */
+
+/* use C calling convention */
+#ifndef CDECL
+#if defined(__WIN32__) && !defined(__GNUC__)
+#define CDECL __cdecl
+#else
+#define CDECL
+#endif
+#endif /* CDECL */
+
+#define PLUGIN_CALL CDECL
+
+// VERSION: AAABBBCCC (A: Major, B: Minor, C: Revision)
+#define MAKE_VERSION(a,b,c) ((((a) * 1000 + (b)) * 1000) + (c))
+
+typedef enum { FALSE , TRUE } BOOL;
+
+typedef enum log_level {
+ DEBUG,
+ INFO,
+ STATUS,
+ WARN,
+ ERROR,
+ CRITICAL
+} log_level;
+
+typedef struct{} fileStream_t;
+typedef struct{} cond_t;
+typedef struct{} mutex_t;
+typedef struct{} thread_t;
+
+#define FILE_OPEN_MODE_READ 0x01
+#define FILE_OPEN_MODE_WRITE 0x02
+#define FILE_OPEN_MODE_READ_WRITE (FILE_OPEN_MODE_READ | FILE_OPEN_MODE_WRITE)
+
+/* returned by condWaitTimeout() if a timeout occurs. */
+#define MUTEX_TIMEDOUT 1
+
+typedef struct pluginCore_t {
+ int version;
+
+ void PLUGIN_CALL (*log)(int level, const char *msg, const char *context);
+ uint32_t PLUGIN_CALL (*ticksMillis);
+
+ fileStream_t* PLUGIN_CALL (*fileOpen)(const char *utf8Filename, int mode);
+ void PLUGIN_CALL (*fileClose)(fileStream_t *stream);
+ int64_t PLUGIN_CALL (*fileRead)(fileStream_t *stream, uint8_t *buf, int size);
+ int64_t PLUGIN_CALL (*fileWrite)(fileStream_t *stream, const uint8_t *buf, int size);
+ int64_t PLUGIN_CALL (*fileSeek)(fileStream_t *stream, int64_t pos, int whence);
+ int64_t PLUGIN_CALL (*fileSize)(fileStream_t *stream);
+
+ thread_t* PLUGIN_CALL (*threadCreate)(int (PLUGIN_CALL *fn)(void *), void *data);
+ uint32_t PLUGIN_CALL (*threadCurrentID)();
+ uint32_t PLUGIN_CALL (*threadGetID)(thread_t *thread);
+ void PLUGIN_CALL (*threadWait)(thread_t *thread, int *status);
+ void PLUGIN_CALL (*threadSleep)(uint32_t millisecs);
+
+ mutex_t* PLUGIN_CALL (*mutexCreate)();
+ void PLUGIN_CALL (*mutexDestroy)(mutex_t *mutex);
+ int PLUGIN_CALL (*mutexLock)(mutex_t *mutex);
+ int PLUGIN_CALL (*mutexUnlock)(mutex_t *mutex);
+
+ cond_t* PLUGIN_CALL (*condCreate)();
+ void PLUGIN_CALL (*condDestroy)(cond_t *cond);
+ int PLUGIN_CALL (*condSignal)(cond_t *cond);
+ int PLUGIN_CALL (*condBroadcast)(cond_t *cond);
+ int PLUGIN_CALL (*condWait)(cond_t *cond, mutex_t *mutex);
+ int PLUGIN_CALL (*condWaitTimeout)(cond_t *cond, mutex_t *mutex, uint32_t ms);
+} pluginCore_t;
+
+typedef enum audioSampleFormat_t {
+ asfUnknown, // unknown format
+ asfU8, asfS8, // unsigned/signed 8 bits
+ asfU16LSB, asfS16LSB, // unsigned/signed 16 bits (endianness: LSB)
+ asfU16MSB, asfS16MSB, // unsigned/signed 16 bits (endianness: MSB)
+ asfU16, asfS16, // unsigned/signed 16 bits (endianness: System)
+ asfS32, // signed 32 bits (endianness: System)
+ asfFloat, // float
+ asfDouble // double
+} audioSampleFormat_t;
+
+// Size of one sample (one channel only) in bytes
+static const int g_audioSampleSize[] = {
+ 0, // asfUnknown
+ 1, 1, // asfU8, asfS8
+ 2, 2, // asfU16LSB, asfS16LSB
+ 2, 2, // asfU16MSB, asfS16MSB
+ 2, 2, // asfU16, asfS16
+ 3, // asfS24
+ 4, // asfS32
+ 4, // asfFloat
+};
+
+struct audioFormatInfo_t {
+ double sampleRate;
+ uint8_t channels;
+ audioSampleFormat_t format;
+};
+
+typedef struct{} audioDecodeStream_t;
+typedef struct{} audioConvertStream_t;
+typedef struct{} videoDecodeStream_t;
+
+typedef struct audioDecoderInfo_t {
+ int priority;
+ BOOL PLUGIN_CALL (*init)();
+ BOOL PLUGIN_CALL (*finalize)();
+ audioDecodeStream_t* PLUGIN_CALL (*open)(const char *filename);
+ void PLUGIN_CALL (*close)(audioDecodeStream_t *stream);
+ double PLUGIN_CALL (*getLength)(audioDecodeStream_t *stream);
+ void PLUGIN_CALL (*getAudioFormatInfo)(audioDecodeStream_t *stream, audioFormatInfo_t *info);
+ double PLUGIN_CALL (*getPosition)(audioDecodeStream_t *stream);
+ void PLUGIN_CALL (*setPosition)(audioDecodeStream_t *stream, double time);
+ BOOL PLUGIN_CALL (*getLoop)(audioDecodeStream_t *stream);
+ void PLUGIN_CALL (*setLoop)(audioDecodeStream_t *stream, BOOL enabled);
+ BOOL PLUGIN_CALL (*isEOF)(audioDecodeStream_t *stream);
+ BOOL PLUGIN_CALL (*isError)(audioDecodeStream_t *stream);
+ int PLUGIN_CALL (*readData)(audioDecodeStream_t *stream, uint8_t *buffer, int bufferSize);
+} audioDecoderInfo_t;
+
+typedef struct audioConverterInfo_t {
+ int priority;
+ BOOL PLUGIN_CALL (*init)();
+ BOOL PLUGIN_CALL (*finalize)();
+ audioConvertStream_t* PLUGIN_CALL (*open)(audioFormatInfo_t *inputFormat,
+ audioFormatInfo_t *outputFormat);
+ void PLUGIN_CALL (*close)(audioConvertStream_t *stream);
+ int PLUGIN_CALL (*convert)(audioConvertStream_t *stream,
+ uint8_t *input, uint8_t *output, int *numSamples);
+ int PLUGIN_CALL (*getOutputBufferSize)(audioConvertStream_t *stream, int inputSize);
+ double PLUGIN_CALL (*getRatio)(audioConvertStream_t *stream);
+} audioConverterInfo_t;
+
+typedef struct videoDecoderInfo_t {
+ int priority;
+ BOOL PLUGIN_CALL (*init)();
+ BOOL PLUGIN_CALL (*finalize)();
+ videoDecodeStream_t* PLUGIN_CALL (*open)(const char *filename);
+ void PLUGIN_CALL (*close)(videoDecodeStream_t *stream);
+ void PLUGIN_CALL (*setLoop)(videoDecodeStream_t *stream, BOOL enable);
+ BOOL PLUGIN_CALL (*getLoop)(videoDecodeStream_t *stream);
+ void PLUGIN_CALL (*setPosition)(videoDecodeStream_t *stream, double time);
+ double PLUGIN_CALL (*getPosition)(videoDecodeStream_t *stream);
+ int PLUGIN_CALL (*getFrameWidth)(videoDecodeStream_t *stream);
+ int PLUGIN_CALL (*getFrameHeight)(videoDecodeStream_t *stream);
+ double PLUGIN_CALL (*getFrameAspect)(videoDecodeStream_t *stream);
+ uint8_t* PLUGIN_CALL (*getFrame)(videoDecodeStream_t *stream, long double time);
+} videoDecoderInfo_t;
+
+typedef struct pluginInfo_t {
+ int version;
+ const char *name;
+ BOOL PLUGIN_CALL (*initialize)();
+ BOOL PLUGIN_CALL (*finalize)();
+ const audioDecoderInfo_t *audioDecoder;
+ const audioConverterInfo_t *audioConverter;
+ const videoDecoderInfo_t *videoDecoder;
+} pluginInfo_t;
+
+
+// plugin entry function (must be implemented by the plugin)
+DLL_EXPORT const pluginInfo_t* PLUGIN_CALL Plugin_register(const pluginCore_t *core);
+
+// must be provided by the plugin and initialized on plugin initialization
+extern const pluginCore_t *pluginCore;
+
+BOOL pluginInitCore(const pluginCore_t *core);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PLUGIN_CORE_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/plugin_video_decode.h b/mediaplugin/src/plugins/media/include/core/plugin_video_decode.h
new file mode 100644
index 00000000..6403852f
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/plugin_video_decode.h
@@ -0,0 +1,53 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _PLUGIN_VIDEO_DECODE_H_
+#define _PLUGIN_VIDEO_DECODE_H_
+
+#include "util.h"
+#include "plugin_core.h"
+#include "logger.h"
+
+#ifdef __cplusplus
+
+class VideoDecodeStream {
+public:
+ virtual ~VideoDecodeStream() {}
+
+ virtual void setLoop(bool enable) = 0;
+ virtual bool getLoop() = 0;
+
+ virtual void setPosition(double time) = 0;
+ virtual double getPosition() = 0;
+
+ virtual int getFrameWidth() = 0;
+ virtual int getFrameHeight() = 0;
+
+ virtual double getFrameAspect() = 0;
+ virtual uint8_t* getFrame(long double time) = 0;
+};
+
+#endif /* __cplusplus */
+
+#endif /* _PLUGIN_VIDEO_DECODE_H_ */
diff --git a/mediaplugin/src/plugins/media/include/core/util.h b/mediaplugin/src/plugins/media/include/core/util.h
new file mode 100644
index 00000000..55c5e93f
--- /dev/null
+++ b/mediaplugin/src/plugins/media/include/core/util.h
@@ -0,0 +1,292 @@
+/* UltraStar Deluxe - Karaoke Game
+ *
+ * UltraStar Deluxe is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ */
+#ifndef _UTIL_H_
+#define _UTIL_H_
+
+#include <string>
+#include "plugin_core.h"
+
+class AudioFormatInfo {
+private:
+ double _sampleRate;
+ uint8_t _channels;
+ audioSampleFormat_t _format;
+ int _frameSize;
+
+ void updateFrameSize() {
+ _frameSize = g_audioSampleSize[_format] * _channels;
+ }
+
+public:
+ double getSampleRate() const { return _sampleRate; }
+ void setSampleRate(double sampleRate) { _sampleRate = sampleRate; }
+
+ uint8_t getChannels() const { return _channels; }
+ void setChannels(uint8_t channels) {
+ _channels = channels;
+ updateFrameSize();
+ }
+
+ audioSampleFormat_t getFormat() const { return _format; }
+ void setFormat(audioSampleFormat_t sampleFormat) {
+ _format = sampleFormat;
+ updateFrameSize();
+ }
+
+ long getFrameSize() const { return _frameSize; }
+ double getBytesPerSec() const { return _frameSize * _sampleRate; }
+
+public:
+ AudioFormatInfo() :
+ _sampleRate(0),
+ _channels(0),
+ _format(asfUnknown),
+ _frameSize(0)
+ {}
+
+ AudioFormatInfo(int channels, int sampleRate, audioSampleFormat_t sampleFormat) :
+ _sampleRate(sampleRate), _channels(channels), _format(sampleFormat)
+ {
+ updateFrameSize();
+ }
+
+ AudioFormatInfo(audioFormatInfo_t *info) :
+ _sampleRate(info->sampleRate),
+ _channels(info->channels),
+ _format(info->format)
+ {
+ updateFrameSize();
+ }
+
+ /**
+ * Returns the inverse ratio of the size of data in this format to its
+ * size in a given target format.
+ * Example: SrcSize*SrcInfo.GetRatio(TgtInfo) = TgtSize
+ */
+ double getRatio(const AudioFormatInfo &targetInfo) const {
+ return (targetInfo.getFrameSize() / this->_frameSize) *
+ (targetInfo.getSampleRate() / this->_sampleRate);
+ }
+
+ void toCStruct(audioFormatInfo_t *info) const {
+ info->channels = _channels;
+ info->format = _format;
+ info->sampleRate = _sampleRate;
+ }
+
+ //AudioFormatInfo copy();
+};
+
+class IPath {
+private:
+ std::string _filename;
+public:
+ IPath(const char *filename) :
+ _filename(filename)
+ {
+ // TODO
+ }
+
+ IPath(std::string filename) :
+ _filename(filename)
+ {
+ // TODO
+ }
+
+ std::string toNative() const {
+ // TODO
+ return _filename;
+ }
+
+ std::string toUTF8() const {
+ // TODO
+ return _filename;
+ }
+
+ bool isFile() const {
+ // TODO
+ return true;
+ }
+};
+
+extern "C" {
+PLUGIN_CALL int threadMainRoutine(void *data);
+}
+
+class Thread {
+private:
+ thread_t *_thread;
+public:
+ Thread() : _thread(0) {}
+ virtual ~Thread() {}
+
+ virtual int run() = 0;
+
+ void start() {
+ _thread = pluginCore->threadCreate(threadMainRoutine, this);
+ }
+
+ /**
+ * Get the 32-bit thread identifier for the current thread.
+ */
+ static uint32_t getCurrentThreadID() {
+ return pluginCore->threadCurrentID();
+ }
+
+ /**
+ * Get the 32-bit thread identifier for the specified thread,
+ * equivalent to SDL_ThreadID() if the specified thread is NULL.
+ */
+ uint32_t getThreadID() {
+ return pluginCore->threadGetID(_thread);
+ }
+
+ /**
+ * Wait a specified number of milliseconds before returning.
+ */
+ static void sleep(uint32_t ms) {
+ pluginCore->threadSleep(ms);
+ }
+
+ /**
+ * Wait for a thread to finish.
+ * The return code for the thread function is placed in the area
+ * pointed to by 'status', if 'status' is not NULL.
+ */
+ void wait(int *status) {
+ if (_thread) {
+ pluginCore->threadWait(_thread, status);
+ }
+ }
+
+ void wait() {
+ int status;
+ if (_thread) {
+ pluginCore->threadWait(_thread, &status);
+ }
+ }
+};
+
+class Condition;
+
+class Mutex {
+private:
+ friend class Condition;
+ mutex_t *_mutex;
+public:
+ Mutex() {
+ _mutex = pluginCore->mutexCreate();
+ }
+
+ ~Mutex() {
+ pluginCore->mutexDestroy(_mutex);
+ }
+
+ /**
+ * Lock the mutex
+ * Returns 0, or -1 on error
+ */
+ int lock() {
+ return pluginCore->mutexLock(_mutex);
+ }
+
+ /**
+ * Unlock the mutex
+ * It is an error to unlock a mutex that has not been locked by
+ * the current thread, and doing so results in undefined behavior.
+ *
+ * Returns 0, or -1 on error
+ */
+ int unlock() {
+ return pluginCore->mutexUnlock(_mutex);
+ }
+
+ class RegionLock {
+ private:
+ Mutex *_mutex;
+ public:
+ RegionLock(Mutex &mutex) :
+ _mutex(&mutex)
+ {
+ _mutex->lock();
+ }
+
+ ~RegionLock() {
+ _mutex->unlock();
+ }
+ };
+};
+
+class Condition {
+private:
+ cond_t *_cond;
+public:
+ Condition() {
+ _cond = pluginCore->condCreate();
+ }
+
+ ~Condition() {
+ pluginCore->condDestroy(_cond);
+ }
+
+ /**
+ * Wait on the condition variable, unlocking the provided mutex.
+ * The mutex must be locked before entering this function!
+ * The mutex is re-locked once the condition variable is signaled.
+ * Returns 0 when it is signaled, or -1 on error.
+ */
+ int wait(const Mutex &mutex) {
+ return pluginCore->condWait(_cond, mutex._mutex);
+ }
+
+ /*
+ * Waits for at most 'ms' milliseconds, and returns 0 if the condition
+ * variable is signaled, SDL_MUTEX_TIMEDOUT if the condition is not
+ * signaled in the allotted time, and -1 on error.
+ * On some platforms this function is implemented by looping with a delay
+ * of 1 ms, and so should be avoided if possible.
+ */
+ int waitTimeout(const Mutex &mutex, uint32_t ms) {
+ return pluginCore->condWaitTimeout(_cond, mutex._mutex, ms);
+ }
+
+ /**
+ * Restart one of the threads that are waiting on the condition variable.
+ * Returns 0 or -1 on error.
+ */
+ int signal() {
+ return pluginCore->condSignal(_cond);
+ }
+
+ /**
+ * Restart all threads that are waiting on the condition variable.
+ * Returns 0 or -1 on error.
+ */
+ int broadcast() {
+ return pluginCore->condBroadcast(_cond);
+ }
+};
+
+#endif /* _UTIL_H_ */