1 #include "encoder/encoderopus.h"
2
3 #include <stdlib.h>
4
5 #include <QByteArray>
6 #include <QMapIterator>
7
8 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
9 #include <QRandomGenerator>
10 #endif
11 #include <QtGlobal>
12
13 #include "encoder/encoderopussettings.h"
14 #include "engine/sidechain/enginesidechain.h"
15 #include "util/logger.h"
16
17 namespace {
18 // From libjitsi's Opus encoder:
19 // 1 byte TOC + maximum frame size (1275)
20 // See https://tools.ietf.org/html/rfc6716#section-3.2
21 constexpr int kMaxOpusBufferSize = 1+1275;
22 // Opus frame duration in milliseconds. Fixed to 60ms
23 constexpr int kOpusFrameMs = 60;
24 constexpr int kOpusChannelCount = 2;
25 // Opus only supports 48 and 96 kHz samplerates
26 constexpr mixxx::audio::SampleRate kMasterSamplerate = mixxx::audio::SampleRate(48000);
27
28 const mixxx::Logger kLogger("EncoderOpus");
29
opusErrorString(int error)30 QString opusErrorString(int error) {
31 QString errorString = "";
32 switch (error) {
33 case OPUS_OK:
34 errorString = "OPUS_OK";
35 break;
36 case OPUS_BAD_ARG:
37 errorString = "OPUS_BAD_ARG";
38 break;
39 case OPUS_BUFFER_TOO_SMALL:
40 errorString = "OPUS_BUFFER_TOO_SMALL";
41 break;
42 case OPUS_INTERNAL_ERROR:
43 errorString = "OPUS_INTERNAL_ERROR";
44 break;
45 case OPUS_INVALID_PACKET:
46 errorString = "OPUS_INVALID_PACKET";
47 break;
48 case OPUS_UNIMPLEMENTED:
49 errorString = "OPUS_UNIMPLEMENTED";
50 break;
51 case OPUS_INVALID_STATE:
52 errorString = "OPUS_INVALID_STATE";
53 break;
54 case OPUS_ALLOC_FAIL:
55 errorString = "OPUS_ALLOC_FAIL";
56 break;
57 default:
58 return "Unknown error";
59 }
60 return errorString + (QString(" (%1)").arg(error));
61 }
62
getSerial()63 int getSerial() {
64 static int prevSerial = 0;
65
66 int serial;
67 do {
68 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
69 serial = static_cast<int>(QRandomGenerator::global()->generate());
70 #else
71 serial = qrand();
72 #endif
73 } while (prevSerial == serial);
74
75 prevSerial = serial;
76 kLogger.debug() << "RETURNING SERIAL " << serial;
77 return serial;
78 }
79 } // namespace
80
81 //static
getMasterSamplerate()82 mixxx::audio::SampleRate EncoderOpus::getMasterSamplerate() {
83 return kMasterSamplerate;
84 }
85
86 //static
getInvalidSamplerateMessage()87 QString EncoderOpus::getInvalidSamplerateMessage() {
88 return QObject::tr(
89 "Using Opus at samplerates other than 48 kHz "
90 "is not supported by the Opus encoder. Please use "
91 "48000 Hz in \"Sound Hardware\" preferences "
92 "or switch to a different encoding.");
93 };
94
EncoderOpus(EncoderCallback * pCallback)95 EncoderOpus::EncoderOpus(EncoderCallback* pCallback)
96 : m_bitrate(0),
97 m_bitrateMode(0),
98 m_channels(0),
99 m_samplerate(0),
100 m_readRequired(0),
101 m_pCallback(pCallback),
102 m_fifoBuffer(EngineSideChain::SIDECHAIN_BUFFER_SIZE * kOpusChannelCount),
103 m_pFifoChunkBuffer(),
104 m_pOpus(nullptr),
105 m_opusDataBuffer(kMaxOpusBufferSize),
106 m_header_write(false),
107 m_packetNumber(0),
108 m_granulePos(0)
109 {
110 // Regarding m_pFifoBuffer:
111 // Size the input FIFO buffer with twice the maximum possible sample count that can be
112 // processed at once, to avoid skipping frames or waiting for the required sample count
113 // and encode at a regular pace.
114 // This is set to the buffer size of the sidechain engine because
115 // Recording (which uses this engine) sends more samples at once to the encoder than
116 // the Live Broadcasting implementation
117
118 m_opusComments.insert("ENCODER", "mixxx/libopus");
119 #if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
120 int serial = static_cast<int>(QRandomGenerator::global()->generate());
121 #else
122 int serial = qrand();
123 #endif
124 ogg_stream_init(&m_oggStream, serial);
125 }
126
~EncoderOpus()127 EncoderOpus::~EncoderOpus() {
128 if (m_pOpus) {
129 opus_encoder_destroy(m_pOpus);
130 }
131
132 ogg_stream_clear(&m_oggStream);
133 }
134
setEncoderSettings(const EncoderSettings & settings)135 void EncoderOpus::setEncoderSettings(const EncoderSettings& settings) {
136 m_bitrate = settings.getQuality();
137 m_bitrateMode = settings.getSelectedOption(EncoderOpusSettings::BITRATE_MODE_GROUP);
138 switch (settings.getChannelMode()) {
139 case EncoderSettings::ChannelMode::MONO:
140 m_channels = 1;
141 break;
142 case EncoderSettings::ChannelMode::STEREO:
143 m_channels = 2;
144 break;
145 case EncoderSettings::ChannelMode::AUTOMATIC:
146 m_channels = 2;
147 break;
148 }
149 }
150
initEncoder(int samplerate,QString * pUserErrorMessage)151 int EncoderOpus::initEncoder(int samplerate, QString* pUserErrorMessage) {
152 Q_UNUSED(pUserErrorMessage);
153
154 if (samplerate != kMasterSamplerate) {
155 kLogger.warning() << "initEncoder failed: samplerate not supported by Opus";
156
157 const QString invalidSamplerateMessage = getInvalidSamplerateMessage();
158
159 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties();
160 props->setType(DLG_WARNING);
161 props->setTitle(QObject::tr("Encoder"));
162 props->setText(invalidSamplerateMessage);
163 props->setKey(invalidSamplerateMessage);
164 ErrorDialogHandler::instance()->requestErrorDialog(props);
165 return -1;
166 }
167 m_samplerate = samplerate;
168 DEBUG_ASSERT(m_samplerate == 8000 || m_samplerate == 12000 ||
169 m_samplerate == 16000 || m_samplerate == 24000 ||
170 m_samplerate == 48000);
171
172 int createResult = 0;
173 m_pOpus = opus_encoder_create(m_samplerate, m_channels, OPUS_APPLICATION_AUDIO, &createResult);
174
175 if (createResult != OPUS_OK) {
176 kLogger.warning() << "opus_encoder_create failed:" << opusErrorString(createResult);
177 return -1;
178 }
179
180 // Optimize encoding for high-quality music
181 opus_encoder_ctl(m_pOpus, OPUS_SET_COMPLEXITY(10)); // Highest setting
182 opus_encoder_ctl(m_pOpus, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC));
183
184 if(m_bitrateMode == OPUS_BITRATE_CONSTRAINED_VBR) {
185 // == Constrained VBR ==
186 // Default mode, gives the best quality/bitrate compromise
187 opus_encoder_ctl(m_pOpus, OPUS_SET_BITRATE(m_bitrate * 1000)); // convert to bits/second
188 opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(1)); // default value in libopus
189 opus_encoder_ctl(m_pOpus, OPUS_SET_VBR_CONSTRAINT(1)); // Constrained VBR
190 } else if(m_bitrateMode == OPUS_BITRATE_CBR) {
191 // == CBR (quality loss at low bitrates) ==
192 opus_encoder_ctl(m_pOpus, OPUS_SET_BITRATE(m_bitrate * 1000)); // convert to bits/second
193 opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(0));
194 } else if(m_bitrateMode == OPUS_BITRATE_VBR) {
195 // == Full VBR ==
196 opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(1));
197 opus_encoder_ctl(m_pOpus, OPUS_SET_VBR_CONSTRAINT(0)); // Unconstrained VBR
198 }
199
200 m_readRequired = m_samplerate * kOpusFrameMs;
201 m_pFifoChunkBuffer = std::make_unique<mixxx::SampleBuffer>(m_readRequired);
202 initStream();
203
204 return 0;
205 }
206
initStream()207 void EncoderOpus::initStream() {
208 ogg_stream_clear(&m_oggStream);
209 ogg_stream_init(&m_oggStream, getSerial());
210 m_header_write = true;
211 m_granulePos = 0;
212 m_packetNumber = 0;
213
214 pushHeaderPacket();
215 pushTagsPacket();
216 }
217
218 // Binary header construction is done manually to properly
219 // handle endianness of multi-byte number fields
pushHeaderPacket()220 void EncoderOpus::pushHeaderPacket() {
221 // Opus identification header
222 // Format from https://tools.ietf.org/html/rfc7845.html#section-5.1
223
224 // Header buffer size:
225 // - Magic signature: 8 bytes
226 // - Version: 1 byte
227 // - Channel count: 1 byte
228 // - Pre-skip: 2 bytes
229 // - Samplerate: 4 bytes
230 // - Output Gain: 2 bytes
231 // - Mapping family: 1 byte
232 // - Channel mapping table: ignored
233 // Total: 19 bytes
234 const int frameSize = 19;
235 QByteArray frame;
236
237 // Magic signature (8 bytes)
238 frame.append("OpusHead", 8);
239
240 // Version number (1 byte, fixed to 1)
241 frame.append(0x01);
242
243 // Channel count (1 byte)
244 frame.append(static_cast<unsigned char>(m_channels));
245
246 // Pre-skip (2 bytes, little-endian)
247 int preskip = 0;
248 opus_encoder_ctl(m_pOpus, OPUS_GET_LOOKAHEAD(&preskip));
249 for (int x = 0; x < 2; x++) {
250 unsigned char preskipByte = (preskip >> (x*8)) & 0xFF;
251 frame.append(preskipByte);
252 }
253
254 // Sample rate (4 bytes, little endian)
255 for (int x = 0; x < 4; x++) {
256 unsigned char samplerateByte = (m_samplerate >> (x*8)) & 0xFF;
257 frame.append(samplerateByte);
258 }
259
260 // Output gain (2 bytes, little-endian, fixed to 0)
261 frame.append((char)0x00);
262 frame.append((char)0x00);
263
264 // Channel mapping (1 byte, fixed to 0, means one stream)
265 frame.append((char)0x00);
266
267 // Ignore channel mapping table
268
269 // Assert the built frame is of correct size
270 int actualFrameSize = frame.size();
271 if (actualFrameSize != frameSize) {
272 kLogger.warning() <<
273 QString("pushHeaderPacket: wrong frame size! expected: %1 - actual: %2")
274 .arg(frameSize).arg(actualFrameSize);
275 }
276
277 // Push finished header to stream
278 ogg_packet packet;
279 packet.b_o_s = 1;
280 packet.e_o_s = 0;
281 packet.granulepos = 0;
282 packet.packetno = m_packetNumber++;
283 packet.packet = reinterpret_cast<unsigned char*>(frame.data());
284 packet.bytes = frameSize;
285
286 if (ogg_stream_packetin(&m_oggStream, &packet) != 0) {
287 // return value != 0 means an internal error happened
288 kLogger.warning() <<
289 "pushHeaderPacket: failed to send packet to Ogg stream";
290 }
291 }
292
pushTagsPacket()293 void EncoderOpus::pushTagsPacket() {
294 // Opus comment header
295 // Format from https://tools.ietf.org/html/rfc7845.html#section-5.2
296
297 QByteArray combinedComments;
298 int commentCount = 0;
299
300 const char* vendorString = opus_get_version_string();
301 int vendorStringLength = strlen(vendorString);
302
303 // == Compute tags frame size ==
304 // - Magic signature: 8 bytes
305 // - Vendor string length: 4 bytes
306 // - Vendor string: dynamic size
307 // - Comment list length: 4 bytes
308 int frameSize = 8 + 4 + vendorStringLength + 4;
309 // - Comment list: dynamic size
310 QMapIterator<QString, QString> iter(m_opusComments);
311 while(iter.hasNext()) {
312 iter.next();
313 QString comment = iter.key() + "=" + iter.value();
314 QByteArray commentBytes = comment.toUtf8();
315 int commentBytesLength = commentBytes.size();
316
317 // One comment is:
318 // - 4 bytes of string length
319 // - string data
320
321 // Add comment length field and data to comments "list"
322 for (int x = 0; x < 4; x++) {
323 unsigned char fieldValue = (commentBytesLength >> (x*8)) & 0xFF;
324 combinedComments.append(fieldValue);
325 }
326 combinedComments.append(commentBytes);
327
328 // Don't forget to include this comment in the overall size calculation
329 frameSize += (4 + commentBytesLength);
330 commentCount++;
331 }
332
333 // == Actual frame building ==
334 QByteArray frame;
335
336 // Magic signature (8 bytes)
337 frame.append("OpusTags", 8);
338
339 // Vendor string (mandatory)
340 // length field (4 bytes, little-endian) + actual string
341 // Write length field
342 for (int x = 0; x < 4; x++) {
343 unsigned char lengthByte = (vendorStringLength >> (x*8)) & 0xFF;
344 frame.append(lengthByte);
345 }
346 // Write string
347 frame.append(vendorString, vendorStringLength);
348
349 // Number of comments (4 bytes, little-endian)
350 for (int x = 0; x < 4; x++) {
351 unsigned char commentCountByte = (commentCount >> (x*8)) & 0xFF;
352 frame.append(commentCountByte);
353 }
354
355 // Comment list (dynamic size)
356 int commentListLength = combinedComments.size();
357 frame.append(combinedComments.constData(), commentListLength);
358
359 // Assert the built frame is of correct size
360 int actualFrameSize = frame.size();
361 if (actualFrameSize != frameSize) {
362 kLogger.warning() <<
363 QString("pushTagsPacket: wrong frame size! expected: %1 - actual: %2")
364 .arg(frameSize).arg(actualFrameSize);
365 }
366
367 // Push finished tags frame to stream
368 ogg_packet packet;
369 packet.b_o_s = 0;
370 packet.e_o_s = 0;
371 packet.granulepos = 0;
372 packet.packetno = m_packetNumber++;
373 packet.packet = reinterpret_cast<unsigned char*>(frame.data());
374 packet.bytes = frameSize;
375
376 if (ogg_stream_packetin(&m_oggStream, &packet) != 0) {
377 // return value != 0 means an internal error happened
378 kLogger.warning() <<
379 "pushTagsPacket: failed to send packet to Ogg stream";
380 }
381 }
382
encodeBuffer(const CSAMPLE * samples,const int size)383 void EncoderOpus::encodeBuffer(const CSAMPLE *samples, const int size) {
384 if (!m_pOpus) {
385 return;
386 }
387
388 int writeRequired = size;
389 int writeAvailable = m_fifoBuffer.writeAvailable();
390 if (writeRequired > writeAvailable) {
391 kLogger.warning() << "FIFO buffer too small, losing samples!"
392 << "required:" << writeRequired
393 << "; available: " << writeAvailable;
394 }
395
396 int writeCount = math_min(writeRequired, writeAvailable);
397 if (writeCount > 0) {
398 m_fifoBuffer.write(samples, writeCount);
399 }
400
401 processFIFO();
402 }
403
processFIFO()404 void EncoderOpus::processFIFO() {
405 while (m_fifoBuffer.readAvailable() >= m_readRequired) {
406 m_fifoBuffer.read(m_pFifoChunkBuffer->data(), m_readRequired);
407
408 if ((m_readRequired % m_channels) != 0) {
409 kLogger.warning() << "processFIFO: channel count doesn't match chunk size";
410 }
411
412 int samplesPerChannel = m_readRequired / m_channels;
413 int result = opus_encode_float(m_pOpus,
414 m_pFifoChunkBuffer->data(), samplesPerChannel,
415 m_opusDataBuffer.data(), kMaxOpusBufferSize);
416
417 if (result < 1) {
418 kLogger.warning() << "opus_encode_float failed:" << opusErrorString(result);
419 return;
420 }
421
422 ogg_packet packet;
423 packet.b_o_s = 0;
424 packet.e_o_s = 0;
425 packet.granulepos = m_granulePos;
426 packet.packetno = m_packetNumber;
427 packet.packet = m_opusDataBuffer.data();
428 packet.bytes = result;
429
430 m_granulePos += samplesPerChannel;
431 m_packetNumber += 1;
432
433 writePage(&packet);
434 }
435 }
436
writePage(ogg_packet * pPacket)437 void EncoderOpus::writePage(ogg_packet* pPacket) {
438 if (!pPacket) {
439 return;
440 }
441
442 // Push headers prepared by initStream if not already done
443 if (m_header_write) {
444 while (true) {
445 int result = ogg_stream_flush(&m_oggStream, &m_oggPage);
446 if (result == 0) {
447 break;
448 }
449
450 kLogger.debug() << "pushing headers to output";
451 m_pCallback->write(m_oggPage.header, m_oggPage.body,
452 m_oggPage.header_len, m_oggPage.body_len);
453 }
454 m_header_write = false;
455 }
456
457 // Push Opus Ogg packets to the stream
458 if (ogg_stream_packetin(&m_oggStream, pPacket) != 0) {
459 // return value != 0 means an internal error happened
460 kLogger.warning() <<
461 "writePage: failed to send packet to Ogg stream";
462 }
463
464 // Try to send available Ogg pages to the output
465 do {
466 if (ogg_stream_pageout(&m_oggStream, &m_oggPage) == 0) {
467 break;
468 }
469
470 m_pCallback->write(m_oggPage.header, m_oggPage.body,
471 m_oggPage.header_len, m_oggPage.body_len);
472 } while(!ogg_page_eos(&m_oggPage));
473 }
474
updateMetaData(const QString & artist,const QString & title,const QString & album)475 void EncoderOpus::updateMetaData(const QString& artist, const QString& title, const QString& album) {
476 m_opusComments.insert("ARTIST", artist);
477 m_opusComments.insert("TITLE", title);
478 m_opusComments.insert("ALBUM", album);
479 }
480
flush()481 void EncoderOpus::flush() {
482 // At this point there may still be samples in the FIFO buffer
483 processFIFO();
484 }
485