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 : }
|