File indexing completed on 2024-04-28 04:51:59

0001 /*
0002     SPDX-FileCopyrightText: 2012 Simon Andreas Eugster <simon.eu@gmail.com>
0003     This file is part of kdenlive. See www.kdenlive.org.
0004 
0005 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0006 */
0007 
0008 #include "audioEnvelope.h"
0009 #include "audioStreamInfo.h"
0010 #include "bin/bin.h"
0011 #include "bin/projectclip.h"
0012 #include "core.h"
0013 #include "kdenlive_debug.h"
0014 #include <KLocalizedString>
0015 #include <QElapsedTimer>
0016 #include <QImage>
0017 #include <QtConcurrent>
0018 #include <algorithm>
0019 #include <cmath>
0020 
0021 AudioEnvelope::AudioEnvelope(const QString &binId, int clipId, size_t offset, size_t length, size_t startPos)
0022     : m_offset(offset)
0023     , m_clipId(clipId)
0024     , m_startpos(startPos)
0025 {
0026     std::shared_ptr<ProjectClip> clip = pCore->bin()->getBinClip(binId);
0027     m_producer = clip->cloneProducer();
0028     if (length > 2000) {
0029         // Analyse on timeline clip zone only
0030         m_offset = 0;
0031         m_producer->set_in_and_out(int(offset), int(offset + length));
0032     }
0033     m_envelopeSize = size_t(m_producer->get_playtime());
0034 
0035     m_producer->set("set.test_image", 1);
0036     connect(&m_watcher, &QFutureWatcherBase::finished, this, [this] { Q_EMIT envelopeReady(this); });
0037     if (!m_producer || !m_producer->is_valid()) {
0038         qCDebug(KDENLIVE_LOG) << "// Cannot create envelope for producer: " << binId;
0039     } else {
0040         m_info = std::make_unique<AudioInfo>(m_producer);
0041     }
0042 }
0043 
0044 AudioEnvelope::~AudioEnvelope()
0045 {
0046     if (hasComputationStarted()) {
0047         // This is better than nothing, but does not seem enough to
0048         // guarantee safe deletion of the AudioEnvelope while the
0049         // computations are running: if the computations have just
0050         // finished, m_watcher might be finished, but the signal
0051         // 'envelopeReady' might still be pending while AudioEnvelope is
0052         // being deleted, which can cause a crash according to
0053         // https://doc.qt.io/qt-5/qobject.html#dtor.QObject.
0054         m_audioSummary.waitForFinished();
0055         m_watcher.waitForFinished();
0056     }
0057 }
0058 
0059 void AudioEnvelope::startComputeEnvelope()
0060 {
0061 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
0062     m_audioSummary = QtConcurrent::run(this, &AudioEnvelope::loadAndNormalizeEnvelope);
0063 #else
0064     m_audioSummary = QtConcurrent::run(&AudioEnvelope::loadAndNormalizeEnvelope, this);
0065 #endif
0066     m_watcher.setFuture(m_audioSummary);
0067 }
0068 
0069 bool AudioEnvelope::hasComputationStarted() const
0070 {
0071     // An empty qFuture is canceled. QtConcurrent::run() returns a
0072     // future that does not support cancellation, so this is a good way
0073     // to check whether the computations have started.
0074     return !m_audioSummary.isCanceled();
0075 }
0076 
0077 const AudioEnvelope::AudioSummary &AudioEnvelope::audioSummary()
0078 {
0079     Q_ASSERT(hasComputationStarted());
0080     m_audioSummary.waitForFinished();
0081     Q_ASSERT(m_audioSummary.constBegin() != m_audioSummary.constEnd());
0082     // We use this instead of m_audioSummary.result() in order to return
0083     // a const reference instead of a copy.
0084     return *m_audioSummary.constBegin();
0085 }
0086 
0087 size_t AudioEnvelope::offset()
0088 {
0089     return m_offset;
0090 }
0091 
0092 const std::vector<qint64> &AudioEnvelope::envelope()
0093 {
0094     // Blocks until the summary is available.
0095     return audioSummary().audioAmplitudes;
0096 }
0097 
0098 AudioEnvelope::AudioSummary AudioEnvelope::loadAndNormalizeEnvelope() const
0099 {
0100     qCDebug(KDENLIVE_LOG) << "Loading envelope …";
0101     AudioSummary summary(m_envelopeSize);
0102     if (!m_info || m_info->size() < 1) {
0103         return summary;
0104     }
0105     int samplingRate = m_info->info(0)->samplingRate();
0106     mlt_audio_format format_s16 = mlt_audio_s16;
0107     int channels = 1;
0108 
0109     QElapsedTimer t;
0110     t.start();
0111     m_producer->seek(0);
0112     size_t max = summary.audioAmplitudes.size();
0113     for (size_t i = 0; i < max; ++i) {
0114         std::unique_ptr<Mlt::Frame> frame(m_producer->get_frame(int(i)));
0115         qint64 position = mlt_frame_get_position(frame->get_frame());
0116         int samples = mlt_audio_calculate_frame_samples(float(m_producer->get_fps()), samplingRate, position);
0117         auto *data = static_cast<qint16 *>(frame->get_audio(format_s16, samplingRate, channels, samples));
0118 
0119         summary.audioAmplitudes[i] = 0;
0120         for (int k = 0; k < samples; ++k) {
0121             summary.audioAmplitudes[i] += abs(data[k]);
0122         }
0123         pCore->displayMessage(i18n("Processing data analysis"), ProcessingJobMessage, int(100 * i / max));
0124     }
0125     qCDebug(KDENLIVE_LOG) << "Calculating the envelope (" << m_envelopeSize << " frames) took " << t.elapsed() << " ms.";
0126     qCDebug(KDENLIVE_LOG) << "Normalizing envelope …";
0127     const qint64 meanBeforeNormalization =
0128         std::accumulate(summary.audioAmplitudes.begin(), summary.audioAmplitudes.end(), 0LL) / qint64(summary.audioAmplitudes.size());
0129 
0130     // Normalize the envelope.
0131     summary.amplitudeMax = 0;
0132     for (size_t i = 0; i < max; ++i) {
0133         summary.audioAmplitudes[i] -= meanBeforeNormalization;
0134         summary.amplitudeMax = std::max(summary.amplitudeMax, qAbs(summary.audioAmplitudes[i]));
0135     }
0136     pCore->displayMessage(i18n("Audio analysis finished"), OperationCompletedMessage, 300);
0137     return summary;
0138 }
0139 
0140 int AudioEnvelope::clipId() const
0141 {
0142     return m_clipId;
0143 }
0144 
0145 size_t AudioEnvelope::startPos() const
0146 {
0147     return m_startpos;
0148 }
0149 
0150 QImage AudioEnvelope::drawEnvelope()
0151 {
0152     const AudioSummary &summary = audioSummary();
0153 
0154     QImage img(int(m_envelopeSize), 400, QImage::Format_ARGB32);
0155     img.fill(qRgb(255, 255, 255));
0156 
0157     if (summary.amplitudeMax == 0) {
0158         return img;
0159     }
0160 
0161     for (int x = 0; x < img.width(); ++x) {
0162         double fy = double(summary.audioAmplitudes[size_t(x)]) / summary.amplitudeMax * img.height();
0163         for (int y = img.height() - 1; y > img.height() - 1 - fy; --y) {
0164             img.setPixel(x, y, qRgb(50, 50, 50));
0165         }
0166     }
0167     return img;
0168 }
0169 
0170 void AudioEnvelope::dumpInfo()
0171 {
0172     if (!m_audioSummary.isFinished()) {
0173         qCDebug(KDENLIVE_LOG) << "Envelope not yet generated, no information available.";
0174     } else {
0175         const AudioSummary &summary = audioSummary();
0176         qCDebug(KDENLIVE_LOG) << "Envelope info"
0177                               << "\n* size = " << summary.audioAmplitudes.size() << "\n* max = " << summary.amplitudeMax;
0178     }
0179 }