summaryrefslogtreecommitdiffstats
path: root/xbmc/cdrip/EncoderFFmpeg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/cdrip/EncoderFFmpeg.cpp')
-rw-r--r--xbmc/cdrip/EncoderFFmpeg.cpp413
1 files changed, 413 insertions, 0 deletions
diff --git a/xbmc/cdrip/EncoderFFmpeg.cpp b/xbmc/cdrip/EncoderFFmpeg.cpp
new file mode 100644
index 0000000..2867b7c
--- /dev/null
+++ b/xbmc/cdrip/EncoderFFmpeg.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2005-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.
+ */
+
+#include "EncoderFFmpeg.h"
+
+#include "ServiceBroker.h"
+#include "addons/Addon.h"
+#include "addons/AddonManager.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/StringUtils.h"
+#include "utils/SystemInfo.h"
+#include "utils/URIUtils.h"
+#include "utils/log.h"
+
+using namespace KODI::CDRIP;
+
+namespace
+{
+
+struct EncoderException : public std::exception
+{
+ std::string s;
+ template<typename... Args>
+ EncoderException(const std::string& fmt, Args&&... args)
+ : s(StringUtils::Format(fmt, std::forward<Args>(args)...))
+ {
+ }
+ ~EncoderException() throw() {} // Updated
+ const char* what() const throw() { return s.c_str(); }
+};
+
+} /* namespace */
+
+bool CEncoderFFmpeg::Init()
+{
+ try
+ {
+ ADDON::AddonPtr addon;
+ const std::string addonId = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
+ CSettings::SETTING_AUDIOCDS_ENCODER);
+ bool success =
+ CServiceBroker::GetAddonMgr().GetAddon(addonId, addon, ADDON::OnlyEnabled::CHOICE_YES);
+ int bitrate;
+ if (success && addon)
+ {
+ addon->GetSettingInt("bitrate", bitrate);
+ bitrate *= 1000; /* Multiply as on settings as kbps */
+ }
+ else
+ {
+ throw EncoderException("Could not get add-on: {}", addonId);
+ }
+
+ // Hack fix about PTS on generated files.
+ // - AAC need to multiply with sample rate
+ // - Note: Within Kodi it can still played without use of sample rate, only becomes by VLC the problem visible,
+ // - WMA need only the multiply with 1000
+ if (addonId == "audioencoder.kodi.builtin.aac")
+ m_samplesCountMultiply = m_iInSampleRate;
+ else if (addonId == "audioencoder.kodi.builtin.wma")
+ m_samplesCountMultiply = 1000;
+ else
+ throw EncoderException("Internal add-on id \"{}\" not known as usable", addonId);
+
+ const std::string filename = URIUtils::GetFileName(m_strFile);
+
+ m_formatCtx = avformat_alloc_context();
+ if (!m_formatCtx)
+ throw EncoderException("Could not allocate output format context");
+
+ m_bcBuffer = static_cast<uint8_t*>(av_malloc(BUFFER_SIZE + AV_INPUT_BUFFER_PADDING_SIZE));
+ if (!m_bcBuffer)
+ throw EncoderException("Could not allocate buffer");
+
+ m_formatCtx->pb = avio_alloc_context(m_bcBuffer, BUFFER_SIZE, AVIO_FLAG_WRITE, this, nullptr,
+ avio_write_callback, avio_seek_callback);
+ if (!m_formatCtx->pb)
+ throw EncoderException("Failed to allocate ByteIOContext");
+
+ /* Guess the desired container format based on the file extension. */
+ m_formatCtx->oformat = av_guess_format(nullptr, filename.c_str(), nullptr);
+ if (!m_formatCtx->oformat)
+ throw EncoderException("Could not find output file format");
+
+ m_formatCtx->url = av_strdup(filename.c_str());
+ if (!m_formatCtx->url)
+ throw EncoderException("Could not allocate url");
+
+ /* Find the encoder to be used by its name. */
+ AVCodec* codec = avcodec_find_encoder(m_formatCtx->oformat->audio_codec);
+ if (!codec)
+ throw EncoderException("Unable to find a suitable FFmpeg encoder");
+
+ /* Create a new audio stream in the output file container. */
+ m_stream = avformat_new_stream(m_formatCtx, nullptr);
+ if (!m_stream)
+ throw EncoderException("Failed to allocate AVStream context");
+
+ m_codecCtx = avcodec_alloc_context3(codec);
+ if (!m_codecCtx)
+ throw EncoderException("Failed to allocate the encoder context");
+
+ /* Set the basic encoder parameters.
+ * The input file's sample rate is used to avoid a sample rate conversion. */
+ m_codecCtx->channels = m_iInChannels;
+ m_codecCtx->channel_layout = av_get_default_channel_layout(m_iInChannels);
+ m_codecCtx->sample_rate = m_iInSampleRate;
+ m_codecCtx->sample_fmt = codec->sample_fmts[0];
+ m_codecCtx->bit_rate = bitrate;
+
+ /* Allow experimental encoders (like FFmpeg builtin AAC encoder) */
+ m_codecCtx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+
+ /* Set the sample rate for the container. */
+ m_codecCtx->time_base.num = 1;
+ m_codecCtx->time_base.den = m_iInSampleRate;
+
+ /* Some container formats (like MP4) require global headers to be present.
+ * Mark the encoder so that it behaves accordingly. */
+ if (m_formatCtx->oformat->flags & AVFMT_GLOBALHEADER)
+ m_codecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
+
+ int err = avcodec_open2(m_codecCtx, codec, nullptr);
+ if (err < 0)
+ throw EncoderException("Failed to open the codec {} (error '{}')",
+ codec->long_name ? codec->long_name : codec->name,
+ FFmpegErrorToString(err));
+
+ err = avcodec_parameters_from_context(m_stream->codecpar, m_codecCtx);
+ if (err < 0)
+ throw EncoderException("Failed to copy encoder parameters to output stream (error '{}')",
+ FFmpegErrorToString(err));
+
+ m_inFormat = GetInputFormat(m_iInBitsPerSample);
+ m_outFormat = m_codecCtx->sample_fmt;
+ m_needConversion = (m_outFormat != m_inFormat);
+
+ /* calculate how many bytes we need per frame */
+ m_neededFrames = m_codecCtx->frame_size;
+ m_neededBytes =
+ av_samples_get_buffer_size(nullptr, m_iInChannels, m_neededFrames, m_inFormat, 0);
+ m_buffer = static_cast<uint8_t*>(av_malloc(m_neededBytes));
+ m_bufferSize = 0;
+
+ m_bufferFrame = av_frame_alloc();
+ if (!m_bufferFrame || !m_buffer)
+ throw EncoderException("Failed to allocate necessary buffers");
+
+ m_bufferFrame->nb_samples = m_codecCtx->frame_size;
+ m_bufferFrame->format = m_inFormat;
+ m_bufferFrame->channel_layout = m_codecCtx->channel_layout;
+ m_bufferFrame->sample_rate = m_codecCtx->sample_rate;
+
+ err = av_frame_get_buffer(m_bufferFrame, 0);
+ if (err < 0)
+ throw EncoderException("Could not allocate output frame samples (error '{}')",
+ FFmpegErrorToString(err));
+
+ avcodec_fill_audio_frame(m_bufferFrame, m_iInChannels, m_inFormat, m_buffer, m_neededBytes, 0);
+
+ if (m_needConversion)
+ {
+ m_swrCtx = swr_alloc_set_opts(nullptr, m_codecCtx->channel_layout, m_outFormat,
+ m_codecCtx->sample_rate, m_codecCtx->channel_layout, m_inFormat,
+ m_codecCtx->sample_rate, 0, nullptr);
+ if (!m_swrCtx || swr_init(m_swrCtx) < 0)
+ throw EncoderException("Failed to initialize the resampler");
+
+ m_resampledBufferSize =
+ av_samples_get_buffer_size(nullptr, m_iInChannels, m_neededFrames, m_outFormat, 0);
+ m_resampledBuffer = static_cast<uint8_t*>(av_malloc(m_resampledBufferSize));
+ m_resampledFrame = av_frame_alloc();
+ if (!m_resampledBuffer || !m_resampledFrame)
+ throw EncoderException("Failed to allocate a frame for resampling");
+
+ m_resampledFrame->nb_samples = m_neededFrames;
+ m_resampledFrame->format = m_outFormat;
+ m_resampledFrame->channel_layout = m_codecCtx->channel_layout;
+ m_resampledFrame->sample_rate = m_codecCtx->sample_rate;
+
+ err = av_frame_get_buffer(m_resampledFrame, 0);
+ if (err < 0)
+ throw EncoderException("Could not allocate output resample frame samples (error '{}')",
+ FFmpegErrorToString(err));
+
+ avcodec_fill_audio_frame(m_resampledFrame, m_iInChannels, m_outFormat, m_resampledBuffer,
+ m_resampledBufferSize, 0);
+ }
+
+ /* set the tags */
+ SetTag("album", m_strAlbum);
+ SetTag("album_artist", m_strArtist);
+ SetTag("genre", m_strGenre);
+ SetTag("title", m_strTitle);
+ SetTag("track", m_strTrack);
+ SetTag("encoder", CSysInfo::GetAppName() + " FFmpeg Encoder");
+
+ /* write the header */
+ err = avformat_write_header(m_formatCtx, nullptr);
+ if (err != 0)
+ throw EncoderException("Failed to write the header (error '{}')", FFmpegErrorToString(err));
+
+ CLog::Log(LOGDEBUG, "CEncoderFFmpeg::{} - Successfully initialized with muxer {} and codec {}",
+ __func__,
+ m_formatCtx->oformat->long_name ? m_formatCtx->oformat->long_name
+ : m_formatCtx->oformat->name,
+ codec->long_name ? codec->long_name : codec->name);
+ }
+ catch (EncoderException& caught)
+ {
+ CLog::Log(LOGERROR, "CEncoderFFmpeg::{} - {}", __func__, caught.what());
+
+ av_freep(&m_buffer);
+ av_frame_free(&m_bufferFrame);
+ swr_free(&m_swrCtx);
+ av_frame_free(&m_resampledFrame);
+ av_freep(&m_resampledBuffer);
+ av_free(m_bcBuffer);
+ avcodec_free_context(&m_codecCtx);
+ if (m_formatCtx)
+ {
+ av_freep(&m_formatCtx->pb);
+ avformat_free_context(m_formatCtx);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+void CEncoderFFmpeg::SetTag(const std::string& tag, const std::string& value)
+{
+ av_dict_set(&m_formatCtx->metadata, tag.c_str(), value.c_str(), 0);
+}
+
+int CEncoderFFmpeg::avio_write_callback(void* opaque, uint8_t* buf, int buf_size)
+{
+ CEncoderFFmpeg* enc = static_cast<CEncoderFFmpeg*>(opaque);
+ if (enc->Write(buf, buf_size) != buf_size)
+ {
+ CLog::Log(LOGERROR, "CEncoderFFmpeg::{} - Error writing FFmpeg buffer to file", __func__);
+ return -1;
+ }
+ return buf_size;
+}
+
+int64_t CEncoderFFmpeg::avio_seek_callback(void* opaque, int64_t offset, int whence)
+{
+ CEncoderFFmpeg* enc = static_cast<CEncoderFFmpeg*>(opaque);
+ return enc->Seek(offset, whence);
+}
+
+ssize_t CEncoderFFmpeg::Encode(uint8_t* pbtStream, size_t nNumBytesRead)
+{
+ while (nNumBytesRead > 0)
+ {
+ size_t space = m_neededBytes - m_bufferSize;
+ size_t copy = nNumBytesRead > space ? space : nNumBytesRead;
+
+ memcpy(&m_buffer[m_bufferSize], pbtStream, copy);
+ m_bufferSize += copy;
+ pbtStream += copy;
+ nNumBytesRead -= copy;
+
+ /* only write full packets */
+ if (m_bufferSize == m_neededBytes)
+ {
+ if (!WriteFrame())
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+bool CEncoderFFmpeg::WriteFrame()
+{
+ int err = AVERROR_UNKNOWN;
+ AVPacket* pkt = av_packet_alloc();
+ if (!pkt)
+ {
+ CLog::Log(LOGERROR, "CEncoderFFmpeg::{} - av_packet_alloc failed: {}", __func__,
+ strerror(errno));
+ return false;
+ }
+
+ try
+ {
+ AVFrame* frame;
+ if (m_needConversion)
+ {
+ //! @bug libavresample isn't const correct
+ if (swr_convert(m_swrCtx, m_resampledFrame->data, m_neededFrames,
+ const_cast<const uint8_t**>(m_bufferFrame->extended_data),
+ m_neededFrames) < 0)
+ throw EncoderException("Error resampling audio");
+
+ frame = m_resampledFrame;
+ }
+ else
+ frame = m_bufferFrame;
+
+ if (frame)
+ {
+ /* To fix correct length on wma files */
+ frame->pts = m_samplesCount;
+ m_samplesCount += frame->nb_samples * m_samplesCountMultiply / m_codecCtx->time_base.den;
+ }
+
+ m_bufferSize = 0;
+ err = avcodec_send_frame(m_codecCtx, frame);
+ if (err < 0)
+ throw EncoderException("Error sending a frame for encoding (error '{}')", __func__,
+ FFmpegErrorToString(err));
+
+ while (err >= 0)
+ {
+ err = avcodec_receive_packet(m_codecCtx, pkt);
+ if (err == AVERROR(EAGAIN) || err == AVERROR_EOF)
+ {
+ av_packet_free(&pkt);
+ return (err == AVERROR(EAGAIN)) ? false : true;
+ }
+ else if (err < 0)
+ {
+ throw EncoderException("Error during encoding (error '{}')", __func__,
+ FFmpegErrorToString(err));
+ }
+
+ err = av_write_frame(m_formatCtx, pkt);
+ if (err < 0)
+ throw EncoderException("Failed to write the frame data (error '{}')", __func__,
+ FFmpegErrorToString(err));
+
+ av_packet_unref(pkt);
+ }
+ }
+ catch (EncoderException& caught)
+ {
+ CLog::Log(LOGERROR, "CEncoderFFmpeg::{} - {}", __func__, caught.what());
+ }
+
+ av_packet_free(&pkt);
+
+ return (err) ? false : true;
+}
+
+bool CEncoderFFmpeg::Close()
+{
+ if (m_formatCtx)
+ {
+ /* if there is anything still in the buffer */
+ if (m_bufferSize > 0)
+ {
+ /* zero the unused space so we dont encode random junk */
+ memset(&m_buffer[m_bufferSize], 0, m_neededBytes - m_bufferSize);
+ /* write any remaining data */
+ WriteFrame();
+ }
+
+ /* Flush if needed */
+ av_freep(&m_buffer);
+ av_frame_free(&m_bufferFrame);
+ swr_free(&m_swrCtx);
+ av_frame_free(&m_resampledFrame);
+ av_freep(&m_resampledBuffer);
+ m_needConversion = false;
+
+ WriteFrame();
+
+ /* write the trailer */
+ av_write_trailer(m_formatCtx);
+
+ /* cleanup */
+ av_free(m_bcBuffer);
+ avcodec_free_context(&m_codecCtx);
+ av_freep(&m_formatCtx->pb);
+ avformat_free_context(m_formatCtx);
+ }
+
+ m_bufferSize = 0;
+
+ return true;
+}
+
+AVSampleFormat CEncoderFFmpeg::GetInputFormat(int inBitsPerSample)
+{
+ switch (inBitsPerSample)
+ {
+ case 8:
+ return AV_SAMPLE_FMT_U8;
+ case 16:
+ return AV_SAMPLE_FMT_S16;
+ case 32:
+ return AV_SAMPLE_FMT_S32;
+ default:
+ throw EncoderException("Invalid input bits per sample");
+ }
+}
+
+std::string CEncoderFFmpeg::FFmpegErrorToString(int err)
+{
+ std::string text;
+ text.reserve(AV_ERROR_MAX_STRING_SIZE);
+ av_strerror(err, text.data(), AV_ERROR_MAX_STRING_SIZE);
+ return text;
+}