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 "mp3encoder.h"
24 :
25 : #include <cmath>
26 :
27 : #ifdef SYSTEM_LAME
28 : # include <lame/lame.h>
29 : #else
30 : # include "lame.h"
31 : #endif
32 :
33 : #include "global/io/iodevice.h"
34 :
35 : #include "log.h"
36 :
37 : using namespace muse::audio;
38 : using namespace muse::audio::encode;
39 :
40 : struct LameHandler
41 : {
42 0 : explicit LameHandler(const SoundTrackFormat& format)
43 0 : {
44 0 : DO_ASSERT(flags);
45 :
46 0 : lame_set_errorf(flags, [](const char* msg, va_list /*ap*/) {
47 0 : LOGE() << msg;
48 0 : });
49 0 : lame_set_debugf(flags, [](const char* msg, va_list /*ap*/) {
50 0 : LOGD() << msg;
51 0 : });
52 0 : lame_set_msgf(flags, [](const char* msg, va_list /*ap*/) {
53 0 : LOGI() << msg;
54 0 : });
55 :
56 0 : lame_set_num_channels(flags, format.outputSpec.audioChannelCount);
57 0 : lame_set_in_samplerate(flags, format.outputSpec.sampleRate);
58 0 : lame_set_brate(flags, format.bitRate);
59 0 : }
60 :
61 0 : ~LameHandler() noexcept
62 : {
63 0 : lame_close(flags);
64 0 : }
65 :
66 0 : bool init()
67 : {
68 0 : return lame_init_params(flags) >= 0;
69 : }
70 :
71 : lame_global_flags* flags = lame_init();
72 : };
73 :
74 0 : Mp3Encoder::Mp3Encoder(const SoundTrackFormat& format, io::IODevice& dstDevice)
75 0 : : AbstractAudioEncoder(format), m_handler{std::make_unique<LameHandler>(format)}, m_dstDevice{&dstDevice}
76 : {
77 0 : DO_ASSERT(m_dstDevice);
78 0 : }
79 :
80 0 : Mp3Encoder::~Mp3Encoder() noexcept = default;
81 :
82 0 : bool Mp3Encoder::begin(const samples_t /*totalSamplesNumber*/)
83 : {
84 : // LAME (lame.h): mp3buf for one encode call — worst case ≈ 1.25 * num_samples_per_channel + 7200 bytes;
85 : // flush needs at least 7200. Same buffer is used for encode + lame_encode_flush, hence margin + ceil.
86 0 : const samples_t n = m_format.outputSpec.samplesPerChannel > 0 ? m_format.outputSpec.samplesPerChannel : 4096;
87 0 : const double sz = 7200.0 + 1.25 * static_cast<double>(n) + 7200.0;
88 0 : m_outputBuffer.resize(static_cast<size_t>(std::ceil(sz)) + 512);
89 :
90 0 : if (!m_handler->init()) {
91 0 : return false;
92 : }
93 :
94 0 : return true;
95 : }
96 :
97 0 : size_t Mp3Encoder::encode(const samples_t samplesPerChannel, const float* input)
98 : {
99 0 : if (samplesPerChannel > m_format.outputSpec.samplesPerChannel) {
100 0 : LOGE() << "Chunk size exceeds buffer capacity";
101 0 : return 0;
102 : }
103 :
104 0 : const int encodedBytes = lame_encode_buffer_interleaved_ieee_float(m_handler->flags, input, static_cast<int>(samplesPerChannel),
105 : m_outputBuffer.data(),
106 0 : static_cast<int>(m_outputBuffer.size()));
107 :
108 0 : if (encodedBytes < 0) {
109 0 : LOGE() << "LAME encoder failed: " << encodedBytes;
110 0 : return 0;
111 : }
112 :
113 0 : if (encodedBytes > 0) {
114 0 : const size_t written = m_dstDevice->write(m_outputBuffer.data(), static_cast<size_t>(encodedBytes));
115 0 : if (written != static_cast<size_t>(encodedBytes)) {
116 0 : return 0;
117 : }
118 : }
119 :
120 0 : return samplesPerChannel;
121 : }
122 :
123 0 : size_t Mp3Encoder::end()
124 : {
125 0 : const int encodedBytes = lame_encode_flush(m_handler->flags,
126 : m_outputBuffer.data(),
127 0 : static_cast<int>(m_outputBuffer.size()));
128 0 : if (encodedBytes > 0) {
129 0 : m_dstDevice->write(m_outputBuffer.data(), static_cast<size_t>(encodedBytes));
130 : }
131 :
132 0 : return 0;
133 : }
|