|
|
|
#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<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)
|
|
|
|
{
|
|
|
|
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<MyStream> 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<char> &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
|