diff --git a/src/Audio.cc b/src/Audio.cc new file mode 100644 index 0000000000000000000000000000000000000000..53278823101a93218f265dd99a8dfd19b132507d --- /dev/null +++ b/src/Audio.cc @@ -0,0 +1,165 @@ +#include "Audio.hh" + +using namespace Vivy; + +AudioStreamCodec::AudioStreamCodec(AVFormatContext *format, AVStream *stream) + : codecId(stream->codecpar->codec_id) + , codecParams(stream->codecpar) + , audioStream(stream) + , dataFormat(format) +{ + codec = avcodec_find_decoder(codecId); + if (codec == nullptr) + throw std::runtime_error("failed to find a decoder for stream"); + + codecContext.reset(avcodec_alloc_context3(codec)); + if (!codecContext) + throw std::runtime_error("failed to allocate codec context"); + + if (avcodec_parameters_to_context(codecContext.get(), codecParams) < 0) + throw std::runtime_error("failed to copy parameters to codec context"); + + if (avcodec_open2(codecContext.get(), codec, nullptr) < 0) + throw std::runtime_error("failed to open audio decoder for a stream"); + + SwrContext *s = dataSwrContext.get(); + av_opt_set_int(s, "in_channel_count", codecContext->channels, 0); + av_opt_set_int(s, "out_channel_count", 1, 0); + av_opt_set_int(s, "in_channel_layout", static_cast<int64_t>(codecContext->channel_layout), 0); + av_opt_set_int(s, "out_channel_layout", AV_CH_LAYOUT_MONO, 0); + av_opt_set_int(s, "in_sample_rate", codecContext->sample_rate, 0); + av_opt_set_int(s, "out_sample_rate", resamplerSampleRate, 0); + av_opt_set_sample_fmt(s, "in_sample_fmt", codecContext->sample_fmt, 0); + av_opt_set_sample_fmt(s, "out_sample_fmt", AV_SAMPLE_FMT_DBL, 0); + swr_init(s); + if (!swr_is_initialized(s)) { + throw std::runtime_error("failed to initialize SwrContext resampler"); + } +} + +void +AudioStreamCodec::decodeData() +{ + if (isDecoded()) + throw std::logic_error("audio stream is already resampled"); + + AVPacket packet; + av_init_packet(&packet); + + /* Iterate through frames */ + while (av_read_frame(dataFormat, &packet) >= 0) { + /* Decode one frame */ + int response = avcodec_send_packet(codecContext.get(), &packet); + if (response < 0) [[unlikely]] { + // av_err2str(response); <- Compound literals are a C99-specific feature + throw std::runtime_error("error while sending a packet to the decoder"); + } + + double *buffer = nullptr; + int old_frame_nb_samples = 0; + + while (response >= 0) { + response = avcodec_receive_frame(codecContext.get(), dataFrame.get()); + if (response == AVERROR(EAGAIN) || response == AVERROR_EOF) [[unlikely]] { + break; + } else if (response < 0) [[unlikely]] { + // av_err2str(response); <- Compound literals are a C99-specific feature + throw std::runtime_error("error while receiving a frame from the decoder"); + } + + /* Resample frames */ + if (old_frame_nb_samples < dataFrame->nb_samples) { + if (nullptr != buffer) + av_free(buffer); + + old_frame_nb_samples = dataFrame->nb_samples; + av_samples_alloc(reinterpret_cast<uint8_t **>(&buffer), nullptr, 1, + dataFrame->nb_samples, AV_SAMPLE_FMT_DBL, 0); + } + + if (const int frame_count_int = + swr_convert(dataSwrContext.get(), reinterpret_cast<uint8_t **>(&buffer), + dataFrame->nb_samples, (const uint8_t **)dataFrame->data, + dataFrame->nb_samples); + frame_count_int < 0) [[unlikely]] { + throw std::runtime_error("error on frame count, is negative but should not be"); + } + + else [[likely]] { + const size_t frame_count = static_cast<size_t>(frame_count_int); + + /* Append resampled frames to data */ + dataRealPtr = (double *)realloc( + dataRealPtr, (dataSize + (size_t)dataFrame->nb_samples) * sizeof(double)); + memcpy(dataRealPtr + dataSize, buffer, frame_count * sizeof(double)); + dataSize += frame_count; + } + } + if (buffer) + av_free(buffer); + + av_packet_unref(&packet); + } +} + +void +AudioStreamCodec::cleanUpData() noexcept +{ + dataPtr.reset(); + dataRealPtr = nullptr; + dataSize = 0; +} + +int +AudioStreamCodec::getChannels() const noexcept +{ + return codecContext->channels; +} + +int +AudioStreamCodec::getSampleRate() const noexcept +{ + return codecContext->sample_rate; +} + +int +AudioStreamCodec::getDataSampleRate() const noexcept +{ + return resamplerSampleRate; +} + +QString +AudioStreamCodec::getName() const noexcept +{ + return QString(codec->name); +} + +AVCodecID +AudioStreamCodec::getCodecId() const noexcept +{ + return codecId; +} + +qint64 +AudioStreamCodec::getBitRate() const noexcept +{ + return codecContext->bit_rate; +} + +bool +AudioStreamCodec::isDecoded() const noexcept +{ + return dataPtr != nullptr && dataRealPtr != nullptr; +} + +size_t +AudioStreamCodec::getDecodedDataSize() const noexcept +{ + return dataSize; +} + +AudioStreamCodec::DataWeakPtr +AudioStreamCodec::getDecodedData() const noexcept +{ + return DataWeakPtr{ dataPtr }; +} diff --git a/src/Audio.hh b/src/Audio.hh index 67ef33151b2557b21d7fd632be432f40e84be73e..d6076292c15a8b60f2f2f1832880a1c7f046bec9 100644 --- a/src/Audio.hh +++ b/src/Audio.hh @@ -17,6 +17,7 @@ extern "C" { #include <QtGlobal> #include <QMap> #include <QVector> +#include <QString> #include <memory.h> namespace Vivy @@ -54,44 +55,47 @@ class AudioStreamCodec final { using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>; public: - AudioStreamCodec(AVStream *); - ~AudioStreamCodec(); + AudioStreamCodec(AVFormatContext *, AVStream *); + ~AudioStreamCodec() = default; // Decode the stream void decodeData(); + void cleanUpData() noexcept; + bool isDecoded() const noexcept; + int getDataSampleRate() const noexcept; size_t getDecodedDataSize() const noexcept; DataWeakPtr getDecodedData() const noexcept; // Some getters int getChannels() const noexcept; int getSampleRate() const noexcept; - const char *getName() const noexcept; + QString getName() const noexcept; AVCodecID getCodecId() const noexcept; qint64 getBitRate() const noexcept; private: - bool initFromStream() noexcept; // Init all the object - void cleanupForReuse() noexcept; // Free everything, can now use initFromStream again - - // Check if ready - bool isReadyForUse() const noexcept; - bool isResampled() const noexcept; - // Codec related informations AVCodecID codecId{ AV_CODEC_ID_NONE }; - AVCodecContextPtr codecContext{ nullptr }; - AVCodecParameters *codecParams{ nullptr }; AVCodec *codec{ nullptr }; + AVCodecParameters *codecParams{ nullptr }; + AVCodecContextPtr codecContext{ nullptr }; // Stream is held by AudioFormatCtx AVStream *audioStream{ nullptr }; - bool isInitFromStream{ false }; // Resampled frame data - SwrContextPtr dataSwrContext{ nullptr, swrContenxtDeleter }; - AVFramePtr dataFrame{ nullptr, avFrameDeleter }; - DataPtr dataPtr{ nullptr, dataDeleter }; + AVFormatContext *dataFormat; + SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; + AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; + DataPtr dataPtr{ nullptr, dataDeleter }; // Holds dataRealPtr + double *dataRealPtr{ nullptr }; size_t dataSize{ 0 }; + + // Constants for the resampler + static constexpr uint resamplerChunkSize = 512; + static constexpr uint resamplerOverlap = 128; + static constexpr uint resamplerDecalage = resamplerChunkSize - resamplerOverlap; + static constexpr uint resamplerSampleRate = 44100; }; /* Facility for pointers to AudioStreamCodec objects */