mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-22 07:53:52 +00:00
Implement the ffmpeg decoder
This commit is contained in:
parent
9a48002025
commit
deb473b9ae
2 changed files with 354 additions and 6 deletions
|
@ -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<FFmpeg_Decoder*>(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<FFmpeg_Decoder*>(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<FFmpeg_Decoder*>(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<MyStream*>::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<MyStream> 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()
|
||||
|
|
|
@ -21,6 +21,18 @@ namespace MWSound
|
|||
{
|
||||
class FFmpeg_Decoder : public Sound_Decoder
|
||||
{
|
||||
AVFormatContext *mFormatCtx;
|
||||
|
||||
struct MyStream;
|
||||
std::vector<MyStream*> 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();
|
||||
|
||||
|
|
Loading…
Reference in a new issue