/* 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 #include #include // 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(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 };