/***************************************************************************
* Copyright (C) 2006-2012 by Ilya Kotov *
* forkotov02@hotmail.ru *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program 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 General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
#include <QObject>
#include <QFile>
#include "decoder_ffmpeg.h"
// callbacks
static int ffmpeg_read(void *data, uint8_t *buf, int size)
{
DecoderFFmpeg *d = (DecoderFFmpeg*)data;
return (int)d->input()->read((char*)buf, size);
}
static int64_t ffmpeg_seek(void *data, int64_t offset, int whence)
{
DecoderFFmpeg *d = (DecoderFFmpeg*)data;
int64_t absolute_pos = 0;
/*if(d->input()->isSequential())
return -1;*/
switch( whence )
{
case AVSEEK_SIZE:
return d->input()->size();
case SEEK_SET:
absolute_pos = offset;
break;
case SEEK_CUR:
absolute_pos = d->input()->pos() + offset;
break;
case SEEK_END:
absolute_pos = d->input()->size() - offset;
default:
return -1;
}
if(absolute_pos < 0 || absolute_pos > d->input()->size())
return -1;
return d->input()->seek(absolute_pos);
}
// Decoder class
#if (LIBAVCODEC_VERSION_INT >= ((53<<16)+(42<<8)+4))
DecoderFFmpeg::DecoderFFmpeg(const QString &path, QIODevice *i)
: Decoder(i)
{
m_bitrate = 0;
m_totalTime = 0;
ic = 0;
m_path = path;
m_temp_pkt.size = 0;
m_pkt.size = 0;
m_pkt.data = 0;
m_output_at = 0;
m_skipBytes = 0;
m_stream = 0;
m_decoded_frame = 0;
av_init_packet(&m_pkt);
av_init_packet(&m_temp_pkt);
}
DecoderFFmpeg::~DecoderFFmpeg()
{
m_bitrate = 0;
m_temp_pkt.size = 0;
if (ic)
avformat_close_input(&ic);
if(m_pkt.data)
av_free_packet(&m_pkt);
if(m_stream)
av_free(m_stream);
if(m_decoded_frame)
av_free(m_decoded_frame);
}
bool DecoderFFmpeg::initialize()
{
m_bitrate = 0;
m_totalTime = 0;
m_seekTime = -1;
avcodec_register_all();
avformat_network_init();
av_register_all();
AVProbeData pd;
uint8_t buf[PROBE_BUFFER_SIZE + AVPROBE_PADDING_SIZE];
pd.filename = m_path.toLocal8Bit().constData();
pd.buf_size = input()->peek((char*)buf, sizeof(buf) - AVPROBE_PADDING_SIZE);
pd.buf = buf;
if(pd.buf_size < PROBE_BUFFER_SIZE)
{
qWarning("DecoderFFmpeg: too small buffer size: %d bytes", pd.buf_size);
return false;
}
AVInputFormat *fmt = av_probe_input_format(&pd, 1);
if(!fmt)
{
qWarning("DecoderFFmpeg: usupported format");
return false;
}
qDebug("DecoderFFmpeg: detected format: %s", fmt->long_name);
qDebug("=%s=", fmt->name);
m_stream = avio_alloc_context(m_input_buf, INPUT_BUFFER_SIZE, 0, this, ffmpeg_read, NULL, ffmpeg_seek);
if(!m_stream)
{
qWarning("DecoderFFmpeg: unable to initialize I/O callbacks");
return false;
}
m_stream->seekable = !input()->isSequential();
m_stream->max_packet_size = INPUT_BUFFER_SIZE;
if(avformat_open_input(&ic, m_path.toLocal8Bit().constData(), fmt, 0) != 0)
{
qDebug("DecoderFFmpeg: av_open_input_stream() failed");
return false;
}
avformat_find_stream_info(ic, 0);
if(ic->pb)
ic->pb->eof_reached = 0;
if (input()->isSequential())
{
QMap<Qmmp::MetaData, QString> metaData;
AVDictionaryEntry *album = av_dict_get(ic->metadata,"album",0,0);
if(!album)
album = av_dict_get(ic->metadata,"WM/AlbumTitle",0,0);
AVDictionaryEntry *artist = av_dict_get(ic->metadata,"artist",0,0);
if(!artist)
artist = av_dict_get(ic->metadata,"author",0,0);
AVDictionaryEntry *comment = av_dict_get(ic->metadata,"comment",0,0);
AVDictionaryEntry *genre = av_dict_get(ic->metadata,"genre",0,0);
AVDictionaryEntry *title = av_dict_get(ic->metadata,"title",0,0);
AVDictionaryEntry *year = av_dict_get(ic->metadata,"WM/Year",0,0);
if(!year)
year = av_dict_get(ic->metadata,"year",0,0);
if(!year)
year = av_dict_get(ic->metadata,"date",0,0);
AVDictionaryEntry *track = av_dict_get(ic->metadata,"track",0,0);
if(!track)
track = av_dict_get(ic->metadata,"WM/Track",0,0);
if(!track)
track = av_dict_get(ic->metadata,"WM/TrackNumber",0,0);
if(album)
metaData.insert(Qmmp::ALBUM, QString::fromUtf8(album->value).trimmed());
if(artist)
metaData.insert(Qmmp::ARTIST, QString::fromUtf8(artist->value).trimmed());
if(comment)
metaData.insert(Qmmp::COMMENT, QString::fromUtf8(comment->value).trimmed());
if(genre)
metaData.insert(Qmmp::GENRE, QString::fromUtf8(genre->value).trimmed());
if(title)
metaData.insert(Qmmp::TITLE, QString::fromUtf8(title->value).trimmed());
if(year)
metaData.insert(Qmmp::YEAR, year->value);
if(track)
metaData.insert(Qmmp::TRACK, track->value);
metaData.insert(Qmmp::URL, m_path);
addMetaData(metaData);
}
ic->flags |= AVFMT_FLAG_GENPTS;
av_read_play(ic);
for (wma_idx = 0; wma_idx < (int)ic->nb_streams; wma_idx++)
{
c = ic->streams[wma_idx]->codec;
if (c->codec_type == AVMEDIA_TYPE_AUDIO)
break;
}
if (c->channels > 0)
c->request_channels = qMin(2, c->channels);
else
c->request_channels = 2;
av_dump_format(ic,0,0,0);
AVCodec *codec = avcodec_find_decoder(c->codec_id);
if (!codec)
{
qWarning("DecoderFFmpeg: unsupported codec for output stream");
return false;
}
if (avcodec_open2(c, codec, 0) < 0)
{
qWarning("DecoderFFmpeg: error while opening codec for output stream");
return false;
}
m_decoded_frame = avcodec_alloc_frame();
m_totalTime = input()->isSequential() ? 0 : ic->duration * 1000 / AV_TIME_BASE;
if(c->codec_id == CODEC_ID_SHORTEN) //ffmpeg bug workaround
m_totalTime = 0;
Qmmp::AudioFormat format = Qmmp::PCM_UNKNOWM;
switch(c->sample_fmt)
{
case AV_SAMPLE_FMT_U8:
format = Qmmp::PCM_S8;
break;
case AV_SAMPLE_FMT_S16:
format = Qmmp::PCM_S16LE;
break;
case AV_SAMPLE_FMT_S32:
case AV_SAMPLE_FMT_FLT:
format = Qmmp::PCM_S32LE;
break;
default:
qWarning("DecoderFFmpeg: unsupported audio format");
return false;
}
configure(c->sample_rate, c->request_channels, format);
if(ic->bit_rate)
m_bitrate = ic->bit_rate/1000;
if(c->bit_rate)
m_bitrate = c->bit_rate/1000;
qDebug("DecoderFFmpeg: initialize succes");
#ifdef Q_OS_WIN
qDebug("total time = %I64d", m_totalTime);
#else
qDebug("total time = %lld ", m_totalTime);
#endif
return true;
}
qint64 DecoderFFmpeg::totalTime()
{
return m_totalTime;
}
int DecoderFFmpeg::bitrate()
{
return m_bitrate;
}
qint64 DecoderFFmpeg::read(char *audio, qint64 maxSize)
{
m_skipBytes = 0;
if(!m_output_at)
fillBuffer();
if(!m_output_at)
return 0;
qint64 len = qMin(m_output_at, maxSize);
memcpy(audio, m_decoded_frame->extended_data[0], len);
m_output_at -= len;
memmove(m_decoded_frame->extended_data[0], m_decoded_frame->extended_data[0] + len, m_output_at);
if(c->sample_fmt == AV_SAMPLE_FMT_FLT)
{
//convert float to signed 32 bit LE
for(int i = 0; i < len >> 2; i++)
{
int32_t *out = (int32_t *)audio;
float *in = (float *) audio;
out[i] = qBound(-1.0f, in[i], +1.0f) * (double) 0x7fffffff;
}
}
return len;
}
qint64 DecoderFFmpeg::ffmpeg_decode()
{
int out_size = 0;
int got_frame = 0;
if((m_pkt.stream_index == wma_idx))
{
avcodec_get_frame_defaults(m_decoded_frame);
int l = avcodec_decode_audio4(c, m_decoded_frame, &got_frame, &m_temp_pkt);
if(got_frame)
out_size = av_samples_get_buffer_size(0, c->channels, m_decoded_frame->nb_samples,
c->sample_fmt, 1);
else
out_size = 0;
if(c->bit_rate)
m_bitrate = c->bit_rate/1000;
if(l < 0)
{
return l;
}
m_temp_pkt.data += l;
m_temp_pkt.size -= l;
}
if (!m_temp_pkt.size && m_pkt.data)
av_free_packet(&m_pkt);
return out_size;
}
void DecoderFFmpeg::seek(qint64 pos)
{
int64_t timestamp = int64_t(pos)*AV_TIME_BASE/1000;
if (ic->start_time != (qint64)AV_NOPTS_VALUE)
timestamp += ic->start_time;
m_seekTime = timestamp;
av_seek_frame(ic, -1, timestamp, AVSEEK_FLAG_BACKWARD);
avcodec_flush_buffers(c);
av_free_packet(&m_pkt);
m_temp_pkt.size = 0;
}
void DecoderFFmpeg::fillBuffer()
{
while(!m_output_at)
{
if(!m_temp_pkt.size)
{
if (av_read_frame(ic, &m_pkt) < 0)
{
m_temp_pkt.size = 0;
break;
}
m_temp_pkt.size = m_pkt.size;
m_temp_pkt.data = m_pkt.data;
if(m_pkt.stream_index != wma_idx)
{
if(m_pkt.data)
av_free_packet(&m_pkt);
m_temp_pkt.size = 0;
continue;
}
if(m_seekTime && c->codec_id == CODEC_ID_APE)
{
int64_t rescaledPts = av_rescale(m_pkt.pts,
AV_TIME_BASE * (int64_t)
ic->streams[m_pkt.stream_index]->time_base.num,
ic->streams[m_pkt.stream_index]->time_base.den);
m_skipBytes = (m_seekTime - rescaledPts) * c->sample_rate * 4 / AV_TIME_BASE;
}
else
m_skipBytes = 0;
m_seekTime = 0;
}
if(m_skipBytes > 0 && c->codec_id == CODEC_ID_APE)
{
while (m_skipBytes > 0)
{
m_output_at = ffmpeg_decode();
if(m_output_at < 0)
break;
m_skipBytes -= m_output_at;
}
if(m_skipBytes < 0)
{
qint64 size = m_output_at;
m_output_at = - m_skipBytes;
m_output_at = m_output_at/4*4;
memmove(m_decoded_frame->data[0],
(m_decoded_frame->data[0] + size - m_output_at),
m_output_at);
m_skipBytes = 0;
}
}
else
m_output_at = ffmpeg_decode();
if(m_output_at < 0)
{
m_output_at = 0;
m_temp_pkt.size = 0;
if(c->codec_id == CODEC_ID_SHORTEN || c->codec_id == CODEC_ID_TWINVQ)
{
if(m_pkt.data)
av_free_packet(&m_pkt);
m_pkt.data = 0;
break;
}
continue;
}
else if(m_output_at == 0)
{
if(c->codec_id == CODEC_ID_SHORTEN || c->codec_id == CODEC_ID_TWINVQ)
continue;
if(m_pkt.data)
av_free_packet(&m_pkt);
m_pkt.data = 0;
break;
}
}
}
#else
//legacy ffmpeg support
DecoderFFmpeg::DecoderFFmpeg(const QString &path, QIODevice *i)
: Decoder(i)
{
m_bitrate = 0;
m_skip = false;
m_totalTime = 0;
ic = 0;
m_path = path;
m_temp_pkt.size = 0;
m_pkt.size = 0;
m_pkt.data = 0;
m_output_buf = 0;
m_output_at = 0;
m_skipBytes = 0;
m_stream = 0;
av_init_packet(&m_pkt);
av_init_packet(&m_temp_pkt);
}
DecoderFFmpeg::~DecoderFFmpeg()
{
m_bitrate = 0;
m_temp_pkt.size = 0;
if (ic)
av_close_input_stream(ic);
if(m_pkt.data)
av_free_packet(&m_pkt);
if(m_output_buf)
av_free(m_output_buf);
if(m_stream)
av_free(m_stream);
}
bool DecoderFFmpeg::initialize()
{
m_bitrate = 0;
m_skip = false;
m_totalTime = 0;
m_seekTime = -1;
av_register_all();
AVProbeData pd;
uint8_t buf[PROBE_BUFFER_SIZE + AVPROBE_PADDING_SIZE];
pd.filename = m_path.toLocal8Bit().constData();
pd.buf_size = input()->peek((char*)buf, sizeof(buf) - AVPROBE_PADDING_SIZE);
pd.buf = buf;
if(pd.buf_size < PROBE_BUFFER_SIZE)
{
qWarning("DecoderFFmpeg: too small buffer size: %d bytes", pd.buf_size);
return false;
}
AVInputFormat *fmt = av_probe_input_format(&pd, 1);
if(!fmt)
{
qWarning("DecoderFFmpeg: usupported format");
return false;
}
qDebug("DecoderFFmpeg: detected format: %s", fmt->long_name);
qDebug("=%s=", fmt->name);
#if (LIBAVFORMAT_VERSION_INT >= ((52<<16)+(105<<8)+0))
m_stream = avio_alloc_context(m_input_buf, INPUT_BUFFER_SIZE, 0, this, ffmpeg_read, NULL, ffmpeg_seek);
if(!m_stream)
{
qWarning("DecoderFFmpeg: unable to initialize I/O callbacks");
return false;
}
m_stream->seekable = !input()->isSequential();
#else
m_stream = (ByteIOContext *)av_malloc(sizeof(ByteIOContext));
init_put_byte(m_stream, m_input_buf, INPUT_BUFFER_SIZE, 0, this, ffmpeg_read, NULL, ffmpeg_seek);
m_stream->is_streamed = input()->isSequential();
#endif
m_stream->max_packet_size = INPUT_BUFFER_SIZE;
AVFormatParameters ap;
memset(&ap, 0, sizeof(ap));
if(av_open_input_stream(&ic, m_stream, m_path.toLocal8Bit(), fmt, &ap) != 0)
{
qDebug("DecoderFFmpeg: av_open_input_stream() failed");
return false;
}
av_find_stream_info(ic);
if(ic->pb)
ic->pb->eof_reached = 0;
if (input()->isSequential())
{
QMap<Qmmp::MetaData, QString> metaData;
AVMetadataTag *album = av_metadata_get(ic->metadata,"album",0,0);
if(!album)
album = av_metadata_get(ic->metadata,"WM/AlbumTitle",0,0);
AVMetadataTag *artist = av_metadata_get(ic->metadata,"artist",0,0);
if(!artist)
artist = av_metadata_get(ic->metadata,"author",0,0);
AVMetadataTag *comment = av_metadata_get(ic->metadata,"comment",0,0);
AVMetadataTag *genre = av_metadata_get(ic->metadata,"genre",0,0);
AVMetadataTag *title = av_metadata_get(ic->metadata,"title",0,0);
AVMetadataTag *year = av_metadata_get(ic->metadata,"WM/Year",0,0);
if(!year)
year = av_metadata_get(ic->metadata,"year",0,0);
if(!year)
year = av_metadata_get(ic->metadata,"date",0,0);
AVMetadataTag *track = av_metadata_get(ic->metadata,"track",0,0);
if(!track)
track = av_metadata_get(ic->metadata,"WM/Track",0,0);
if(!track)
track = av_metadata_get(ic->metadata,"WM/TrackNumber",0,0);
if(album)
metaData.insert(Qmmp::ALBUM, QString::fromUtf8(album->value).trimmed());
if(artist)
metaData.insert(Qmmp::ARTIST, QString::fromUtf8(artist->value).trimmed());
if(comment)
metaData.insert(Qmmp::COMMENT, QString::fromUtf8(comment->value).trimmed());
if(genre)
metaData.insert(Qmmp::GENRE, QString::fromUtf8(genre->value).trimmed());
if(title)
metaData.insert(Qmmp::TITLE, QString::fromUtf8(title->value).trimmed());
if(year)
metaData.insert(Qmmp::YEAR, year->value);
if(track)
metaData.insert(Qmmp::TRACK, track->value);
metaData.insert(Qmmp::URL, m_path);
addMetaData(metaData);
}
ic->flags |= AVFMT_FLAG_GENPTS;
av_read_play(ic);
for (wma_idx = 0; wma_idx < (int)ic->nb_streams; wma_idx++)
{
c = ic->streams[wma_idx]->codec;
#if LIBAVCODEC_VERSION_MAJOR < 53
if (c->codec_type == CODEC_TYPE_AUDIO)
#else
if (c->codec_type == AVMEDIA_TYPE_AUDIO)
#endif
break;
}
if (c->channels > 0)
c->request_channels = qMin(2, c->channels);
else
c->request_channels = 2;
#if (LIBAVCODEC_VERSION_INT >= ((52<<16)+(101<<8)+0))
av_dump_format(ic,0,0,0);
#else
dump_format(ic,0,0,0);
#endif
AVCodec *codec = avcodec_find_decoder(c->codec_id);
if (!codec)
{
qWarning("DecoderFFmpeg: unsupported codec for output stream");
return false;
}
if (avcodec_open(c, codec) < 0)
{
qWarning("DecoderFFmpeg: error while opening codec for output stream");
return false;
}
m_totalTime = input()->isSequential() ? 0 : ic->duration * 1000 / AV_TIME_BASE;
m_output_buf = (uint8_t *)av_malloc(AVCODEC_MAX_AUDIO_FRAME_SIZE*2);
#if (LIBAVCODEC_VERSION_INT >= ((52<<16)+(20<<8)+0))
if(c->codec_id == CODEC_ID_SHORTEN) //ffmpeg bug workaround
m_totalTime = 0;
#endif
#if (LIBAVUTIL_VERSION_INT >= ((50<<16)+(38<<8)+0))
if(c->sample_fmt == AV_SAMPLE_FMT_S32)
configure(c->sample_rate, c->request_channels, Qmmp::PCM_S32LE);
else
configure(c->sample_rate, c->request_channels, Qmmp::PCM_S16LE);
#else
if(c->sample_fmt == SAMPLE_FMT_S32)
configure(c->sample_rate, c->request_channels, Qmmp::PCM_S32LE);
else
configure(c->sample_rate, c->request_channels, Qmmp::PCM_S16LE);
#endif
if(ic->bit_rate)
m_bitrate = ic->bit_rate/1000;
if(c->bit_rate)
m_bitrate = c->bit_rate/1000;
qDebug("DecoderFFmpeg: initialize succes");
return true;
}
qint64 DecoderFFmpeg::totalTime()
{
return m_totalTime;
}
int DecoderFFmpeg::bitrate()
{
return m_bitrate;
}
qint64 DecoderFFmpeg::read(char *audio, qint64 maxSize)
{
m_skipBytes = 0;
if (m_skip)
{
while(m_temp_pkt.size)
ffmpeg_decode(m_output_buf);
m_output_at = 0;
m_skip = false;
}
if(!m_output_at)
fillBuffer();
if(!m_output_at)
return 0;
qint64 len = qMin(m_output_at, maxSize);
memcpy(audio, m_output_buf, len);
m_output_at -= len;
memmove(m_output_buf, m_output_buf + len, m_output_at);
return len;
}
qint64 DecoderFFmpeg::ffmpeg_decode(uint8_t *audio)
{
int out_size = AVCODEC_MAX_AUDIO_FRAME_SIZE * 2;
if((m_pkt.stream_index == wma_idx))
{
#if (LIBAVCODEC_VERSION_INT >= ((52<<16)+(23<<8)+0))
int l = avcodec_decode_audio3(c, (int16_t *)(audio), &out_size, &m_temp_pkt);
#else
int l = avcodec_decode_audio2(c, (int16_t *)(audio), &out_size, m_temp_pkt.data, m_temp_pkt.size);
#endif
if(c->bit_rate)
m_bitrate = c->bit_rate/1000;
if(l < 0)
return l;
m_temp_pkt.data += l;
m_temp_pkt.size -= l;
}
if (!m_temp_pkt.size && m_pkt.data)
av_free_packet(&m_pkt);
return out_size;
}
void DecoderFFmpeg::seek(qint64 pos)
{
int64_t timestamp = int64_t(pos)*AV_TIME_BASE/1000;
if (ic->start_time != (qint64)AV_NOPTS_VALUE)
timestamp += ic->start_time;
m_seekTime = timestamp;
av_seek_frame(ic, -1, timestamp, AVSEEK_FLAG_BACKWARD);
if(m_pkt.size)
m_skip = true;
}
void DecoderFFmpeg::fillBuffer()
{
while(!m_output_at)
{
if(!m_temp_pkt.size)
{
if (av_read_frame(ic, &m_pkt) < 0)
{
m_temp_pkt.size = 0;
break;
}
m_temp_pkt.size = m_pkt.size;
m_temp_pkt.data = m_pkt.data;
if(m_pkt.stream_index != wma_idx)
{
if(m_pkt.data)
av_free_packet(&m_pkt);
m_temp_pkt.size = 0;
continue;
}
if(m_seekTime && c->codec_id == CODEC_ID_APE)
{
int64_t rescaledPts = av_rescale(m_pkt.pts,
AV_TIME_BASE * (int64_t)
ic->streams[m_pkt.stream_index]->time_base.num,
ic->streams[m_pkt.stream_index]->time_base.den);
m_skipBytes = (m_seekTime - rescaledPts) * c->sample_rate * 4 / AV_TIME_BASE;
}
else
m_skipBytes = 0;
m_seekTime = 0;
}
if(m_skipBytes > 0 && c->codec_id == CODEC_ID_APE)
{
while (m_skipBytes > 0)
{
m_output_at = ffmpeg_decode(m_output_buf);
if(m_output_at < 0)
break;
m_skipBytes -= m_output_at;
}
if(m_skipBytes < 0)
{
qint64 size = m_output_at;
m_output_at = - m_skipBytes;
m_output_at = m_output_at/4*4;
memmove(m_output_buf, (m_output_buf + size - m_output_at), m_output_at);
m_skipBytes = 0;
}
}
else
m_output_at = ffmpeg_decode(m_output_buf);
if(m_output_at < 0)
{
m_output_at = 0;
m_temp_pkt.size = 0;
if(c->codec_id == CODEC_ID_SHORTEN)
{
if(m_pkt.data)
av_free_packet(&m_pkt);
m_pkt.data = 0;
break;
}
continue;
}
else if(m_output_at == 0)
{
if(c->codec_id == CODEC_ID_SHORTEN)
continue;
if(m_pkt.data)
av_free_packet(&m_pkt);
m_pkt.data = 0;
break;
}
}
}
#endif