From deb473b9ae69a37a4c9db91a39f08c7184b5bb24 Mon Sep 17 00:00:00 2001 From: Chris Robinson Date: Tue, 20 Mar 2012 16:45:01 -0700 Subject: [PATCH] Implement the ffmpeg decoder --- apps/openmw/mwsound/ffmpeg_decoder.cpp | 348 ++++++++++++++++++++++++- apps/openmw/mwsound/ffmpeg_decoder.hpp | 12 + 2 files changed, 354 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index cb14df946f..b8ad809263 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -9,33 +9,369 @@ 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) { - fail("Not currently working"); + close(); + mDataStream = mResourceMgr.openResource(fname); + + if((mFormatCtx=avformat_alloc_context()) == NULL) + fail("Failed to allocate context"); + + try + { + 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) + fail("Failed to open input"); + + if(avformat_find_stream_info(mFormatCtx, NULL) < 0) + fail("Failed to find stream info"); + + 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; + codec = avcodec_find_decoder(stream->mCodecCtx->codec_id); + if(!codec || avcodec_open(stream->mCodecCtx, codec) < 0) + fail("Could not open audio codec"); + + 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"); + } + 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); + + mStreams.erase(mStreams.begin()); + } + if(mFormatCtx) + av_close_input_file(mFormatCtx); + mFormatCtx = NULL; } void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type) { - fail("Not currently working"); + 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) { - fail("Not currently working"); - return 0; + if(mStreams.empty()) + fail("No audio streams"); + + return mStreams.front()->readAVAudioData(buffer, bytes); } void FFmpeg_Decoder::rewind() { - fail("Not currently working"); + av_seek_frame(mFormatCtx, -1, 0, 0); + for(size_t i = 0;i < mStreams.size();i++) + mStreams[i]->clearPackets(); } -FFmpeg_Decoder::FFmpeg_Decoder() +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() diff --git a/apps/openmw/mwsound/ffmpeg_decoder.hpp b/apps/openmw/mwsound/ffmpeg_decoder.hpp index de39259886..9211bcc0dc 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.hpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.hpp @@ -21,6 +21,18 @@ namespace MWSound { class FFmpeg_Decoder : public Sound_Decoder { + AVFormatContext *mFormatCtx; + + struct MyStream; + std::vector mStreams; + + bool getNextPacket(int streamidx); + + Ogre::DataStreamPtr mDataStream; + static int readPacket(void *user_data, uint8_t *buf, int buf_size); + static int writePacket(void *user_data, uint8_t *buf, int buf_size); + static int64_t seek(void *user_data, int64_t offset, int whence); + virtual void open(const std::string &fname); virtual void close();