diff --git a/src/Lib/Audio.cc b/src/Lib/Audio.cc index 23ad3e86e0fe014206f8e41bb7f0c044c7a12aa6..47684383ae37da01c3aff1deb430b71e7b7f4b39 100644 --- a/src/Lib/Audio.cc +++ b/src/Lib/Audio.cc @@ -9,125 +9,29 @@ using namespace Vivy; // Create an audio contecxt from a file AudioContext::AudioContext(const QString &path) - : filePath(path) + : Super(path) { - if (!format) - throw std::runtime_error("out of memory, can't create allocate the AVFormatContext"); - - const std::string filePathStdHolder = filePath.toStdString(); - const char *filename = filePathStdHolder.c_str(); - AVFormatContext *formatPtr = format.get(); - - // Get the format from the audio file - if (avformat_open_input(&formatPtr, filename, nullptr, nullptr) != 0) { - [[maybe_unused]] void *relatedOnFailure = format.release(); // freed by avformat_open_input - throw std::runtime_error("failed to open file"); - } - - if (avformat_find_stream_info(formatPtr, nullptr) < 0) { - throw std::runtime_error("failed to get audio stream info"); - } - - // Populate all the stream indexes - for (uint i = 0; i < format->nb_streams; ++i) { - AVStream *itFormat = format->streams[i]; - AVCodecParameters *params = itFormat->codecpar; - AVCodec *streamCodec = avcodec_find_decoder(params->codec_id); - if (streamCodec && streamCodec->type == AVMEDIA_TYPE_AUDIO) { - audioStreams.insert(i, std::make_shared<Stream>(streamCodec, formatPtr, itFormat, i)); - } - } - - // Get the default stream - defaultStreamIndex = av_find_best_stream(formatPtr, AVMEDIA_TYPE_AUDIO, - -1, // Let AV find one stream - -1, // We don't want related streams - nullptr, 0); - if (defaultStreamIndex < 0) { - qCritical() << "Could not find the best audio stream"; - } - - qDebug() << "Opened audio context for" << path << "with duration" << formatPtr->duration - << "and default stream index" << defaultStreamIndex; -} - -// Get a specific audio stream, try to lock the weak pointer. if the index was -// not present the used_count will be 0 and you won't be able to lock it. -AudioContext::StreamWeakPtr -AudioContext::getStream(int index) const noexcept -{ - if (index < 0) - return StreamWeakPtr{ spareNullSreamPtr }; - - uint unsignedIndex = static_cast<uint>(index); - const auto found = audioStreams.find(unsignedIndex); - - if (found != audioStreams.end()) - return StreamWeakPtr{ *found }; - - return StreamWeakPtr{ spareNullSreamPtr }; -} - -// Get the default stream of this audio context. If no default audio stream is -// present you won't be able to lock the weak pointer. -AudioContext::StreamWeakPtr -AudioContext::getDefaultStream() const noexcept -{ - if (defaultStreamIndex < 0) - return StreamWeakPtr{ spareNullSreamPtr }; - return getStream(defaultStreamIndex); } QString AudioContext::getElementName() const noexcept { - QFileInfo file(filePath); - return "AudioContext: " + file.baseName(); + return "Audio" + Super::getElementName(); } QJsonDocument AudioContext::getProperties() const noexcept { - QJsonDocument ret; - QJsonArray streams; - - QFileInfo file(filePath); - - for (const auto &audioStreamPtr : audioStreams) { - streams.append(audioStreamPtr->getProperties()); - } - - QJsonObject self{ { "Streams", streams }, { "Base name", file.baseName() } }; - - ret.setObject(self); - return ret; + return Super::getProperties(); } -// AudioContext::Stream class implementation +// AudioStream class implementation // Constructor, need an AVFormat and an AVStream -AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *stream, - int index) - : codecId(stream->codecpar->codec_id) - , codec(streamCodec) - , codecParams(stream->codecpar) - , audioStream(stream) - , streamIndexInAudioContext(index) - , dataFormat(formatPtr) +AudioStream::AudioStream(AVCodec *streamCodec, AVFormatContext *formatPtr, AVStream *streamArg, + int index) + : Super(streamCodec, formatPtr, streamArg, index) { - 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); @@ -140,14 +44,9 @@ AudioContext::Stream::Stream(AVCodec *streamCodec, AVFormatContext *formatPtr, A swr_init(s); if (!swr_is_initialized(s)) throw std::runtime_error("failed to initialize SwrContext resampler"); - - qDebug() << "[Stream] Codec" << codec->name << "id:" << codecId; - qDebug() << "[Stream] sample rate:" << codecParams->sample_rate; - qDebug() << "[Stream] bit rate: " << codecParams->bit_rate; - qDebug() << "[Stream] channels: " << codecParams->channels; } -AudioContext::Stream::~Stream() noexcept +AudioStream::~AudioStream() noexcept { if (dataPtr) { qDebug() << "Free data ptr"; @@ -157,10 +56,9 @@ AudioContext::Stream::~Stream() noexcept } QJsonObject -AudioContext::Stream::getProperties() const noexcept +AudioStream::getProperties() const noexcept { - QJsonObject ret; - ret.insert("Codec name", codec->name); + QJsonObject ret = Super::getProperties(); ret.insert("Sample rate", codecParams->sample_rate); ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate)); ret.insert("Channels", codecParams->channels); @@ -169,11 +67,11 @@ AudioContext::Stream::getProperties() const noexcept // Decode the data, don't call it if data already decoded void -AudioContext::Stream::decodeData() +AudioStream::decodeData() { if (isDecoded()) throw std::logic_error("audio stream is already resampled"); - qDebug() << "Launch decoding of stream" << streamIndexInAudioContext; + qDebug() << "Launch decoding of stream" << streamIndexInContext; AVPacket packet; av_init_packet(&packet); @@ -181,7 +79,7 @@ AudioContext::Stream::decodeData() // Iterate through frames while (av_read_frame(dataFormat, &packet) >= 0) { // Only decode audio - if (packet.stream_index != streamIndexInAudioContext) { + if (packet.stream_index != streamIndexInContext) { av_packet_unref(&packet); continue; } @@ -242,13 +140,13 @@ AudioContext::Stream::decodeData() av_packet_unref(&packet); } - qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext + qDebug() << "Decoding data finished for stream" << streamIndexInContext << "dataPtr =" << dataPtr << "with dataSize =" << dataSize; } // Delete decoded data, clean up thing void -AudioContext::Stream::cleanUpData() noexcept +AudioStream::cleanUpData() noexcept { free(dataPtr); dataPtr = nullptr; @@ -257,14 +155,14 @@ AudioContext::Stream::cleanUpData() noexcept // Get the number of channels in the audio context stream int -AudioContext::Stream::getChannels() const noexcept +AudioStream::getChannels() const noexcept { return codecContext->channels; } // Get the sample rate int -AudioContext::Stream::getSampleRate() const noexcept +AudioStream::getSampleRate() const noexcept { return codecContext->sample_rate; } @@ -272,35 +170,21 @@ AudioContext::Stream::getSampleRate() const noexcept // Get the sample rate of the decoded data (need to call decodeData for that // value to have a meaning). int -AudioContext::Stream::getDataSampleRate() const noexcept +AudioStream::getDataSampleRate() const noexcept { return resamplerSampleRate; } -// Get the name of the codec used to decode the stream -QString -AudioContext::Stream::getName() const noexcept -{ - return QString::fromUtf8(codec->name); -} - -// Get the codec's ID used to decode the stream -AVCodecID -AudioContext::Stream::getCodecId() const noexcept -{ - return codecId; -} - // Get the bit rate of the stream qint64 -AudioContext::Stream::getBitRate() const noexcept +AudioStream::getBitRate() const noexcept { return codecContext->bit_rate; } // Get the information about the decoded state of this stream bool -AudioContext::Stream::isDecoded() const noexcept +AudioStream::isDecoded() const noexcept { return dataPtr != nullptr; } @@ -308,7 +192,7 @@ AudioContext::Stream::isDecoded() const noexcept // Get the decoded data's size, will return 0 if the data is not decoded. Don't // rely on this behaviour to get the decoded state of the stream! size_t -AudioContext::Stream::getDecodedDataSize() const noexcept +AudioStream::getDecodedDataSize() const noexcept { return dataSize; } @@ -316,28 +200,22 @@ AudioContext::Stream::getDecodedDataSize() const noexcept // Get the decoded data, safe to call even if the data is not decoded has the // pointer will be null. You must check it with an `if` statement. double * -AudioContext::Stream::getDecodedData() const noexcept +AudioStream::getDecodedData() const noexcept { return dataPtr; } // Get the chunk size of the decoded data size_t -AudioContext::Stream::getDecodedChunkSize() const noexcept +AudioStream::getDecodedChunkSize() const noexcept { return 512; } // Get the decalage of the decoded data size_t -AudioContext::Stream::getDecodedDecalage() const noexcept +AudioStream::getDecodedDecalage() const noexcept { constexpr size_t overlap = 128; // The overlap return getDecodedChunkSize() - overlap; } - -int -AudioContext::Stream::getStreamIndex() const noexcept -{ - return streamIndexInAudioContext; -} diff --git a/src/Lib/Audio.hh b/src/Lib/Audio.hh index 3259d1ffdec318d6bfeba89149de700d40dbea03..15d73c52a17324fa1eb26ff23fee737739b7f647 100644 --- a/src/Lib/Audio.hh +++ b/src/Lib/Audio.hh @@ -23,104 +23,66 @@ namespace Vivy { class AudioContext; +// Hold all the data for a stream in an audio file. Should only be owned by +// the AudioContext class. +class AudioStream final : public AbstractMediaStream<AVMEDIA_TYPE_AUDIO> { + VIVY_UNMOVABLE_OBJECT(AudioStream) + using Super = AbstractMediaStream<AVMEDIA_TYPE_AUDIO>; + +public: + AudioStream(AVCodec *, AVFormatContext *, AVStream *, int index); + ~AudioStream() noexcept override; + + // The non-owning view of the stream's data + using DataWeakPtr = std::weak_ptr<double[]>; + + // Decode the stream + void decodeData(); + void cleanUpData() noexcept; + bool isDecoded() const noexcept; + int getDataSampleRate() const noexcept; + size_t getDecodedDataSize() const noexcept; + size_t getDecodedChunkSize() const noexcept; + size_t getDecodedDecalage() const noexcept; + double *getDecodedData() const noexcept; + + // Some getters + int getChannels() const noexcept; + int getSampleRate() const noexcept; + qint64 getBitRate() const noexcept; + QJsonObject getProperties() const noexcept override; + +private: + // Resampled frame data + SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; + AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; + double *dataPtr{ 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; +}; + // The memory representation of an audio file. It is created inplace and should // not be moved around. It should be used to get the properties of an audio // file and its streams. The user can use a stream from the file and get data // from it. -class AudioContext final { +class AudioContext final : public AbstractMediaContext<AudioStream> { VIVY_UNMOVABLE_OBJECT(AudioContext) + using Super = AbstractMediaContext<AudioStream>; public: - // Hold all the data for a stream in an audio file. Should only be owned by - // the AudioContext class. - class Stream final { - VIVY_UNMOVABLE_OBJECT(Stream) - - static inline Utils::DeleterFunctionType<double> dataDeleter = - std::bind_front(Utils::freePtrIfNotNull<double>, av_free); - - static inline Utils::DeleterFunctionType<AVCodecContext> codecContexteleter = - std::bind_front(Utils::freePPtrIfNotNull<AVCodecContext>, avcodec_free_context); - - static inline Utils::DeleterFunctionType<AVFrame> avFrameDeleter = - std::bind_front(Utils::freePPtrIfNotNull<AVFrame>, av_frame_free); - - static inline Utils::DeleterFunctionType<SwrContext> swrContenxtDeleter = - std::bind_front(Utils::freePPtrIfNotNull<SwrContext>, swr_free); - - // All the used types - using AVCodecContextPtr = std::unique_ptr<AVCodecContext, decltype(codecContexteleter)>; - using DataPtr = std::shared_ptr<double[]>; - using AVFramePtr = std::unique_ptr<AVFrame, decltype(avFrameDeleter)>; - using SwrContextPtr = std::unique_ptr<SwrContext, decltype(swrContenxtDeleter)>; - - public: - Stream(AVCodec *, AVFormatContext *, AVStream *, int index); - ~Stream() noexcept; - - // The non-owning view of the stream's data - using DataWeakPtr = std::weak_ptr<double[]>; - - // Decode the stream - void decodeData(); - void cleanUpData() noexcept; - bool isDecoded() const noexcept; - int getDataSampleRate() const noexcept; - size_t getDecodedDataSize() const noexcept; - size_t getDecodedChunkSize() const noexcept; - size_t getDecodedDecalage() const noexcept; - double *getDecodedData() const noexcept; - - // Some getters - int getChannels() const noexcept; - int getSampleRate() const noexcept; - QString getName() const noexcept; - AVCodecID getCodecId() const noexcept; - qint64 getBitRate() const noexcept; - - // Get the index from the audio context - int getStreamIndex() const noexcept; - - QJsonObject getProperties() const noexcept; - - private: - // Codec related informations - AVCodecID codecId{ AV_CODEC_ID_NONE }; - AVCodec *codec{ nullptr }; - AVCodecParameters *codecParams{ nullptr }; - AVCodecContextPtr codecContext{ nullptr }; - - // Stream is held by AudioContext - AVStream *audioStream{ nullptr }; - - // Store the index of this stream - int streamIndexInAudioContext; - - // Resampled frame data - AVFormatContext *dataFormat; - SwrContextPtr dataSwrContext{ swr_alloc(), swrContenxtDeleter }; - AVFramePtr dataFrame{ av_frame_alloc(), avFrameDeleter }; - double *dataPtr{ 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; - }; - - using StreamPtr = std::shared_ptr<Stream>; - using StreamWeakPtr = std::weak_ptr<Stream>; + using StreamPtr = std::shared_ptr<AudioStream>; + using StreamWeakPtr = std::weak_ptr<AudioStream>; public: AudioContext(const QString &path); - StreamWeakPtr getStream(int) const noexcept; - StreamWeakPtr getDefaultStream() const noexcept; - - QString getElementName() const noexcept; - QJsonDocument getProperties() const noexcept; + QString getElementName() const noexcept override; + QJsonDocument getProperties() const noexcept override; private: // Regarding the format