diff options
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(¤tListEntry->pkt); + // free packet data + av_free_packet(¤tListEntry->pkt); + // Note: param must be a pointer to a pointer! + av_freep(¤tListEntry); + 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_ */ |