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