LCOV - code coverage report
Current view: top level - muse/framework/audio/engine/internal/export - soundtrackwriter.cpp Coverage Total Hit
Test: filtered_coverage.info Lines: 0.0 % 100 0
Test Date: 2026-11-05 04:07:02

            Line data    Source code
       1              : /*
       2              :  * SPDX-License-Identifier: GPL-3.0-only
       3              :  * MuseScore-CLA-applies
       4              :  *
       5              :  * MuseScore Studio
       6              :  * Music Composition & Notation
       7              :  *
       8              :  * Copyright (C) 2021 MuseScore Limited and others
       9              :  *
      10              :  * This program is free software: you can redistribute it and/or modify
      11              :  * it under the terms of the GNU General Public License version 3 as
      12              :  * published by the Free Software Foundation.
      13              :  *
      14              :  * This program is distributed in the hope that it will be useful,
      15              :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      16              :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17              :  * GNU General Public License for more details.
      18              :  *
      19              :  * You should have received a copy of the GNU General Public License
      20              :  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
      21              :  */
      22              : 
      23              : #include "soundtrackwriter.h"
      24              : 
      25              : #include <algorithm>
      26              : #include <cstdint>
      27              : 
      28              : #include "global/defer.h"
      29              : 
      30              : #include "audio/common/audioerrors.h"
      31              : 
      32              : #include "mp3encoder.h"
      33              : #include "oggencoder.h"
      34              : #include "flacencoder.h"
      35              : #include "wavencoder.h"
      36              : #include "aacencoder.h"
      37              : 
      38              : #include "log.h"
      39              : 
      40              : using namespace muse;
      41              : using namespace muse::audio;
      42              : using namespace muse::audio::soundtrack;
      43              : 
      44            0 : static encode::AbstractAudioEncoderPtr createEncoder(const SoundTrackFormat& format, io::IODevice& dstDevice)
      45              : {
      46            0 :     switch (format.type) {
      47            0 :     case SoundTrackType::MP3: return std::make_unique<encode::Mp3Encoder>(format, dstDevice);
      48            0 :     case SoundTrackType::OGG: return std::make_unique<encode::OggEncoder>(format, dstDevice);
      49            0 :     case SoundTrackType::FLAC: return std::make_unique<encode::FlacEncoder>(format, dstDevice);
      50            0 :     case SoundTrackType::WAV: return std::make_unique<encode::WavEncoder>(format, dstDevice);
      51            0 :     case SoundTrackType::AAC: return std::make_unique<encode::AacEncoder>(format, dstDevice);
      52            0 :     case SoundTrackType::Undefined: break;
      53              :     }
      54              : 
      55            0 :     UNREACHABLE;
      56              :     return nullptr;
      57              : }
      58              : 
      59            0 : SoundTrackWriter::SoundTrackWriter(io::IODevice& dstDevice, const SoundTrackFormat& format,
      60            0 :                                    const secs_t totalDuration, IAudioNodePtr source)
      61            0 :     : m_source(std::move(source))
      62              : {
      63            0 :     if (!m_source) {
      64            0 :         return;
      65              :     }
      66            0 :     IF_ASSERT_FAILED(format.isValid()) {
      67            0 :         return;
      68              :     }
      69              : 
      70            0 :     const OutputSpec& outputSpec = format.outputSpec;
      71              : 
      72            0 :     auto durationToSamples = [&](const secs_t& duration) {
      73            0 :         const double sec = std::max(0.0, duration.raw());
      74            0 :         return static_cast<samples_t>(std::llround(sec * static_cast<double>(outputSpec.sampleRate)));
      75            0 :     };
      76              : 
      77            0 :     m_dataSamples = durationToSamples(totalDuration);
      78            0 :     m_leadingSilenceSamples = durationToSamples(format.leadingSilenceDuration);
      79            0 :     const samples_t trailingSilenceSamples = durationToSamples(format.trailingSilenceDuration);
      80            0 :     m_totalSamples = m_leadingSilenceSamples + m_dataSamples + trailingSilenceSamples;
      81              : 
      82            0 :     const samples_t intermediateSamplesNumber = outputSpec.samplesPerChannel * outputSpec.audioChannelCount;
      83            0 :     m_intermBuffer.resize(intermediateSamplesNumber);
      84            0 :     m_renderStep = outputSpec.samplesPerChannel;
      85              : 
      86            0 :     m_encoderPtr = createEncoder(format, dstDevice);
      87            0 :     if (!m_encoderPtr) {
      88            0 :         return;
      89              :     }
      90              : 
      91            0 :     if (!m_encoderPtr->begin(m_totalSamples)) {
      92            0 :         m_encoderPtr.reset();
      93              :     }
      94            0 : }
      95              : 
      96            0 : Ret SoundTrackWriter::write()
      97              : {
      98            0 :     TRACEFUNC;
      99              : 
     100            0 :     if (!m_source || !m_encoderPtr) {
     101            0 :         return false;
     102              :     }
     103              : 
     104            0 :     m_source->setOutputSpec(m_encoderPtr->format().outputSpec);
     105            0 :     m_source->setMode(ProcessMode::PlayingOffline);
     106              : 
     107            0 :     DEFER {
     108            0 :         if (!m_isAborted) {
     109            0 :             m_encoderPtr->end();
     110              :         }
     111              : 
     112            0 :         m_isAborted = false;
     113            0 :     };
     114              : 
     115            0 :     Ret ret = writeStreaming();
     116            0 :     if (!ret) {
     117            0 :         return ret;
     118              :     }
     119              : 
     120            0 :     return muse::make_ok();
     121            0 : }
     122              : 
     123            0 : void SoundTrackWriter::abort()
     124              : {
     125            0 :     m_isAborted = true;
     126            0 : }
     127              : 
     128            0 : Progress SoundTrackWriter::progress()
     129              : {
     130            0 :     return m_progress;
     131              : }
     132              : 
     133            0 : Ret SoundTrackWriter::writeStreaming()
     134              : {
     135            0 :     TRACEFUNC;
     136            0 :     if (m_totalSamples == 0) {
     137            0 :         LOGI() << "No audio to export";
     138            0 :         return make_ret(Err::NoAudioToExport);
     139              :     }
     140              : 
     141            0 :     samples_t framesWritten = 0;
     142              : 
     143            0 :     sendProgress(0, m_totalSamples);
     144              : 
     145              :     // Phase 1: leading silence
     146            0 :     const samples_t leadingEnd = m_leadingSilenceSamples;
     147            0 :     while (framesWritten < leadingEnd && !m_isAborted) {
     148              :         const samples_t chunk = static_cast<samples_t>(
     149            0 :             std::min<uint64_t>(m_renderStep, leadingEnd - framesWritten));
     150              : 
     151            0 :         std::fill(m_intermBuffer.begin(), m_intermBuffer.end(), 0.f);
     152              : 
     153            0 :         const size_t encoded = m_encoderPtr->encode(chunk, m_intermBuffer.data());
     154            0 :         if (encoded == 0) {
     155            0 :             return make_ret(Err::ErrorEncode);
     156              :         }
     157              : 
     158            0 :         framesWritten += chunk;
     159            0 :         sendProgress(framesWritten, m_totalSamples);
     160            0 :         rpcChannel()->process();
     161              :     }
     162              : 
     163              :     // Phase 2: actual audio data
     164            0 :     const samples_t audioEnd = m_leadingSilenceSamples + m_dataSamples;
     165            0 :     while (framesWritten < audioEnd && !m_isAborted) {
     166              :         const samples_t chunk = static_cast<samples_t>(
     167            0 :             std::min<uint64_t>(m_renderStep, audioEnd - framesWritten));
     168              : 
     169              :         //! NOTE The mixer mixes additively and relies on the caller to zero the output buffer
     170              :         //! (real time does this via AudioEngine::fillSilent), so clear it before each block.
     171            0 :         std::fill(m_intermBuffer.begin(), m_intermBuffer.end(), 0.f);
     172              : 
     173            0 :         m_source->process(m_intermBuffer.data(), chunk);
     174              : 
     175            0 :         const size_t encoded = m_encoderPtr->encode(chunk, m_intermBuffer.data());
     176            0 :         if (encoded == 0) {
     177            0 :             return make_ret(Err::ErrorEncode);
     178              :         }
     179              : 
     180            0 :         framesWritten += chunk;
     181            0 :         sendProgress(framesWritten, m_totalSamples);
     182            0 :         rpcChannel()->process();
     183              :     }
     184              : 
     185              :     // Phase 3: trailing silence
     186            0 :     while (framesWritten < m_totalSamples && !m_isAborted) {
     187              :         const samples_t chunk = static_cast<samples_t>(
     188            0 :             std::min<uint64_t>(m_renderStep, m_totalSamples - framesWritten));
     189              : 
     190            0 :         std::fill(m_intermBuffer.begin(), m_intermBuffer.end(), 0.f);
     191              : 
     192            0 :         const size_t encoded = m_encoderPtr->encode(chunk, m_intermBuffer.data());
     193            0 :         if (encoded == 0) {
     194            0 :             return make_ret(Err::ErrorEncode);
     195              :         }
     196              : 
     197            0 :         framesWritten += chunk;
     198            0 :         sendProgress(framesWritten, m_totalSamples);
     199            0 :         rpcChannel()->process();
     200              :     }
     201              : 
     202            0 :     if (m_isAborted) {
     203            0 :         return make_ret(Ret::Code::Cancel);
     204              :     }
     205              : 
     206            0 :     return muse::make_ok();
     207            0 : }
     208              : 
     209            0 : void SoundTrackWriter::sendProgress(uint64_t framesWritten, uint64_t totalFrames)
     210              : {
     211            0 :     const int current = totalFrames > 0 ? static_cast<int>((framesWritten * 100) / totalFrames) : 0;
     212            0 :     m_progress.progress(current, 100);
     213            0 : }
        

Generated by: LCOV version 2.0-1