#ifdef OPENMW_USE_FFMPEG #include "ffmpeg_decoder.hpp" namespace MWSound { static void fail(const std::string &msg) { throw std::runtime_error("FFmpeg exception: "+msg); } struct PacketList { AVPacket pkt; PacketList *next; }; struct FFmpeg_Decoder::MyStream { AVCodecContext *mCodecCtx; int mStreamIdx; PacketList *mPackets; char *mDecodedData; size_t mDecodedDataSize; FFmpeg_Decoder *mParent; void clearPackets(); void *getAVAudioData(size_t *length); size_t readAVAudioData(void *data, size_t length); }; int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; return stream->read(buf, buf_size); } int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; return stream->write(buf, buf_size); } int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) { Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; whence &= ~AVSEEK_FORCE; if(whence == AVSEEK_SIZE) return stream->size(); if(whence == SEEK_SET) stream->seek(offset); else if(whence == SEEK_CUR) stream->seek(stream->tell()+offset); else if(whence == SEEK_END) stream->seek(stream->size()+offset); else return -1; return stream->tell(); } /* Used by getAV*Data to search for more compressed data, and buffer it in the * correct stream. It won't buffer data for streams that the app doesn't have a * handle for. */ bool FFmpeg_Decoder::getNextPacket(int streamidx) { PacketList *packet; packet = (PacketList*)av_malloc(sizeof(*packet)); packet->next = NULL; next_packet: while(av_read_frame(mFormatCtx, &packet->pkt) >= 0) { std::vector::iterator iter = mStreams.begin(); /* Check each stream the user has a handle for, looking for the one * this packet belongs to */ while(iter != mStreams.end()) { if((*iter)->mStreamIdx == packet->pkt.stream_index) { PacketList **last; last = &(*iter)->mPackets; while(*last != NULL) last = &(*last)->next; *last = packet; if((*iter)->mStreamIdx == streamidx) return true; packet = (PacketList*)av_malloc(sizeof(*packet)); packet->next = NULL; goto next_packet; } iter++; } /* Free the packet and look for another */ av_free_packet(&packet->pkt); } av_free(packet); return false; } void FFmpeg_Decoder::MyStream::clearPackets() { while(mPackets) { PacketList *self = mPackets; mPackets = self->next; av_free_packet(&self->pkt); av_free(self); } } void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length) { int size; int len; if(length) *length = 0; if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO) return NULL; mDecodedDataSize = 0; next_packet: if(!mPackets && !mParent->getNextPacket(mStreamIdx)) return NULL; /* Decode some data, and check for errors */ size = AVCODEC_MAX_AUDIO_FRAME_SIZE; while((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, &mPackets->pkt)) == 0) { PacketList *self; if(size > 0) break; /* Packet went unread and no data was given? Drop it and try the next, * I guess... */ self = mPackets; mPackets = self->next; av_free_packet(&self->pkt); av_free(self); if(!mPackets) goto next_packet; size = AVCODEC_MAX_AUDIO_FRAME_SIZE; } if(len < 0) return NULL; if(len < mPackets->pkt.size) { /* Move the unread data to the front and clear the end bits */ int remaining = mPackets->pkt.size - len; memmove(mPackets->pkt.data, &mPackets->pkt.data[len], remaining); memset(&mPackets->pkt.data[remaining], 0, mPackets->pkt.size - remaining); mPackets->pkt.size -= len; } else { PacketList *self; self = mPackets; mPackets = self->next; av_free_packet(&self->pkt); av_free(self); } if(size == 0) goto next_packet; /* Set the output buffer size */ mDecodedDataSize = size; if(length) *length = mDecodedDataSize; return mDecodedData; } size_t FFmpeg_Decoder::MyStream::readAVAudioData(void *data, size_t length) { size_t dec = 0; while(dec < length) { /* If there's no decoded data, find some */ if(mDecodedDataSize == 0) { if(getAVAudioData(NULL) == NULL) break; } if(mDecodedDataSize > 0) { /* Get the amount of bytes remaining to be written, and clamp to * the amount of decoded data we have */ size_t rem = length-dec; if(rem > mDecodedDataSize) rem = mDecodedDataSize; /* Copy the data to the app's buffer and increment */ if(data != NULL) { memcpy(data, mDecodedData, rem); data = (char*)data + rem; } dec += rem; /* If there's any decoded data left, move it to the front of the * buffer for next time */ if(rem < mDecodedDataSize) memmove(mDecodedData, &mDecodedData[rem], mDecodedDataSize - rem); mDecodedDataSize -= rem; } } /* Return the number of bytes we were able to get */ return dec; } void FFmpeg_Decoder::open(const std::string &fname) { close(); mDataStream = mResourceMgr.openResource(fname); if((mFormatCtx=avformat_alloc_context()) == NULL) fail("Failed to allocate context"); mFormatCtx->pb = avio_alloc_context(NULL, 0, 0, this, readPacket, writePacket, seek); if(!mFormatCtx->pb || avformat_open_input(&mFormatCtx, fname.c_str(), NULL, NULL) != 0) { avformat_free_context(mFormatCtx); mFormatCtx = NULL; fail("Failed to allocate input stream"); } try { if(avformat_find_stream_info(mFormatCtx, NULL) < 0) fail("Failed to find stream info in "+fname); for(size_t j = 0;j < mFormatCtx->nb_streams;j++) { if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO) { std::auto_ptr stream(new MyStream); stream->mCodecCtx = mFormatCtx->streams[j]->codec; stream->mStreamIdx = j; stream->mPackets = NULL; AVCodec *codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); if(!codec) { std::stringstream ss("No codec found for id "); ss << stream->mCodecCtx->codec_id; fail(ss.str()); } if(avcodec_open(stream->mCodecCtx, codec) < 0) fail("Failed to open audio codec " + std::string(codec->long_name)); stream->mDecodedData = (char*)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE); stream->mDecodedDataSize = 0; stream->mParent = this; mStreams.push_back(stream.release()); break; } } if(mStreams.empty()) fail("No audio streams in "+fname); } catch(std::exception &e) { av_close_input_file(mFormatCtx); mFormatCtx = NULL; throw; } } void FFmpeg_Decoder::close() { while(!mStreams.empty()) { MyStream *stream = mStreams.front(); stream->clearPackets(); avcodec_close(stream->mCodecCtx); av_free(stream->mDecodedData); delete stream; mStreams.erase(mStreams.begin()); } if(mFormatCtx) av_close_input_file(mFormatCtx); mFormatCtx = NULL; mDataStream.setNull(); } void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { if(mStreams.empty()) fail("No audio stream info"); MyStream *stream = mStreams[0]; if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8) *type = SampleType_UInt8; else if(stream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16) *type = SampleType_Int16; else fail(std::string("Unsupported sample format: ")+ av_get_sample_fmt_name(stream->mCodecCtx->sample_fmt)); if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO) *chans = ChannelConfig_Mono; else if(stream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO) *chans = ChannelConfig_Stereo; else if(stream->mCodecCtx->channel_layout == 0) { /* Unknown channel layout. Try to guess. */ if(stream->mCodecCtx->channels == 1) *chans = ChannelConfig_Mono; else if(stream->mCodecCtx->channels == 2) *chans = ChannelConfig_Stereo; else { std::stringstream sstr("Unsupported raw channel count: "); sstr << stream->mCodecCtx->channels; fail(sstr.str()); } } else { char str[1024]; av_get_channel_layout_string(str, sizeof(str), stream->mCodecCtx->channels, stream->mCodecCtx->channel_layout); fail(std::string("Unsupported channel layout: ")+str); } *samplerate = stream->mCodecCtx->sample_rate; } size_t FFmpeg_Decoder::read(char *buffer, size_t bytes) { if(mStreams.empty()) fail("No audio streams"); return mStreams.front()->readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::readAll(std::vector &output) { if(mStreams.empty()) fail("No audio streams"); MyStream *stream = mStreams.front(); char *inbuf; size_t got; while((inbuf=(char*)stream->getAVAudioData(&got)) != NULL && got > 0) output.insert(output.end(), inbuf, inbuf+got); } void FFmpeg_Decoder::rewind() { av_seek_frame(mFormatCtx, -1, 0, 0); std::for_each(mStreams.begin(), mStreams.end(), std::mem_fun(&MyStream::clearPackets)); } FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL) { static bool done_init = false; /* We need to make sure ffmpeg is initialized. Optionally silence warning * output from the lib */ if(!done_init) { av_register_all(); av_log_set_level(AV_LOG_ERROR); done_init = true; } } FFmpeg_Decoder::~FFmpeg_Decoder() { close(); } } #endif