353 lines
12 KiB
C++
353 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2005-2008 Team XBMC
|
|
* http://www.xbmc.org
|
|
*
|
|
* 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, 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 XBMC; see the file COPYING. If not, write to
|
|
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
*
|
|
*/
|
|
|
|
#include "OMXAudioCodecOMX.h"
|
|
#ifdef TARGET_LINUX
|
|
#include "XMemUtils.h"
|
|
#endif
|
|
#include "utils/log.h"
|
|
|
|
#include "utils/PCMRemap.h"
|
|
|
|
// the size of the audio_render output port buffers
|
|
#define AUDIO_DECODE_OUTPUT_BUFFER (32*1024)
|
|
static const char rounded_up_channels_shift[] = {0,0,1,2,2,3,3,3,3};
|
|
|
|
COMXAudioCodecOMX::COMXAudioCodecOMX()
|
|
{
|
|
m_pBufferOutput = NULL;
|
|
m_iBufferOutputAlloced = 0;
|
|
m_iBufferOutputUsed = 0;
|
|
|
|
m_pCodecContext = NULL;
|
|
m_pConvert = NULL;
|
|
m_bOpenedCodec = false;
|
|
|
|
m_channels = 0;
|
|
m_pFrame1 = NULL;
|
|
m_frameSize = 0;
|
|
m_bGotFrame = false;
|
|
m_bNoConcatenate = false;
|
|
m_iSampleFormat = AV_SAMPLE_FMT_NONE;
|
|
m_desiredSampleFormat = AV_SAMPLE_FMT_NONE;
|
|
}
|
|
|
|
COMXAudioCodecOMX::~COMXAudioCodecOMX()
|
|
{
|
|
m_dllAvUtil.av_free(m_pBufferOutput);
|
|
m_pBufferOutput = NULL;
|
|
m_iBufferOutputAlloced = 0;
|
|
m_iBufferOutputUsed = 0;
|
|
Dispose();
|
|
}
|
|
|
|
bool COMXAudioCodecOMX::Open(COMXStreamInfo &hints, enum PCMLayout layout)
|
|
{
|
|
AVCodec* pCodec;
|
|
m_bOpenedCodec = false;
|
|
|
|
if (!m_dllAvUtil.Load() || !m_dllAvCodec.Load() || !m_dllSwResample.Load())
|
|
return false;
|
|
|
|
m_dllAvCodec.avcodec_register_all();
|
|
|
|
pCodec = m_dllAvCodec.avcodec_find_decoder(hints.codec);
|
|
if (!pCodec)
|
|
{
|
|
CLog::Log(LOGDEBUG,"COMXAudioCodecOMX::Open() Unable to find codec %d", hints.codec);
|
|
return false;
|
|
}
|
|
|
|
m_bFirstFrame = true;
|
|
m_pCodecContext = m_dllAvCodec.avcodec_alloc_context3(pCodec);
|
|
m_pCodecContext->debug_mv = 0;
|
|
m_pCodecContext->debug = 0;
|
|
m_pCodecContext->workaround_bugs = 1;
|
|
|
|
if (pCodec->capabilities & CODEC_CAP_TRUNCATED)
|
|
m_pCodecContext->flags |= CODEC_FLAG_TRUNCATED;
|
|
|
|
m_channels = 0;
|
|
m_pCodecContext->channels = hints.channels;
|
|
m_pCodecContext->sample_rate = hints.samplerate;
|
|
m_pCodecContext->block_align = hints.blockalign;
|
|
m_pCodecContext->bit_rate = hints.bitrate;
|
|
m_pCodecContext->bits_per_coded_sample = hints.bitspersample;
|
|
if (hints.codec == AV_CODEC_ID_TRUEHD)
|
|
{
|
|
if (layout == PCM_LAYOUT_2_0)
|
|
{
|
|
m_pCodecContext->request_channel_layout = AV_CH_LAYOUT_STEREO;
|
|
m_pCodecContext->channels = 2;
|
|
m_pCodecContext->channel_layout = m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels);
|
|
}
|
|
else if (layout <= PCM_LAYOUT_5_1)
|
|
{
|
|
m_pCodecContext->request_channel_layout = AV_CH_LAYOUT_5POINT1;
|
|
m_pCodecContext->channels = 6;
|
|
m_pCodecContext->channel_layout = m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels);
|
|
}
|
|
}
|
|
if (m_pCodecContext->request_channel_layout)
|
|
CLog::Log(LOGNOTICE,"COMXAudioCodecOMX::Open() Requesting channel layout of %x", (unsigned)m_pCodecContext->request_channel_layout);
|
|
|
|
// vorbis has variable sized planar output, so skip concatenation
|
|
if (hints.codec == AV_CODEC_ID_VORBIS)
|
|
m_bNoConcatenate = true;
|
|
|
|
if(m_pCodecContext->bits_per_coded_sample == 0)
|
|
m_pCodecContext->bits_per_coded_sample = 16;
|
|
|
|
if( hints.extradata && hints.extrasize > 0 )
|
|
{
|
|
m_pCodecContext->extradata_size = hints.extrasize;
|
|
m_pCodecContext->extradata = (uint8_t*)m_dllAvUtil.av_mallocz(hints.extrasize + FF_INPUT_BUFFER_PADDING_SIZE);
|
|
memcpy(m_pCodecContext->extradata, hints.extradata, hints.extrasize);
|
|
}
|
|
|
|
if (m_dllAvCodec.avcodec_open2(m_pCodecContext, pCodec, NULL) < 0)
|
|
{
|
|
CLog::Log(LOGDEBUG,"COMXAudioCodecOMX::Open() Unable to open codec");
|
|
Dispose();
|
|
return false;
|
|
}
|
|
|
|
m_pFrame1 = m_dllAvCodec.avcodec_alloc_frame();
|
|
m_bOpenedCodec = true;
|
|
m_iSampleFormat = AV_SAMPLE_FMT_NONE;
|
|
m_desiredSampleFormat = m_pCodecContext->sample_fmt == AV_SAMPLE_FMT_S16 ? AV_SAMPLE_FMT_S16 : AV_SAMPLE_FMT_FLTP;
|
|
return true;
|
|
}
|
|
|
|
void COMXAudioCodecOMX::Dispose()
|
|
{
|
|
if (m_pFrame1) m_dllAvUtil.av_free(m_pFrame1);
|
|
m_pFrame1 = NULL;
|
|
|
|
if (m_pConvert)
|
|
m_dllSwResample.swr_free(&m_pConvert);
|
|
|
|
if (m_pCodecContext)
|
|
{
|
|
if (m_pCodecContext->extradata) m_dllAvUtil.av_free(m_pCodecContext->extradata);
|
|
m_pCodecContext->extradata = NULL;
|
|
if (m_bOpenedCodec) m_dllAvCodec.avcodec_close(m_pCodecContext);
|
|
m_bOpenedCodec = false;
|
|
m_dllAvUtil.av_free(m_pCodecContext);
|
|
m_pCodecContext = NULL;
|
|
}
|
|
|
|
m_dllAvCodec.Unload();
|
|
m_dllAvUtil.Unload();
|
|
m_dllSwResample.Unload();
|
|
|
|
m_bGotFrame = false;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::Decode(BYTE* pData, int iSize, double dts, double pts)
|
|
{
|
|
int iBytesUsed, got_frame;
|
|
if (!m_pCodecContext) return -1;
|
|
|
|
AVPacket avpkt;
|
|
m_bGotFrame = false;
|
|
m_dllAvCodec.av_init_packet(&avpkt);
|
|
avpkt.data = pData;
|
|
avpkt.size = iSize;
|
|
iBytesUsed = m_dllAvCodec.avcodec_decode_audio4( m_pCodecContext
|
|
, m_pFrame1
|
|
, &got_frame
|
|
, &avpkt);
|
|
if (iBytesUsed < 0 || !got_frame)
|
|
{
|
|
return iBytesUsed;
|
|
}
|
|
/* some codecs will attempt to consume more data than what we gave */
|
|
if (iBytesUsed > iSize)
|
|
{
|
|
CLog::Log(LOGWARNING, "COMXAudioCodecOMX::Decode - decoder attempted to consume more data than given");
|
|
iBytesUsed = iSize;
|
|
}
|
|
|
|
if (m_bFirstFrame)
|
|
{
|
|
CLog::Log(LOGDEBUG, "COMXAudioCodecOMX::Decode(%p,%d) format=%d(%d) chan=%d samples=%d size=%d data=%p,%p,%p,%p,%p,%p,%p,%p",
|
|
pData, iSize, m_pCodecContext->sample_fmt, m_desiredSampleFormat, m_pCodecContext->channels, m_pFrame1->nb_samples,
|
|
m_pFrame1->linesize[0],
|
|
m_pFrame1->data[0], m_pFrame1->data[1], m_pFrame1->data[2], m_pFrame1->data[3], m_pFrame1->data[4], m_pFrame1->data[5], m_pFrame1->data[6], m_pFrame1->data[7]
|
|
);
|
|
}
|
|
|
|
m_bGotFrame = true;
|
|
if (!m_iBufferOutputUsed)
|
|
{
|
|
m_dts = dts;
|
|
m_pts = pts;
|
|
}
|
|
return iBytesUsed;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::GetData(BYTE** dst, double &dts, double &pts)
|
|
{
|
|
if (!m_bGotFrame)
|
|
return 0;
|
|
int inLineSize, outLineSize;
|
|
/* input audio is aligned */
|
|
int inputSize = m_dllAvUtil.av_samples_get_buffer_size(&inLineSize, m_pCodecContext->channels, m_pFrame1->nb_samples, m_pCodecContext->sample_fmt, 0);
|
|
/* output audio will be packed */
|
|
int outputSize = m_dllAvUtil.av_samples_get_buffer_size(&outLineSize, m_pCodecContext->channels, m_pFrame1->nb_samples, m_desiredSampleFormat, 1);
|
|
|
|
if (m_iBufferOutputAlloced < m_iBufferOutputUsed + outputSize)
|
|
{
|
|
m_pBufferOutput = (BYTE*)m_dllAvUtil.av_realloc(m_pBufferOutput, m_iBufferOutputUsed + outputSize + FF_INPUT_BUFFER_PADDING_SIZE);
|
|
m_iBufferOutputAlloced = m_iBufferOutputUsed + outputSize;
|
|
}
|
|
*dst = m_pBufferOutput;
|
|
|
|
/* need to convert format */
|
|
if(m_pCodecContext->sample_fmt != m_desiredSampleFormat)
|
|
{
|
|
if(m_pConvert && (m_pCodecContext->sample_fmt != m_iSampleFormat || m_channels != m_pCodecContext->channels))
|
|
{
|
|
m_dllSwResample.swr_free(&m_pConvert);
|
|
m_channels = m_pCodecContext->channels;
|
|
}
|
|
|
|
if(!m_pConvert)
|
|
{
|
|
m_iSampleFormat = m_pCodecContext->sample_fmt;
|
|
m_pConvert = m_dllSwResample.swr_alloc_set_opts(NULL,
|
|
m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels),
|
|
m_desiredSampleFormat, m_pCodecContext->sample_rate,
|
|
m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels),
|
|
m_pCodecContext->sample_fmt, m_pCodecContext->sample_rate,
|
|
0, NULL);
|
|
|
|
if(!m_pConvert || m_dllSwResample.swr_init(m_pConvert) < 0)
|
|
{
|
|
CLog::Log(LOGERROR, "COMXAudioCodecOMX::Decode - Unable to initialise convert format %d to %d", m_pCodecContext->sample_fmt, m_desiredSampleFormat);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* use unaligned flag to keep output packed */
|
|
uint8_t *out_planes[m_pCodecContext->channels];
|
|
if(m_dllAvUtil.av_samples_fill_arrays(out_planes, NULL, m_pBufferOutput + m_iBufferOutputUsed, m_pCodecContext->channels, m_pFrame1->nb_samples, m_desiredSampleFormat, 1) < 0 ||
|
|
m_dllSwResample.swr_convert(m_pConvert, out_planes, m_pFrame1->nb_samples, (const uint8_t **)m_pFrame1->data, m_pFrame1->nb_samples) < 0)
|
|
{
|
|
CLog::Log(LOGERROR, "COMXAudioCodecOMX::Decode - Unable to convert format %d to %d", (int)m_pCodecContext->sample_fmt, m_desiredSampleFormat);
|
|
outputSize = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* copy to a contiguous buffer */
|
|
uint8_t *out_planes[m_pCodecContext->channels];
|
|
if (m_dllAvUtil.av_samples_fill_arrays(out_planes, NULL, m_pBufferOutput + m_iBufferOutputUsed, m_pCodecContext->channels, m_pFrame1->nb_samples, m_desiredSampleFormat, 1) < 0 ||
|
|
m_dllAvUtil.av_samples_copy(out_planes, m_pFrame1->data, 0, 0, m_pFrame1->nb_samples, m_pCodecContext->channels, m_desiredSampleFormat) < 0 )
|
|
{
|
|
outputSize = 0;
|
|
}
|
|
}
|
|
int desired_size = AUDIO_DECODE_OUTPUT_BUFFER * (m_pCodecContext->channels * GetBitsPerSample()) >> (rounded_up_channels_shift[m_pCodecContext->channels] + 4);
|
|
|
|
if (m_bFirstFrame)
|
|
{
|
|
CLog::Log(LOGDEBUG, "COMXAudioCodecOMX::GetData size=%d/%d line=%d/%d buf=%p, desired=%d", inputSize, outputSize, inLineSize, outLineSize, *dst, desired_size);
|
|
m_bFirstFrame = false;
|
|
}
|
|
m_iBufferOutputUsed += outputSize;
|
|
|
|
if (!m_bNoConcatenate && m_pCodecContext->sample_fmt == AV_SAMPLE_FMT_FLTP && m_frameSize && (int)m_frameSize != outputSize)
|
|
CLog::Log(LOGERROR, "COMXAudioCodecOMX::GetData Unexpected change of size (%d->%d)", m_frameSize, outputSize);
|
|
m_frameSize = outputSize;
|
|
|
|
// if next buffer submitted won't fit then flush it out
|
|
if (m_iBufferOutputUsed + outputSize > desired_size || m_bNoConcatenate)
|
|
{
|
|
int ret = m_iBufferOutputUsed;
|
|
m_bGotFrame = false;
|
|
m_iBufferOutputUsed = 0;
|
|
dts = m_dts;
|
|
pts = m_pts;
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void COMXAudioCodecOMX::Reset()
|
|
{
|
|
if (m_pCodecContext) m_dllAvCodec.avcodec_flush_buffers(m_pCodecContext);
|
|
m_bGotFrame = false;
|
|
m_iBufferOutputUsed = 0;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::GetChannels()
|
|
{
|
|
if (!m_pCodecContext)
|
|
return 0;
|
|
return m_pCodecContext->channels;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::GetSampleRate()
|
|
{
|
|
if (!m_pCodecContext)
|
|
return 0;
|
|
return m_pCodecContext->sample_rate;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::GetBitsPerSample()
|
|
{
|
|
if (!m_pCodecContext)
|
|
return 0;
|
|
return m_pCodecContext->sample_fmt == AV_SAMPLE_FMT_S16 ? 16 : 32;
|
|
}
|
|
|
|
int COMXAudioCodecOMX::GetBitRate()
|
|
{
|
|
if (!m_pCodecContext)
|
|
return 0;
|
|
return m_pCodecContext->bit_rate;
|
|
}
|
|
|
|
static unsigned count_bits(int64_t value)
|
|
{
|
|
unsigned bits = 0;
|
|
for(;value;++bits)
|
|
value &= value - 1;
|
|
return bits;
|
|
}
|
|
|
|
uint64_t COMXAudioCodecOMX::GetChannelMap()
|
|
{
|
|
uint64_t layout;
|
|
int bits = count_bits(m_pCodecContext->channel_layout);
|
|
if (bits == m_pCodecContext->channels)
|
|
layout = m_pCodecContext->channel_layout;
|
|
else
|
|
{
|
|
CLog::Log(LOGINFO, "COMXAudioCodecOMX::GetChannelMap - FFmpeg reported %d channels, but the layout contains %d ignoring", m_pCodecContext->channels, bits);
|
|
layout = m_dllAvUtil.av_get_default_channel_layout(m_pCodecContext->channels);
|
|
}
|
|
|
|
|
|
return layout;
|
|
}
|