You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
416 lines
11 KiB
416 lines
11 KiB
10 years ago
|
/* aKode: FFMPEG Decoder
|
||
|
|
||
|
Copyright (C) 2005 Allan Sandfeld Jensen <kde@carewolf.com>
|
||
|
|
||
|
This library is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU Library General Public
|
||
|
License as published by the Free Software Foundation; either
|
||
|
version 2 of the License, or (at your option) any later version.
|
||
|
|
||
|
This library is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
Library General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU Library General Public License
|
||
|
along with this library; see the file COPYING.LIB. If not, write to
|
||
|
the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
|
||
|
Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
|
||
|
#include "akodelib.h"
|
||
|
// #ifdef HAVE_FFMPEG
|
||
|
|
||
|
#include "file.h"
|
||
|
#include "audioframe.h"
|
||
|
#include "decoder.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <ffmpeg/avcodec.h>
|
||
|
#include <ffmpeg/avformat.h>
|
||
|
#include <ffmpeg/avio.h>
|
||
|
|
||
|
#include "ffmpeg_decoder.h"
|
||
|
#include <iostream>
|
||
|
|
||
|
// FFMPEG callbacks
|
||
|
extern "C" {
|
||
|
static int akode_read(void* opaque, unsigned char *buf, int size)
|
||
|
{
|
||
|
aKode::File *file = (aKode::File*)opaque;
|
||
|
return file->read((char*)buf, size);
|
||
|
}
|
||
|
static int akode_write(void* opaque, unsigned char *buf, int size)
|
||
|
{
|
||
|
aKode::File *file = (aKode::File*)opaque;
|
||
|
return file->write((char*)buf, size);
|
||
|
}
|
||
|
static offset_t akode_seek(void* opaque, offset_t pos, int whence)
|
||
|
{
|
||
|
aKode::File *file = (aKode::File*)opaque;
|
||
|
return file->seek(pos, whence);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
namespace aKode {
|
||
|
|
||
|
|
||
|
bool FFMPEGDecoderPlugin::canDecode(File* /*src*/) {
|
||
|
// ### FIXME
|
||
|
return true;
|
||
|
}
|
||
|
/*
|
||
|
void FFMPEGDecoderPlugin::initializeFFMPEG() {
|
||
|
av_register_all();
|
||
|
}*/
|
||
|
|
||
|
extern "C" { FFMPEGDecoderPlugin ffmpeg_decoder; }
|
||
|
|
||
|
#define FILE_BUFFER_SIZE 8192
|
||
|
|
||
|
struct FFMPEGDecoder::private_data
|
||
|
{
|
||
|
private_data() : audioStream(-1), videoStream(-1), packetSize(0), position(0),
|
||
|
eof(false), error(false), initialized(false), retries(0) {};
|
||
|
|
||
|
AVFormatContext* ic;
|
||
|
AVCodec* codec;
|
||
|
AVInputFormat *fmt;
|
||
|
ByteIOContext stream;
|
||
|
|
||
|
int audioStream;
|
||
|
int videoStream;
|
||
|
|
||
|
AVPacket packet;
|
||
|
uint8_t* packetData;
|
||
|
int packetSize;
|
||
|
|
||
|
File *src;
|
||
|
AudioConfiguration config;
|
||
|
|
||
|
long position;
|
||
|
|
||
|
bool eof, error;
|
||
|
bool initialized;
|
||
|
int retries;
|
||
|
|
||
|
unsigned char file_buffer[FILE_BUFFER_SIZE];
|
||
|
char buffer[AVCODEC_MAX_AUDIO_FRAME_SIZE];
|
||
|
int buffer_size;
|
||
|
};
|
||
|
|
||
|
FFMPEGDecoder::FFMPEGDecoder(File *src) {
|
||
|
d = new private_data;
|
||
|
av_register_all();
|
||
|
|
||
|
d->src = src;
|
||
|
}
|
||
|
|
||
|
FFMPEGDecoder::~FFMPEGDecoder() {
|
||
|
closeFile();
|
||
|
delete d;
|
||
|
}
|
||
|
|
||
|
|
||
|
static bool setAudioConfiguration(AudioConfiguration *config, AVCodecContext *codec_context)
|
||
|
{
|
||
|
config->sample_rate = codec_context->sample_rate;
|
||
|
config->channels = codec_context->channels;
|
||
|
// I do not know FFMPEGs surround channel ordering
|
||
|
if (config->channels > 2) return false;
|
||
|
config->channel_config = MonoStereo;
|
||
|
// avcodec.h says sample_fmt is not used. I guess it means it is always S16
|
||
|
switch(codec_context->sample_fmt) {
|
||
|
case SAMPLE_FMT_U8:
|
||
|
config->sample_width = 8; // beware unsigned!
|
||
|
break;
|
||
|
case SAMPLE_FMT_S16:
|
||
|
config->sample_width = 16;
|
||
|
break;
|
||
|
/* disabled because I don't know byte ordering
|
||
|
case SAMPLE_FMT_S24:
|
||
|
config->sample_width = 24;
|
||
|
break;
|
||
|
*/
|
||
|
case SAMPLE_FMT_S32:
|
||
|
config->sample_width = 32;
|
||
|
break;
|
||
|
case SAMPLE_FMT_FLT:
|
||
|
config->sample_width = -32;
|
||
|
break;
|
||
|
default:
|
||
|
return false;;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
bool FFMPEGDecoder::openFile() {
|
||
|
d->src->openRO();
|
||
|
d->src->fadvise();
|
||
|
|
||
|
// The following duplicates what av_open_input_file would normally do
|
||
|
|
||
|
// url_fdopen
|
||
|
init_put_byte(&d->stream, d->file_buffer, FILE_BUFFER_SIZE, 0, d->src, akode_read, akode_write, akode_seek);
|
||
|
d->stream.is_streamed = !d->src->seekable();
|
||
|
d->stream.max_packet_size = FILE_BUFFER_SIZE;
|
||
|
|
||
|
{
|
||
|
// 2048 is PROBE_BUF_SIZE from libavformat/utils.c
|
||
|
AVProbeData pd;
|
||
|
uint8_t buf[2048];
|
||
|
pd.filename = d->src->filename;
|
||
|
pd.buf = buf;
|
||
|
pd.buf_size = 0;
|
||
|
pd.buf_size = get_buffer(&d->stream, buf, 2048);
|
||
|
d->fmt = av_probe_input_format(&pd, 1);
|
||
|
// Seek back to 0
|
||
|
// copied from url_fseek
|
||
|
long offset1 = 0 - (d->stream.pos - (d->stream.buf_end - d->stream.buffer));
|
||
|
if (offset1 >= 0 && offset1 <= (d->stream.buf_end - d->stream.buffer)) {
|
||
|
/* can do the seek inside the buffer */
|
||
|
d->stream.buf_ptr = d->stream.buffer + offset1;
|
||
|
} else {
|
||
|
if (!d->src->seek(0)) {
|
||
|
d->src->close();
|
||
|
return false;
|
||
|
} else {
|
||
|
d->stream.pos = 0;
|
||
|
d->stream.buf_ptr = d->file_buffer;
|
||
|
d->stream.buf_end = d->file_buffer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (!d->fmt) {
|
||
|
std::cerr << "akode: FFMPEG: Format not found\n";
|
||
|
closeFile();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (av_open_input_stream(&d->ic, &d->stream, d->src->filename, d->fmt, 0) != 0)
|
||
|
{
|
||
|
closeFile();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
av_find_stream_info( d->ic );
|
||
|
|
||
|
// Find the first a/v streams
|
||
|
d->audioStream = -1;
|
||
|
d->videoStream = -1;
|
||
|
for (int i = 0; i < d->ic->nb_streams; i++) {
|
||
|
if (d->ic->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO)
|
||
|
d->audioStream = i;
|
||
|
else
|
||
|
if (d->ic->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
|
||
|
d->videoStream = i;
|
||
|
}
|
||
|
if (d->audioStream == -1)
|
||
|
{
|
||
|
std::cerr << "akode: FFMPEG: Audio stream not found\n";
|
||
|
// for now require an audio stream
|
||
|
closeFile();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Set config
|
||
|
if (!setAudioConfiguration(&d->config, d->ic->streams[d->audioStream]->codec))
|
||
|
{
|
||
|
closeFile();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
d->codec = avcodec_find_decoder(d->ic->streams[d->audioStream]->codec->codec_id);
|
||
|
if (!d->codec) {
|
||
|
std::cerr << "akode: FFMPEG: Codec not found\n";
|
||
|
closeFile();
|
||
|
return false;
|
||
|
}
|
||
|
avcodec_open( d->ic->streams[d->audioStream]->codec, d->codec );
|
||
|
|
||
|
double ffpos = (double)d->ic->streams[d->audioStream]->start_time / (double)AV_TIME_BASE;
|
||
|
d->position = (long)(ffpos * d->config.sample_rate);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void FFMPEGDecoder::closeFile() {
|
||
|
if( d->packetSize > 0 ) {
|
||
|
av_free_packet( &d->packet );
|
||
|
d->packetSize = 0;
|
||
|
}
|
||
|
|
||
|
if( d->codec ) {
|
||
|
avcodec_close( d->ic->streams[d->audioStream]->codec );
|
||
|
d->codec = 0;
|
||
|
}
|
||
|
if( d->ic ) {
|
||
|
// make sure av_close_input_file doesn't actually close the file
|
||
|
d->ic->iformat->flags = d->ic->iformat->flags | AVFMT_NOFILE;
|
||
|
av_close_input_file( d->ic );
|
||
|
d->ic = 0;
|
||
|
}
|
||
|
if (d->src) {
|
||
|
d->src->close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::readPacket() {
|
||
|
do {
|
||
|
av_init_packet(&d->packet);
|
||
|
if ( av_read_frame(d->ic, &d->packet) < 0 ) {
|
||
|
av_free_packet( &d->packet );
|
||
|
d->packetSize = 0;
|
||
|
d->packetData = 0;
|
||
|
return false;
|
||
|
}
|
||
|
if (d->packet.stream_index == d->audioStream) {
|
||
|
d->packetSize = d->packet.size;
|
||
|
d->packetData = d->packet.data;
|
||
|
return true;
|
||
|
}
|
||
|
av_free_packet(&d->packet);
|
||
|
} while (true);
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
template<typename T>
|
||
|
static long demux(FFMPEGDecoder::private_data* d, AudioFrame* frame) {
|
||
|
int channels = d->config.channels;
|
||
|
long length = d->buffer_size/(channels*sizeof(T));
|
||
|
frame->reserveSpace(&d->config, length);
|
||
|
|
||
|
T offset = 0;
|
||
|
if (frame->sample_width == 8) offset = -128; // convert unsigned to signed
|
||
|
|
||
|
// Demux into frame
|
||
|
T* buffer = (T*)d->buffer;
|
||
|
T** data = (T**)frame->data;
|
||
|
for(int i=0; i<length; i++)
|
||
|
for(int j=0; j<channels; j++)
|
||
|
data[j][i] = buffer[i*channels+j] + offset;
|
||
|
return length;
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::readFrame(AudioFrame* frame)
|
||
|
{
|
||
|
if (!d->initialized) {
|
||
|
if (!openFile()) {
|
||
|
d->error = true;
|
||
|
return false;
|
||
|
}
|
||
|
d->initialized = true;
|
||
|
}
|
||
|
|
||
|
if( d->packetSize <= 0 )
|
||
|
if (!readPacket()) {
|
||
|
std::cerr << "akode: FFMPEG: EOF guessed\n";
|
||
|
d->eof = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
assert(d->packet.stream_index == d->audioStream);
|
||
|
|
||
|
retry:
|
||
|
int len = avcodec_decode_audio( d->ic->streams[d->audioStream]->codec,
|
||
|
(short*)d->buffer, &d->buffer_size,
|
||
|
d->packetData, d->packetSize );
|
||
|
|
||
|
if (len <= 0) {
|
||
|
d->retries++;
|
||
|
if (d->retries > 8) {
|
||
|
std::cerr << "akode: FFMPEG: Decoding failure\n";
|
||
|
d->error = true;
|
||
|
return false;
|
||
|
}
|
||
|
goto retry;
|
||
|
} else
|
||
|
d->retries = 0;
|
||
|
|
||
|
d->packetSize -= len;
|
||
|
d->packetData += len;
|
||
|
long length = 0;
|
||
|
switch (d->config.sample_width) {
|
||
|
case 8:
|
||
|
length = demux<int8_t>(d,frame);
|
||
|
break;
|
||
|
case 16:
|
||
|
length = demux<int16_t>(d,frame);
|
||
|
break;
|
||
|
case 32:
|
||
|
length = demux<int32_t>(d,frame);
|
||
|
break;
|
||
|
case -32:
|
||
|
length = demux<float>(d,frame);
|
||
|
break;
|
||
|
default:
|
||
|
assert(false);
|
||
|
}
|
||
|
if (length == 0) return readFrame(frame);
|
||
|
std::cout << "akode: FFMPEG: Frame length: " << length << "\n";
|
||
|
|
||
|
if( d->packetSize <= 0 )
|
||
|
av_free_packet( &d->packet );
|
||
|
|
||
|
frame->pos = (d->position*1000)/d->config.sample_rate;
|
||
|
d->position += length;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
long FFMPEGDecoder::length() {
|
||
|
if (!d->initialized) return -1;
|
||
|
// ### Returns only the length of the first stream
|
||
|
double ffmpeglen = (double)d->ic->streams[d->audioStream]->duration / (double)AV_TIME_BASE;
|
||
|
return (long)(ffmpeglen*1000.0);
|
||
|
}
|
||
|
|
||
|
long FFMPEGDecoder::position() {
|
||
|
if (!d->initialized) return -1;
|
||
|
// ### Replace with FFMPEG native call
|
||
|
return (d->position*1000)/d->config.sample_rate;
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::eof() {
|
||
|
return d->eof;
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::error() {
|
||
|
return d->error;
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::seekable() {
|
||
|
return d->src->seekable();
|
||
|
}
|
||
|
|
||
|
bool FFMPEGDecoder::seek(long pos) {
|
||
|
if (!d->initialized) return false;
|
||
|
AVRational time_base = d->ic->streams[d->audioStream]->time_base;
|
||
|
std::cout<< "time base is " << time_base.num << "/" << time_base.den << "\n";
|
||
|
long ffpos = 0;
|
||
|
{
|
||
|
int div = (pos / (time_base.num*1000)) * time_base.den;
|
||
|
int rem = (pos % (time_base.num*1000)) * time_base.den;
|
||
|
ffpos = div + rem / (time_base.num*1000);
|
||
|
}
|
||
|
std::cout<< "seeking to " << pos << "ms\n";
|
||
|
// long ffpos = (long)((pos/1000.0)*AV_TIME_BASE);
|
||
|
std::cout<< "seeking to " << ffpos << "pos\n";
|
||
|
bool res = av_seek_frame(d->ic, d->audioStream, ffpos, 0);
|
||
|
if (res < 0) return false;
|
||
|
else {
|
||
|
d->position = (pos * d->config.sample_rate)/1000;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const AudioConfiguration* FFMPEGDecoder::audioConfiguration() {
|
||
|
if (!d->initialized) return 0;
|
||
|
return &d->config;
|
||
|
}
|
||
|
|
||
|
} // namespace
|
||
|
|
||
|
// #endif // HAVE_FFMPEG
|