1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-26 18:35:20 +00:00
OpenMW/apps/openmw/mwsound/ffmpeg_decoder.cpp

363 lines
9.7 KiB
C++
Raw Normal View History

2012-03-16 23:59:21 -07:00
#ifdef OPENMW_USE_FFMPEG
2012-11-14 05:32:23 +01:00
2012-03-16 23:59:21 -07:00
#include "ffmpeg_decoder.hpp"
2012-11-14 05:32:23 +01:00
// auto_ptr
#include <memory>
#include <stdexcept>
2012-03-16 23:59:21 -07:00
namespace MWSound
{
2012-03-17 02:51:46 -07:00
static void fail(const std::string &msg)
{ throw std::runtime_error("FFmpeg exception: "+msg); }
2012-03-20 16:45:01 -07:00
struct FFmpeg_Decoder::MyStream {
AVCodecContext *mCodecCtx;
int mStreamIdx;
2012-12-15 22:54:54 -08:00
AVPacket mPacket;
2012-03-20 16:45:01 -07:00
char *mDecodedData;
size_t mDecodedDataSize;
FFmpeg_Decoder *mParent;
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()
2012-03-20 16:45:01 -07:00
{
if(!mStream.get())
return false;
2012-03-20 16:45:01 -07:00
2012-12-15 22:54:54 -08:00
while(av_read_frame(mFormatCtx, &mStream->mPacket) >= 0)
2012-03-20 16:45:01 -07:00
{
/* Check if the packet belongs to this stream */
2012-12-15 22:54:54 -08:00
if(mStream->mStreamIdx == mStream->mPacket.stream_index)
return true;
2012-12-15 22:54:54 -08:00
2012-03-20 16:45:01 -07:00
/* Free the packet and look for another */
2012-12-15 22:54:54 -08:00
av_free_packet(&mStream->mPacket);
2012-03-20 16:45:01 -07:00
}
return false;
}
void *FFmpeg_Decoder::MyStream::getAVAudioData(size_t *length)
{
2012-12-15 22:54:54 -08:00
int size, len;
2012-03-20 16:45:01 -07:00
if(length) *length = 0;
if(mCodecCtx->codec_type != AVMEDIA_TYPE_AUDIO)
return NULL;
mDecodedDataSize = 0;
2012-12-15 22:54:54 -08:00
do {
if(mPacket.size == 0 && !mParent->getNextPacket())
return NULL;
2012-03-20 16:45:01 -07:00
2012-12-15 22:54:54 -08:00
/* Decode some data, and check for errors */
2012-03-20 16:45:01 -07:00
size = AVCODEC_MAX_AUDIO_FRAME_SIZE;
2012-12-15 22:54:54 -08:00
if((len=avcodec_decode_audio3(mCodecCtx, (int16_t*)mDecodedData, &size, &mPacket)) < 0)
return NULL;
2012-03-20 16:45:01 -07:00
/* Move the unread data to the front and clear the end bits */
2012-12-15 22:54:54 -08:00
int remaining = mPacket.size - len;
if(remaining <= 0)
av_free_packet(&mPacket);
else
{
memmove(mPacket.data, &mPacket.data[len], remaining);
av_shrink_packet(&mPacket, remaining);
}
} while(size == 0);
2012-03-20 16:45:01 -07:00
/* 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)
2012-03-16 23:59:21 -07:00
{
2012-03-20 16:45:01 -07:00
close();
mDataStream = mResourceMgr.openResource(fname);
2012-03-20 16:45:01 -07:00
if((mFormatCtx=avformat_alloc_context()) == NULL)
fail("Failed to allocate context");
2012-03-21 12:20:36 -07:00
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)
2012-03-20 16:45:01 -07:00
{
2012-03-21 12:20:36 -07:00
avformat_free_context(mFormatCtx);
mFormatCtx = NULL;
fail("Failed to allocate input stream");
}
2012-03-20 16:45:01 -07:00
2012-03-21 12:20:36 -07:00
try
{
2012-03-20 16:45:01 -07:00
if(avformat_find_stream_info(mFormatCtx, NULL) < 0)
2012-03-20 18:49:52 -07:00
fail("Failed to find stream info in "+fname);
2012-03-20 16:45:01 -07:00
int audio_idx = -1;
2012-03-20 16:45:01 -07:00
for(size_t j = 0;j < mFormatCtx->nb_streams;j++)
{
if(mFormatCtx->streams[j]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
audio_idx = j;
2012-03-20 16:45:01 -07:00
break;
}
}
if(audio_idx == -1)
2012-03-20 18:49:52 -07:00
fail("No audio streams in "+fname);
std::auto_ptr<MyStream> stream(new MyStream);
stream->mCodecCtx = mFormatCtx->streams[audio_idx]->codec;
stream->mStreamIdx = audio_idx;
2012-12-15 22:54:54 -08:00
memset(&stream->mPacket, 0, sizeof(stream->mPacket));
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_open2(stream->mCodecCtx, codec, NULL) < 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;
mStream = stream;
2012-03-20 16:45:01 -07:00
}
catch(std::exception &e)
{
avformat_close_input(&mFormatCtx);
2012-03-20 16:45:01 -07:00
mFormatCtx = NULL;
throw;
}
2012-03-16 23:59:21 -07:00
}
void FFmpeg_Decoder::close()
2012-03-16 23:59:21 -07:00
{
if(mStream.get())
2012-03-20 16:45:01 -07:00
{
2012-12-15 22:54:54 -08:00
av_free_packet(&mStream->mPacket);
avcodec_close(mStream->mCodecCtx);
av_free(mStream->mDecodedData);
2012-03-20 16:45:01 -07:00
}
mStream.reset();
2012-03-20 16:45:01 -07:00
if(mFormatCtx)
2012-12-10 00:59:39 +01:00
{
AVIOContext* context = mFormatCtx->pb;
avformat_close_input(&mFormatCtx);
av_free(context);
2012-12-10 00:59:39 +01:00
}
2012-03-20 16:45:01 -07:00
mFormatCtx = NULL;
mDataStream.setNull();
2012-03-16 23:59:21 -07:00
}
std::string FFmpeg_Decoder::getName()
{
return mFormatCtx->filename;
}
void FFmpeg_Decoder::getInfo(int *samplerate, ChannelConfig *chans, SampleType *type)
2012-03-17 02:51:46 -07:00
{
if(!mStream.get())
2012-03-20 16:45:01 -07:00
fail("No audio stream info");
if(mStream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_U8)
2012-03-20 16:45:01 -07:00
*type = SampleType_UInt8;
else if(mStream->mCodecCtx->sample_fmt == AV_SAMPLE_FMT_S16)
2012-03-20 16:45:01 -07:00
*type = SampleType_Int16;
else
2012-03-20 18:49:52 -07:00
fail(std::string("Unsupported sample format: ")+
av_get_sample_fmt_name(mStream->mCodecCtx->sample_fmt));
2012-03-20 16:45:01 -07:00
if(mStream->mCodecCtx->channel_layout == AV_CH_LAYOUT_MONO)
2012-03-20 16:45:01 -07:00
*chans = ChannelConfig_Mono;
else if(mStream->mCodecCtx->channel_layout == AV_CH_LAYOUT_STEREO)
2012-03-20 16:45:01 -07:00
*chans = ChannelConfig_Stereo;
else if(mStream->mCodecCtx->channel_layout == AV_CH_LAYOUT_QUAD)
*chans = ChannelConfig_Quad;
else if(mStream->mCodecCtx->channel_layout == AV_CH_LAYOUT_5POINT1)
*chans = ChannelConfig_5point1;
else if(mStream->mCodecCtx->channel_layout == AV_CH_LAYOUT_7POINT1)
*chans = ChannelConfig_7point1;
else if(mStream->mCodecCtx->channel_layout == 0)
2012-03-20 16:45:01 -07:00
{
/* Unknown channel layout. Try to guess. */
if(mStream->mCodecCtx->channels == 1)
2012-03-20 16:45:01 -07:00
*chans = ChannelConfig_Mono;
else if(mStream->mCodecCtx->channels == 2)
2012-03-20 16:45:01 -07:00
*chans = ChannelConfig_Stereo;
else
{
std::stringstream sstr("Unsupported raw channel count: ");
sstr << mStream->mCodecCtx->channels;
2012-03-20 16:45:01 -07:00
fail(sstr.str());
}
}
else
{
char str[1024];
av_get_channel_layout_string(str, sizeof(str), mStream->mCodecCtx->channels,
mStream->mCodecCtx->channel_layout);
2012-03-20 16:45:01 -07:00
fail(std::string("Unsupported channel layout: ")+str);
}
*samplerate = mStream->mCodecCtx->sample_rate;
2012-03-17 02:51:46 -07:00
}
size_t FFmpeg_Decoder::read(char *buffer, size_t bytes)
2012-03-17 02:51:46 -07:00
{
if(!mStream.get())
fail("No audio stream");
2012-03-20 16:45:01 -07:00
size_t got = mStream->readAVAudioData(buffer, bytes);
mSamplesRead += got / mStream->mCodecCtx->channels /
av_get_bytes_per_sample(mStream->mCodecCtx->sample_fmt);
return got;
2012-03-17 02:51:46 -07:00
}
void FFmpeg_Decoder::readAll(std::vector<char> &output)
{
if(!mStream.get())
fail("No audio stream");
char *inbuf;
size_t got;
while((inbuf=(char*)mStream->getAVAudioData(&got)) != NULL && got > 0)
{
output.insert(output.end(), inbuf, inbuf+got);
mSamplesRead += got / mStream->mCodecCtx->channels /
av_get_bytes_per_sample(mStream->mCodecCtx->sample_fmt);
}
}
void FFmpeg_Decoder::rewind()
{
2012-03-20 16:45:01 -07:00
av_seek_frame(mFormatCtx, -1, 0, 0);
if(mStream.get())
2012-12-15 22:54:54 -08:00
av_free_packet(&mStream->mPacket);
mSamplesRead = 0;
}
size_t FFmpeg_Decoder::getSampleOffset()
{
return mSamplesRead;
}
2012-03-16 23:59:21 -07:00
FFmpeg_Decoder::FFmpeg_Decoder() : mFormatCtx(NULL), mSamplesRead(0)
2012-03-16 23:59:21 -07:00
{
2012-03-20 16:45:01 -07:00
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;
}
2012-03-16 23:59:21 -07:00
}
FFmpeg_Decoder::~FFmpeg_Decoder()
{
close();
2012-03-16 23:59:21 -07:00
}
}
#endif