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 }