Skip to content
Extraits de code Groupes Projets
Sélectionner une révision Git
  • 9a5d8d487fc2ccf301ed7fef0cc34414d4e79f64
  • master par défaut protégée
2 résultats

plotout.c

Blame
  • Audio.cc 11,04 Kio
    #include "Audio.hh"
    
    #include <QJsonObject>
    #include <QJsonArray>
    
    using namespace Vivy;
    
    // AudioContext class implementation
    
    // Create an audio contecxt from a file
    AudioContext::AudioContext(const QString &path)
        : filePath(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();
    }
    
    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;
    }
    
    // AudioContext::Stream 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)
    {
        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");
    
        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
    {
        if (dataPtr) {
            qDebug() << "Free data ptr";
            free(dataPtr);
        }
        qDebug() << "Delete stream object";
    }
    
    QJsonObject
    AudioContext::Stream::getProperties() const noexcept
    {
        QJsonObject ret;
        ret.insert("Codec name", codec->name);
        ret.insert("Sample rate", codecParams->sample_rate);
        ret.insert("Bit rate", static_cast<int>(codecParams->bit_rate));
        ret.insert("Channels", codecParams->channels);
        return ret;
    }
    
    // Decode the data, don't call it if data already decoded
    void
    AudioContext::Stream::decodeData()
    {
        if (isDecoded())
            throw std::logic_error("audio stream is already resampled");
        qDebug() << "Launch decoding of stream" << streamIndexInAudioContext;
    
        AVPacket packet;
        av_init_packet(&packet);
    
        // Iterate through frames
        while (av_read_frame(dataFormat, &packet) >= 0) {
            // Only decode audio
            if (packet.stream_index != streamIndexInAudioContext) {
                av_packet_unref(&packet);
                continue;
            }
    
            // Decode one frame
            int response = avcodec_send_packet(codecContext.get(), &packet);
            if (response < 0) [[unlikely]] {
                throw std::runtime_error(
                    QStringLiteral("error n°%1 while sending a packet to the decoder")
                        .arg(response)
                        .toStdString());
            }
    
            double *buffer        = nullptr;
            int oldFrameNbSamples = 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]] {
                    throw std::runtime_error(
                        QStringLiteral("error n°%1 while receiving a from from the decoder")
                            .arg(response)
                            .toStdString());
                }
    
                // Reuse buffer if possible
                if (oldFrameNbSamples < dataFrame->nb_samples) {
                    dataDeleter(buffer);
                    oldFrameNbSamples = dataFrame->nb_samples;
                    av_samples_alloc(reinterpret_cast<uint8_t **>(&buffer), nullptr, 1,
                                     dataFrame->nb_samples, AV_SAMPLE_FMT_DBL, 0);
                }
    
                // Resample frame
                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");
                }
    
                // Append resampled frames to data
                else [[likely]] {
                    const size_t frame_count = static_cast<size_t>(frame_count_int);
                    dataPtr                  = reinterpret_cast<double *>(
                        realloc(dataPtr, (dataSize + (size_t)dataFrame->nb_samples) * sizeof(double)));
                    memcpy(dataPtr + dataSize, buffer, frame_count * sizeof(double));
                    dataSize += frame_count;
                }
            }
    
            dataDeleter(buffer);
            av_packet_unref(&packet);
        }
    
        qDebug() << "Decoding data finished for stream" << streamIndexInAudioContext
                 << "dataPtr =" << dataPtr << "with dataSize =" << dataSize;
    }
    
    // Delete decoded data, clean up thing
    void
    AudioContext::Stream::cleanUpData() noexcept
    {
        free(dataPtr);
        dataPtr  = nullptr;
        dataSize = 0;
    }
    
    // Get the number of channels in the audio context stream
    int
    AudioContext::Stream::getChannels() const noexcept
    {
        return codecContext->channels;
    }
    
    // Get the sample rate
    int
    AudioContext::Stream::getSampleRate() const noexcept
    {
        return codecContext->sample_rate;
    }
    
    // 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
    {
        return resamplerSampleRate;
    }
    
    // Get the name of the codec used to decode the stream
    QString
    AudioContext::Stream::getName() const noexcept
    {
        return QString(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
    {
        return codecContext->bit_rate;
    }
    
    quint64
    AudioContext::Stream::getLength() const noexcept
    {
        // The whole file duration, not individual streams.
        return quint64(std::chrono::duration<qreal,std::ratio<1,1000>>(std::chrono::microseconds(dataFormat->duration)).count());
    }
    
    // Get the information about the decoded state of this stream
    bool
    AudioContext::Stream::isDecoded() const noexcept
    {
        return dataPtr != nullptr;
    }
    
    // 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
    {
        return dataSize;
    }
    
    // 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
    {
        return dataPtr;
    }
    
    // Get the chunk size of the decoded data
    size_t
    AudioContext::Stream::getDecodedChunkSize() const noexcept
    {
        return 512;
    }
    
    // Get the decalage of the decoded data
    size_t
    AudioContext::Stream::getDecodedDecalage() const noexcept
    {
        constexpr size_t overlap = 128; // The overlap
        return getDecodedChunkSize() - overlap;
    }
    
    int
    AudioContext::Stream::getStreamIndex() const noexcept
    {
        return streamIndexInAudioContext;
    }