diff options
Diffstat (limited to 'xbmc/cores/AudioEngine/Encoders')
-rw-r--r-- | xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp | 322 | ||||
-rw-r--r-- | xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h | 55 |
2 files changed, 377 insertions, 0 deletions
diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp new file mode 100644 index 0000000..09bb26a --- /dev/null +++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.cpp @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#define AC3_ENCODE_BITRATE 640000 +#define DTS_ENCODE_BITRATE 1411200 + +#include "cores/AudioEngine/Encoders/AEEncoderFFmpeg.h" +#include "cores/AudioEngine/Utils/AEUtil.h" +#include "ServiceBroker.h" +#include "utils/log.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include <string.h> +#include <cassert> + +CAEEncoderFFmpeg::CAEEncoderFFmpeg() : m_CodecCtx(NULL), m_SwrCtx(NULL) +{ +} + +CAEEncoderFFmpeg::~CAEEncoderFFmpeg() +{ + Reset(); + swr_free(&m_SwrCtx); + avcodec_free_context(&m_CodecCtx); +} + +bool CAEEncoderFFmpeg::IsCompatible(const AEAudioFormat& format) +{ + if (!m_CodecCtx) + return false; + + bool match = ( + format.m_dataFormat == m_CurrentFormat.m_dataFormat && + format.m_sampleRate == m_CurrentFormat.m_sampleRate + ); + + if (match) + { + CAEChannelInfo layout; + BuildChannelLayout(AV_CH_LAYOUT_5POINT1_BACK, layout); /* hard coded for AC3 & DTS currently */ + match = (m_CurrentFormat.m_channelLayout == layout); + } + + return match; +} + +unsigned int CAEEncoderFFmpeg::BuildChannelLayout(const int64_t ffmap, CAEChannelInfo& layout) +{ + /* build the channel layout and count the channels */ + layout.Reset(); + if (ffmap & AV_CH_FRONT_LEFT ) layout += AE_CH_FL ; + if (ffmap & AV_CH_FRONT_RIGHT ) layout += AE_CH_FR ; + if (ffmap & AV_CH_FRONT_CENTER ) layout += AE_CH_FC ; + if (ffmap & AV_CH_LOW_FREQUENCY ) layout += AE_CH_LFE ; + if (ffmap & AV_CH_BACK_LEFT ) layout += AE_CH_BL ; + if (ffmap & AV_CH_BACK_RIGHT ) layout += AE_CH_BR ; + if (ffmap & AV_CH_FRONT_LEFT_OF_CENTER ) layout += AE_CH_FLOC; + if (ffmap & AV_CH_FRONT_RIGHT_OF_CENTER) layout += AE_CH_FROC; + if (ffmap & AV_CH_BACK_CENTER ) layout += AE_CH_BC ; + if (ffmap & AV_CH_SIDE_LEFT ) layout += AE_CH_SL ; + if (ffmap & AV_CH_SIDE_RIGHT ) layout += AE_CH_SR ; + if (ffmap & AV_CH_TOP_CENTER ) layout += AE_CH_TC ; + if (ffmap & AV_CH_TOP_FRONT_LEFT ) layout += AE_CH_TFL ; + if (ffmap & AV_CH_TOP_FRONT_CENTER ) layout += AE_CH_TFC ; + if (ffmap & AV_CH_TOP_FRONT_RIGHT ) layout += AE_CH_TFR ; + if (ffmap & AV_CH_TOP_BACK_LEFT ) layout += AE_CH_TBL ; + if (ffmap & AV_CH_TOP_BACK_CENTER ) layout += AE_CH_TBC ; + if (ffmap & AV_CH_TOP_BACK_RIGHT ) layout += AE_CH_TBR ; + + return layout.Count(); +} + +bool CAEEncoderFFmpeg::Initialize(AEAudioFormat &format, bool allow_planar_input) +{ + Reset(); + + bool ac3 = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_AUDIOOUTPUT_AC3PASSTHROUGH); + + AVCodec *codec = NULL; + + /* fallback to ac3 if we support it, we might not have DTS support */ + if (ac3) + { + m_CodecName = "AC3"; + m_CodecID = AV_CODEC_ID_AC3; + m_BitRate = AC3_ENCODE_BITRATE; + codec = avcodec_find_encoder(m_CodecID); + } + + /* check we got the codec */ + if (!codec) + return false; + + m_CodecCtx = avcodec_alloc_context3(codec); + if (!m_CodecCtx) + return false; + + m_CodecCtx->bit_rate = m_BitRate; + m_CodecCtx->sample_rate = format.m_sampleRate; + m_CodecCtx->channel_layout = AV_CH_LAYOUT_5POINT1_BACK; + + /* select a suitable data format */ + if (codec->sample_fmts) + { + bool hasFloat = false; + bool hasDouble = false; + bool hasS32 = false; + bool hasS16 = false; + bool hasU8 = false; + bool hasFloatP = false; + bool hasUnknownFormat = false; + + for(int i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) + { + switch (codec->sample_fmts[i]) + { + case AV_SAMPLE_FMT_FLT: hasFloat = true; break; + case AV_SAMPLE_FMT_DBL: hasDouble = true; break; + case AV_SAMPLE_FMT_S32: hasS32 = true; break; + case AV_SAMPLE_FMT_S16: hasS16 = true; break; + case AV_SAMPLE_FMT_U8 : hasU8 = true; break; + case AV_SAMPLE_FMT_FLTP: + if (allow_planar_input) + hasFloatP = true; + else + hasUnknownFormat = true; + break; + case AV_SAMPLE_FMT_NONE: return false; + default: hasUnknownFormat = true; break; + } + } + + if (hasFloat) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLT; + format.m_dataFormat = AE_FMT_FLOAT; + } + else if (hasFloatP) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_FLTP; + format.m_dataFormat = AE_FMT_FLOATP; + } + else if (hasDouble) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_DBL; + format.m_dataFormat = AE_FMT_DOUBLE; + } + else if (hasS32) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_S32; + format.m_dataFormat = AE_FMT_S32NE; + } + else if (hasS16) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_S16; + format.m_dataFormat = AE_FMT_S16NE; + } + else if (hasU8) + { + m_CodecCtx->sample_fmt = AV_SAMPLE_FMT_U8; + format.m_dataFormat = AE_FMT_U8; + } + else if (hasUnknownFormat) + { + m_CodecCtx->sample_fmt = codec->sample_fmts[0]; + format.m_dataFormat = AE_FMT_FLOAT; + m_NeedConversion = true; + CLog::Log(LOGINFO, + "CAEEncoderFFmpeg::Initialize - Unknown audio format, it will be resampled."); + } + else + { + CLog::Log( + LOGERROR, + "CAEEncoderFFmpeg::Initialize - Unable to find a suitable data format for the codec ({})", + m_CodecName); + avcodec_free_context(&m_CodecCtx); + return false; + } + } + + m_CodecCtx->channels = BuildChannelLayout(m_CodecCtx->channel_layout, m_Layout); + + /* open the codec */ + if (avcodec_open2(m_CodecCtx, codec, NULL)) + { + avcodec_free_context(&m_CodecCtx); + return false; + } + + format.m_frames = m_CodecCtx->frame_size; + format.m_frameSize = m_CodecCtx->channels * (CAEUtil::DataFormatToBits(format.m_dataFormat) >> 3); + format.m_channelLayout = m_Layout; + + m_CurrentFormat = format; + m_NeededFrames = format.m_frames; + m_OutputRatio = (double)m_NeededFrames / m_OutputSize; + m_SampleRateMul = 1.0 / (double)m_CodecCtx->sample_rate; + + if (m_NeedConversion) + { + m_SwrCtx = swr_alloc_set_opts(NULL, + m_CodecCtx->channel_layout, m_CodecCtx->sample_fmt, m_CodecCtx->sample_rate, + m_CodecCtx->channel_layout, AV_SAMPLE_FMT_FLT, m_CodecCtx->sample_rate, + 0, NULL); + if (!m_SwrCtx || swr_init(m_SwrCtx) < 0) + { + CLog::Log(LOGERROR, "CAEEncoderFFmpeg::Initialize - Failed to initialise resampler."); + swr_free(&m_SwrCtx); + avcodec_free_context(&m_CodecCtx); + return false; + } + } + CLog::Log(LOGINFO, "CAEEncoderFFmpeg::Initialize - {} encoder ready", m_CodecName); + return true; +} + +void CAEEncoderFFmpeg::Reset() +{ + m_BufferSize = 0; +} + +unsigned int CAEEncoderFFmpeg::GetBitRate() +{ + return m_BitRate; +} + +AVCodecID CAEEncoderFFmpeg::GetCodecID() +{ + return m_CodecID; +} + +unsigned int CAEEncoderFFmpeg::GetFrames() +{ + return m_NeededFrames; +} + +int CAEEncoderFFmpeg::Encode(uint8_t *in, int in_size, uint8_t *out, int out_size) +{ + int got_output; + AVFrame *frame; + + if (!m_CodecCtx) + return 0; + + /* allocate the input frame + * sadly, we have to alloc/dealloc it everytime since we have no guarantee the + * data argument will be constant over iterated calls and the frame needs to + * setup pointers inside data */ + frame = av_frame_alloc(); + if (!frame) + return 0; + + frame->nb_samples = m_CodecCtx->frame_size; + frame->format = m_CodecCtx->sample_fmt; + frame->channel_layout = m_CodecCtx->channel_layout; + frame->channels = m_CodecCtx->channels; + + avcodec_fill_audio_frame(frame, m_CodecCtx->channels, m_CodecCtx->sample_fmt, + in, in_size, 0); + + /* initialize the output packet */ + AVPacket* pkt = av_packet_alloc(); + if (!pkt) + { + CLog::Log(LOGERROR, "CAEEncoderFFmpeg::{} - av_packet_alloc failed: {}", __FUNCTION__, + strerror(errno)); + av_frame_free(&frame); + return 0; + } + + pkt->size = out_size; + pkt->data = out; + + /* encode it */ + int ret = avcodec_encode_audio2(m_CodecCtx, pkt, frame, &got_output); + + int size = pkt->size; + + /* free temporary data */ + av_frame_free(&frame); + + /* free the packet */ + av_packet_free(&pkt); + + if (ret < 0 || !got_output) + { + CLog::Log(LOGERROR, "CAEEncoderFFmpeg::Encode - Encoding failed"); + return 0; + } + + /* return the number of frames used */ + return size; +} + + +int CAEEncoderFFmpeg::GetData(uint8_t **data) +{ + int size; + *data = m_Buffer; + size = m_BufferSize; + m_BufferSize = 0; + return size; +} + +double CAEEncoderFFmpeg::GetDelay(unsigned int bufferSize) +{ + if (!m_CodecCtx) + return 0; + + int frames = m_CodecCtx->delay; + if (m_BufferSize) + frames += m_NeededFrames; + + return ((double)frames + ((double)bufferSize * m_OutputRatio)) * m_SampleRateMul; +} + diff --git a/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h new file mode 100644 index 0000000..3274d3a --- /dev/null +++ b/xbmc/cores/AudioEngine/Encoders/AEEncoderFFmpeg.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2010-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/AudioEngine/Interfaces/AEEncoder.h" + +extern "C" { +#include <libswresample/swresample.h> +} + +/* ffmpeg re-defines this, so undef it to squash the warning */ +#undef restrict + +class CAEEncoderFFmpeg: public IAEEncoder +{ +public: + CAEEncoderFFmpeg(); + ~CAEEncoderFFmpeg() override; + + bool IsCompatible(const AEAudioFormat& format) override; + bool Initialize(AEAudioFormat &format, bool allow_planar_input = false) override; + void Reset() override; + + unsigned int GetBitRate() override; + AVCodecID GetCodecID() override; + unsigned int GetFrames() override; + + int Encode(uint8_t *in, int in_size, uint8_t *out, int out_size) override; + int GetData(uint8_t **data) override; + double GetDelay(unsigned int bufferSize) override; +private: + unsigned int BuildChannelLayout(const int64_t ffmap, CAEChannelInfo& layout); + + std::string m_CodecName; + AVCodecID m_CodecID; + unsigned int m_BitRate = 0; + AEAudioFormat m_CurrentFormat; + AVCodecContext *m_CodecCtx; + SwrContext *m_SwrCtx; + CAEChannelInfo m_Layout; + uint8_t m_Buffer[8 + AV_INPUT_BUFFER_MIN_SIZE]; + int m_BufferSize = 0; + int m_OutputSize = 0; + double m_OutputRatio = 0.0; + double m_SampleRateMul = 0.0; + unsigned int m_NeededFrames = 0; + bool m_NeedConversion = false; +}; + |