/* 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 #include const uint8_t* STATUS_PACKET = (uint8_t*)"STATUS_PACKET"; #define UNICODE_PROTOCOL_NAME "ufile" #define UNICODE_PROTOCOL_PREFIX UNICODE_PROTOCOL_NAME ":" std::string MediaCore_FFmpeg::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 MediaCore_FFmpeg::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(); registerUTF8FileProtocol(); } 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; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(51,65,0) case SAMPLE_FMT_DBL: *format = asfDouble; break; #endif default: return false; } return true; } /** * UTF-8 Filename wrapper based on: * http://www.mail-archive.com/libav-user@mplayerhq.hu/msg02460.html */ static 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; break; default: return AVERROR_NOTSUPP; } fileStream_t *stream = pluginCore->fileOpen(utf8Filename.c_str(), mode); if (!stream) return AVERROR_NOENT; h->priv_data = stream; return 0; } static int CDECL ffmpegStreamRead(URLContext *h, uint8_t *buf, int size) { fileStream_t *stream = (fileStream_t*)h->priv_data; return pluginCore->fileRead(stream, buf, size); } #if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(52,68,0) static int CDECL ffmpegStreamWrite(URLContext *h, const unsigned char *buf, int size) #else static int CDECL ffmpegStreamWrite(URLContext *h, unsigned char *buf, int size) #endif { fileStream_t *stream = (fileStream_t*)h->priv_data; return pluginCore->fileWrite(stream, buf, size); } static 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); } } static int CDECL ffmpegStreamClose(URLContext *h) { fileStream_t *stream = (fileStream_t*)h->priv_data; pluginCore->fileClose(stream); return 0; } void MediaCore_FFmpeg::registerUTF8FileProtocol() { 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)); } /* 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 (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) { { 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, 10) == 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; }