diff options
author | Stéphane Lepin <stephane.lepin@gmail.com> | 2017-08-15 11:00:34 +0200 |
---|---|---|
committer | Stéphane Lepin <stephane.lepin@gmail.com> | 2018-12-31 15:36:46 +0100 |
commit | 792e3e6d9a79560615960c855eaf9c8772ade299 (patch) | |
tree | a0b810a6488417a7f773400b396894640fc7e832 /src/encoder | |
parent | 18e9314383de7ee83af146a44b380c80298b0d55 (diff) |
Opus encoder
Diffstat (limited to 'src/encoder')
-rw-r--r-- | src/encoder/encoder.cpp | 41 | ||||
-rw-r--r-- | src/encoder/encoderopus.cpp | 463 | ||||
-rw-r--r-- | src/encoder/encoderopus.h | 58 | ||||
-rw-r--r-- | src/encoder/encoderopussettings.cpp | 155 | ||||
-rw-r--r-- | src/encoder/encoderopussettings.h | 60 |
5 files changed, 766 insertions, 11 deletions
diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp index 87b9522494..77cbe87f58 100644 --- a/src/encoder/encoder.cpp +++ b/src/encoder/encoder.cpp @@ -22,11 +22,17 @@ #endif #include "encoder/encoderwave.h" #include "encoder/encodersndfileflac.h" + #include "encoder/encodermp3settings.h" #include "encoder/encodervorbissettings.h" #include "encoder/encoderwavesettings.h" #include "encoder/encoderflacsettings.h" +#ifdef __OPUS__ +#include "encoder/encoderopus.h" +#include "encoder/encoderopussettings.h" +#endif + #include <QList> EncoderFactory EncoderFactory::factory; @@ -38,11 +44,15 @@ const EncoderFactory& EncoderFactory::getFactory() EncoderFactory::EncoderFactory() { // Add new supported formats here. Also modify the getNewEncoder/getEncoderSettings method. - m_formats.append(Encoder::Format("WAV PCM",ENCODING_WAVE, true)); - m_formats.append(Encoder::Format("AIFF PCM",ENCODING_AIFF, true)); + m_formats.append(Encoder::Format("WAV PCM", ENCODING_WAVE, true)); + m_formats.append(Encoder::Format("AIFF PCM", ENCODING_AIFF, true)); m_formats.append(Encoder::Format("FLAC", ENCODING_FLAC, true)); - m_formats.append(Encoder::Format("MP3",ENCODING_MP3, false)); - m_formats.append(Encoder::Format("OGG Vorbis",ENCODING_OGG, false)); + m_formats.append(Encoder::Format("MP3", ENCODING_MP3, false)); + m_formats.append(Encoder::Format("OGG Vorbis", ENCODING_OGG, false)); + +#ifdef __OPUS__ + m_formats.append(Encoder::Format("Opus", ENCODING_OPUS, false)); +#endif } const QList<Encoder::Format> EncoderFactory::getFormats() const @@ -86,20 +96,27 @@ EncoderPointer EncoderFactory::getNewEncoder(Encoder::Format format, pEncoder = std::make_shared<EncoderSndfileFlac>(pCallback); pEncoder->setEncoderSettings(EncoderFlacSettings(pConfig)); } else if (format.internalName == ENCODING_MP3) { - #ifdef __FFMPEGFILE_ENCODERS__ +#ifdef __FFMPEGFILE_ENCODERS__ pEncoder = std::make_shared<EncoderFfmpegMp3>(pCallback); - #else +#else pEncoder = std::make_shared<EncoderMp3>(pCallback); - #endif +#endif pEncoder->setEncoderSettings(EncoderMp3Settings(pConfig)); } else if (format.internalName == ENCODING_OGG) { - #ifdef __FFMPEGFILE_ENCODERS__ +#ifdef __FFMPEGFILE_ENCODERS__ pEncoder = std::make_shared<EncoderFfmpegVorbis>(pCallback); - #else +#else pEncoder = std::make_shared<EncoderVorbis>(pCallback); - #endif +#endif pEncoder->setEncoderSettings(EncoderVorbisSettings(pConfig)); - } else { + } +#ifdef __OPUS__ + else if (format.internalName == ENCODING_OPUS) { + pEncoder = std::make_shared<EncoderOpus>(pCallback); + pEncoder->setEncoderSettings(EncoderOpusSettings(pConfig)); + } +#endif + else { qWarning() << "Unsupported format requested! " << format.internalName; DEBUG_ASSERT(false); pEncoder = std::make_shared<EncoderWave>(pCallback); @@ -121,6 +138,8 @@ EncoderSettingsPointer EncoderFactory::getEncoderSettings(Encoder::Format format return std::make_shared<EncoderMp3Settings>(pConfig); } else if (format.internalName == ENCODING_OGG) { return std::make_shared<EncoderVorbisSettings>(pConfig); + } else if (format.internalName == ENCODING_OPUS) { + return std::make_shared<EncoderOpusSettings>(pConfig); } else { qWarning() << "Unsupported format requested! " << format.internalName; DEBUG_ASSERT(false); diff --git a/src/encoder/encoderopus.cpp b/src/encoder/encoderopus.cpp new file mode 100644 index 0000000000..f932169137 --- /dev/null +++ b/src/encoder/encoderopus.cpp @@ -0,0 +1,463 @@ +// encoderopus.cpp +// Create on August 15th 2017 by Palakis + +#include <stdlib.h> +#include <QByteArray> +#include <QMapIterator> +#include <QtGlobal> + +#include "encoder/encoderopussettings.h" +#include "engine/sidechain/enginesidechain.h" +#include "util/logger.h" + +#include "encoder/encoderopus.h" + +namespace { +// From libjitsi's Opus encoder: +// 1 byte TOC + maximum frame size (1275) +// See https://tools.ietf.org/html/rfc6716#section-3.2 +const int kMaxOpusBufferSize = 1+1275; +// Opus frame duration in milliseconds. Fixed to 60ms +const int kOpusFrameMs = 60; +// Opus only supports these samplerates +const int kOpusSamplerate = 48000; + +const mixxx::Logger kLogger("EncoderOpus"); +} + +EncoderOpus::EncoderOpus(EncoderCallback* pCallback) + : m_bitrate(0), + m_bitrateMode(0), + m_channels(0), + m_samplerate(0), + m_readRequired(0), + m_pCallback(pCallback), + m_fifoBuffer(EngineSideChain::SIDECHAIN_BUFFER_SIZE * 2), + m_pFifoChunkBuffer(nullptr), + m_pOpus(nullptr), + m_opusDataBuffer(kMaxOpusBufferSize), + m_header_write(false), + m_packetNumber(0), + m_granulePos(0) +{ + // Regarding m_pFifoBuffer: + // Size the input FIFO buffer with twice the maximum possible sample count that can be + // processed at once, to avoid skipping frames or waiting for the required sample count + // and encode at a regular pace. + // This is set to the buffer size of the sidechain engine because + // Recording (which uses this engine) sends more samples at once to the encoder than + // the Live Broadcasting implementation + + m_opusComments.insert("ENCODER", "mixxx/libopus"); + ogg_stream_init(&m_oggStream, qrand()); +} + +EncoderOpus::~EncoderOpus() { + if (m_pOpus) { + opus_encoder_destroy(m_pOpus); + } + + ogg_stream_clear(&m_oggStream); + delete m_pFifoChunkBuffer; +} + +void EncoderOpus::setEncoderSettings(const EncoderSettings& settings) { + m_bitrate = settings.getQuality(); + m_bitrateMode = settings.getSelectedOption(EncoderOpusSettings::BITRATE_MODE_GROUP); + switch (settings.getChannelMode()) { + case EncoderSettings::ChannelMode::MONO: + m_channels = 1; + break; + case EncoderSettings::ChannelMode::STEREO: + m_channels = 2; + break; + case EncoderSettings::ChannelMode::AUTOMATIC: + m_channels = 2; + break; + } +} + +int EncoderOpus::initEncoder(int samplerate, QString errorMessage) { + Q_UNUSED(errorMessage); + + if (samplerate != kOpusSamplerate) { + kLogger.warning() << "initEncoder failed: samplerate not supported by Opus"; + + QString invalidSamplerate = QObject::tr( + "Using Opus at samplerates other than 48 kHz " + "is not supported by the Opus encoder. Please use " + "48000 Hz in \"Sound Hardware\" preferences " + "or switch to a different encoding."); + + ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); + props->setType(DLG_WARNING); + props->setTitle(QObject::tr("Encoder")); + props->setText(invalidSamplerate); + props->setKey(invalidSamplerate); + ErrorDialogHandler::instance()->requestErrorDialog(props); + return -1; + } + m_samplerate = samplerate; + + int createResult = 0; + m_pOpus = opus_encoder_create(m_samplerate, m_channels, OPUS_APPLICATION_AUDIO, &createResult); + + if (createResult != OPUS_OK) { + kLogger.warning() << "opus_encoder_create failed:" << opusErrorString(createResult); + return -1; + } + + // Optimize encoding for high-quality music + opus_encoder_ctl(m_pOpus, OPUS_SET_COMPLEXITY(10)); // Highest setting + opus_encoder_ctl(m_pOpus, OPUS_SET_SIGNAL(OPUS_SIGNAL_MUSIC)); + + if(m_bitrateMode == OPUS_BITRATE_CONSTRAINED_VBR) { + // == Constrained VBR == + // Default mode, gives the best quality/bitrate compromise + opus_encoder_ctl(m_pOpus, OPUS_SET_BITRATE(m_bitrate * 1000)); // convert to bits/second + opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(1)); // default value in libopus + opus_encoder_ctl(m_pOpus, OPUS_SET_VBR_CONSTRAINT(1)); // Constrained VBR + } else if(m_bitrateMode == OPUS_BITRATE_CBR) { + // == CBR (quality loss at low bitrates) == + opus_encoder_ctl(m_pOpus, OPUS_SET_BITRATE(m_bitrate * 1000)); // convert to bits/second + opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(0)); + } else if(m_bitrateMode == OPUS_BITRATE_VBR) { + // == Full VBR == + opus_encoder_ctl(m_pOpus, OPUS_SET_VBR(1)); + opus_encoder_ctl(m_pOpus, OPUS_SET_VBR_CONSTRAINT(0)); // Unconstrained VBR + } + + double samplingPeriodMs = ( 1.0 / ((double)m_samplerate) ) * 1000.0; + double samplesPerChannel = kOpusFrameMs / samplingPeriodMs; + + m_readRequired = samplesPerChannel * m_channels; + m_pFifoChunkBuffer = new mixxx::SampleBuffer(m_readRequired); + initStream(); + + return 0; +} + +void EncoderOpus::initStream() { + ogg_stream_clear(&m_oggStream); + ogg_stream_init(&m_oggStream, getSerial()); + m_header_write = true; + m_granulePos = 0; + m_packetNumber = 0; + + pushHeaderPacket(); + pushTagsPacket(); +} + +// Binary header construction is done manually to properly +// handle endianness of multi-byte number fields +void EncoderOpus::pushHeaderPacket() { + // Opus identification header + // Format from https://tools.ietf.org/html/rfc7845.html#section-5.1 + + // Header buffer size: + // - Magic signature: 8 bytes + // - Version: 1 byte + // - Channel count: 1 byte + // - Pre-skip: 2 bytes + // - Samplerate: 4 bytes + // - Output Gain: 2 bytes + // - Mapping family: 1 byte + // - Channel mapping table: ignored + // Total: 19 bytes + const int frameSize = 19; + QByteArray frame; + + // Magic signature (8 bytes) + frame.append("OpusHead", 8); + + // Version number (1 byte, fixed to 1) + frame.append(0x01); + + // Channel count (1 byte) + frame.append((unsigned char)m_channels); + + // Pre-skip (2 bytes, little-endian) + int preskip = 0; + opus_encoder_ctl(m_pOpus, OPUS_GET_LOOKAHEAD(&preskip)); + for (int x = 0; x < 2; x++) { + unsigned char preskipByte = (preskip >> (x*8)) & 0xFF; + frame.append(preskipByte); + } + + // Sample rate (4 bytes, little endian) + for (int x = 0; x < 4; x++) { + unsigned char samplerateByte = (m_samplerate >> (x*8)) & 0xFF; + frame.append(samplerateByte); + } + + // Output gain (2 bytes, little-endian, fixed to 0) + frame.append((char)0x00); + frame.append((char)0x00); + + // Channel mapping (1 byte, fixed to 0, means one stream) + frame.append((char)0x00); + + // Ignore channel mapping table + + // Assert the built frame is of correct size + int actualFrameSize = frame.size(); + if (actualFrameSize != frameSize) { + kLogger.warning() << + QString("pushHeaderPacket: wrong frame size! expected: %1 - actual: %2") + .arg(frameSize).arg(actualFrameSize); + } + + // Push finished header to stream + ogg_packet packet; + packet.b_o_s = 1; + packet.e_o_s = 0; + packet.granulepos = 0; + packet.packetno = m_packetNumber++; + packet.packet = (unsigned char*)frame.data(); + packet.bytes = frameSize; + + if (ogg_stream_packetin(&m_oggStream, &packet) != 0) { + // return value != 0 means an internal error happened + kLogger.warning() << + "pushHeaderPacket: failed to send packet to Ogg stream"; + } +} + +void EncoderOpus::pushTagsPacket() { + // Opus comment header + // Format from https://tools.ietf.org/html/rfc7845.html#section-5.2 + + QByteArray combinedComments; + int commentCount = 0; + + const char* vendorString = opus_get_version_string(); + int vendorStringLength = strlen(vendorString); + + // == Compute tags frame size == + // - Magic signature: 8 bytes + // - Vendor string length: 4 bytes + // - Vendor string: dynamic size + // - Comment list length: 4 bytes + int frameSize = 8 + 4 + vendorStringLength + 4; + // - Comment list: dynamic size + QMapIterator<QString, QString> iter(m_opusComments); + while(iter.hasNext()) { + iter.next(); + QString comment = iter.key() + "=" + iter.value(); + QByteArray commentBytes = comment.toUtf8(); + int commentBytesLength = commentBytes.size(); + + // One comment is: + // - 4 bytes of string length + // - string data + + // Add comment length field and data to comments "list" + for (int x = 0; x < 4; x++) { + unsigned char fieldValue = (commentBytesLength >> (x*8)) & 0xFF; + combinedComments.append(fieldValue); + } + combinedComments.append(commentBytes); + + // Don't forget to include this comment in the overall size calculation + frameSize += (4 + commentBytesLength); + commentCount++; + } + + // == Actual frame building == + QByteArray frame; + + // Magic signature (8 bytes) + frame.append("OpusTags", 8); + + // Vendor string (mandatory) + // length field (4 bytes, little-endian) + actual string + // Write length field + for (int x = 0; x < 4; x++) { + unsigned char lengthByte = (vendorStringLength >> (x*8)) & 0xFF; + frame.append(lengthByte); + } + // Write string + frame.append(vendorString, vendorStringLength); + + // Number of comments (4 bytes, little-endian) + for (int x = 0; x < 4; x++) { + unsigned char commentCountByte = (commentCount >> (x*8)) & 0xFF; + frame.append(commentCountByte); + } + + // Comment list (dynamic size) + int commentListLength = combinedComments.size(); + frame.append(combinedComments.constData(), commentListLength); + + // Assert the built frame is of correct size + int actualFrameSize = frame.size(); + if (actualFrameSize != frameSize) { + kLogger.warning() << + QString("pushTagsPacket: wrong frame size! expected: %1 - actual: %2") + .arg(frameSize).arg(actualFrameSize); + } + + // Push finished tags frame to stream + ogg_packet packet; + packet.b_o_s = 0; + packet.e_o_s = 0; + packet.granulepos = 0; + packet.packetno = m_packetNumber++; + packet.packet = (unsigned char*)frame.data(); + packet.bytes = frameSize; + + if (ogg_stream_packetin(&m_oggStream, &packet) != 0) { + // return value != 0 means an internal error happened + kLogger.warning() << + "pushTagsPacket: failed to send packet to Ogg stream"; + } +} + +void EncoderOpus::encodeBuffer(const CSAMPLE *samples, const int size) { + if (!m_pOpus) { + return; + } + + int writeRequired = size; + int writeAvailable = m_fifoBuffer.writeAvailable(); + if (writeRequired > writeAvailable) { + kLogger.warning() << "FIFO buffer too small, loosing samples!" + << "required:" << writeRequired + << "; available: " << writeAvailable; + } + + int writeCount = math_min(writeRequired, writeAvailable); + if (writeCount > 0) { + m_fifoBuffer.write(samples, writeCount); + } + + processFIFO(); +} + +void EncoderOpus::processFIFO() { + while (m_fifoBuffer.readAvailable() >= m_readRequired) { + m_fifoBuffer.read(m_pFifoChunkBuffer->data(), m_readRequired); + + if ((m_readRequired % m_channels) != 0) { + kLogger.warning() << "processFIFO: channel count doesn't match chunk size"; + } + + int samplesPerChannel = m_readRequired / m_channels; + int result = opus_encode_float(m_pOpus, + m_pFifoChunkBuffer->data(), samplesPerChannel, + m_opusDataBuffer.data(), kMaxOpusBufferSize); + + if (result < 1) { + kLogger.warning() << "opus_encode_float failed:" << opusErrorString(result); + return; + } + + ogg_packet packet; + packet.b_o_s = 0; + packet.e_o_s = 0; + packet.granulepos = m_granulePos; + packet.packetno = m_packetNumber; + packet.packet = m_opusDataBuffer.data(); + packet.bytes = result; + + m_granulePos += samplesPerChannel; + m_packetNumber += 1; + + writePage(&packet); + } +} + +void EncoderOpus::writePage(ogg_packet* pPacket) { + if (!pPacket) { + return; + } + + // Push headers prepared by initStream if not already done + if (m_header_write) { + while (true) { + int result = ogg_stream_flush(&m_oggStream, &m_oggPage); + if (result == 0) + break; + + kLogger.debug() << "pushing headers to output"; + m_pCallback->write(m_oggPage.header, m_oggPage.body, + m_oggPage.header_len, m_oggPage.body_len); + } + m_header_write = false; + } + + // Push Opus Ogg packets to the stream + if (ogg_stream_packetin(&m_oggStream, pPacket) != 0) { + // return value != 0 means an internal error happened + kLogger.warning() << + "writePage: failed to send packet to Ogg stream"; + } + + // Try to send available Ogg pages to the output + do { + if (ogg_stream_pageout(&m_oggStream, &m_oggPage) == 0) { + break; + } + + m_pCallback->write(m_oggPage.header, m_oggPage.body, + m_oggPage.header_len, m_oggPage.body_len); + } while(!ogg_page_eos(&m_oggPage)); +} + +void EncoderOpus::updateMetaData(const QString& artist, const QString& title, const QString& album) { + m_opusComments.insert("ARTIST", artist); + m_opusComments.insert("TITLE", title); + m_opusComments.insert("ALBUM", album); +} + +void EncoderOpus::flush() { + // At this point there may still be samples in the FIFO buffer + processFIFO(); +} + +QString EncoderOpus::opusErrorString(int error) { + QString errorString = ""; + switch (error) { + case OPUS_OK: + errorString = "OPUS_OK"; + break; + case OPUS_BAD_ARG: + errorString = "OPUS_BAD_ARG"; + break; + case OPUS_BUFFER_TOO_SMALL: + errorString = "OPUS_BUFFER_TOO_SMALL"; + break; + case OPUS_INTERNAL_ERROR: + errorString = "OPUS_INTERNAL_ERROR"; + break; + case OPUS_INVALID_PACKET: + errorString = "OPUS_INVALID_PACKET"; + break; + case OPUS_UNIMPLEMENTED: + errorString = "OPUS_UNIMPLEMENTED"; + break; + case OPUS_INVALID_STATE: + errorString = "OPUS_INVALID_STATE"; + break; + case OPUS_ALLOC_FAIL: + errorString = "OPUS_ALLOC_FAIL"; + break; + default: + return "Unknown error"; + } + return errorString + (QString(" (%1)").arg(error)); +} + +int EncoderOpus::getSerial() { + static int prevSerial = 0; + + int serial; + do { + serial = qrand(); + } while(prevSerial == serial); + + prevSerial = serial; + kLogger.debug() << "RETURNING SERIAL " << serial; + return serial; +} + diff --git a/src/encoder/encoderopus.h b/src/encoder/encoderopus.h new file mode 100644 index 0000000000..1122a3c2e7 --- /dev/null +++ b/src/encoder/encoderopus.h @@ -0,0 +1,58 @@ +// encoderopus.h +// Create on August 15th 2017 by Palakis + +#ifndef ENCODER_ENCODEROPUS_H +#define ENCODER_ENCODEROPUS_H + +#include <QMap> +#include <QString> +#include <QVector> + +#include <ogg/ogg.h> +#include <opus/opus.h> + +#include "encoder/encoder.h" +#include "encoder/encodercallback.h" +#include "util/fifo.h" +#include "util/sample.h" +#include "util/samplebuffer.h" + +class EncoderOpus: public Encoder { + public: + EncoderOpus(EncoderCallback* pCallback = nullptr); + virtual ~EncoderOpus(); + + int initEncoder(int samplerate, QString errorMessage) override; + void encodeBuffer(const CSAMPLE *samples, const int size) override; + void updateMetaData(const QString& artist, const QString& title, const QString& album) override; + void flush() override; + void setEncoderSettings(const EncoderSettings& settings) override; + + private: + static QString opusErrorString(int error); + static int getSerial(); + void initStream(); + void pushHeaderPacket(); + void pushTagsPacket(); + void writePage(ogg_packet* pPacket); + void processFIFO(); + + int m_bitrate; + int m_bitrateMode; + int m_channels; + int m_samplerate; + int m_readRequired; + EncoderCallback* m_pCallback; + FIFO<CSAMPLE> m_fifoBuffer; + mixxx::SampleBuffer* m_pFifoChunkBuffer; + OpusEncoder* m_pOpus; + QVector<unsigned char> m_opusDataBuffer; + ogg_stream_state m_oggStream; + ogg_page m_oggPage; + bool m_header_write; + int m_packetNumber; + ogg_int64_t m_granulePos; + QMap<QString, QString> m_opusComments; +}; + +#endif // ENCODER_ENCODEROPUS_H diff --git a/src/encoder/encoderopussettings.cpp b/src/encoder/encoderopussettings.cpp new file mode 100644 index 0000000000..ec47fda2ce --- /dev/null +++ b/src/encoder/encoderopussettings.cpp @@ -0,0 +1,155 @@ +// encoderopussettings.cpp +// Create on August 15th 2017 by Palakis + +#include <QMap> + +#include "encoder/encoderopussettings.h" +#include "recording/defs_recording.h" +#include "util/logger.h" + +namespace { +const int kDefaultQualityIndex = 6; +const char* kQualityKey = "Opus_Quality"; +const mixxx::Logger kLogger("EncoderOpusSettings"); +} + +const QString EncoderOpusSettings::BITRATE_MODE_GROUP = "Opus_BitrateMode"; + +EncoderOpusSettings::EncoderOpusSettings(UserSettingsPointer pConfig) + : m_pConfig(pConfig) { + m_qualList.append(32); + m_qualList.append(48); + m_qualList.append(64); + m_qualList.append(80); + m_qualList.append(96); + m_qualList.append(112); + m_qualList.append(128); + m_qualList.append(160); + m_qualList.append(192); + m_qualList.append(224); + m_qualList.append(256); + m_qualList.append(320); + m_qualList.append(510); // Max Opus bitrate + + QMap<int, QString> modes; + modes.insert(OPUS_BITRATE_CONSTRAINED_VBR, + QObject::tr("Constrained VBR")); + modes.insert(OPUS_BITRATE_CBR, QObject::tr("CBR")); + modes.insert(OPUS_BITRATE_VBR, QObject::tr("Full VBR (bitrate ignored)")); + + // OptionsGroup needs a QList<string> for the option list. Using QMap::values() + // ensures that the returned QList<string> will be sorted in ascending key order, which + // is what we want to be able to match OPUS_BITRATE_* number defines to the right mode + // in EncoderOpus. + m_radioList.append(OptionsGroup( + QObject::tr("Bitrate Mode"), BITRATE_MODE_GROUP, modes.values())); +} + +EncoderOpusSettings::~EncoderOpusSettings() { +} + +QList<int> EncoderOpusSettings::getQualityValues() const { + return m_qualList; +} + +void EncoderOpusSettings::setQualityByValue(int qualityValue) { + // Same behavior as Vorbis: Opus does not have a fixed set of + // bitrates, so we can accept any value. + int indexValue; + if (m_qualList.contains(qualityValue)) { + indexValue = m_qualList.indexOf(qualityValue); + } else { + // If we let the user write a bitrate value, this would allow to save such value. + indexValue = qualityValue; + } + + m_pConfig->setValue<int>(ConfigKey(RECORDING_PREF_KEY, kQualityKey), indexValue); +} + +void EncoderOpusSettings::setQualityByIndex(int qualityIndex) { + if (qualityIndex >= 0 && qualityIndex < m_qualList.size()) { + m_pConfig->setValue<int>(ConfigKey(RECORDING_PREF_KEY, kQualityKey), qualityIndex); + } else { + kLogger.warning() << "Invalid qualityIndex given to EncoderVorbisSettings: " + << qualityIndex << ". Ignoring it"; + } +} +int EncoderOpusSettings::getQuality() const { + int qualityIndex = m_pConfig->getValue( + ConfigKey(RECORDING_PREF_KEY, kQualityKey), kDefaultQualityIndex); + + if (qualityIndex >= 0 && qualityIndex < m_qualList.size()) { + return m_qualList.at(qualityIndex); + } else { + // Same as Vorbis: Opus does not have a fixed set of + // bitrates, so we can accept any value. + return qualityIndex; + } +} + +int EncoderOpusSettings::getQualityIndex() const { + int qualityIndex = m_pConfig->getValue( + ConfigKey(RECORDING_PREF_KEY, kQualityKey), kDefaultQualityIndex); + + if (qualityIndex >= 0 && qualityIndex < m_qualList.size()) { + return qualityIndex; + } else { + kLogger.warning() << "Invalid qualityIndex in EncoderVorbisSettings " + << qualityIndex << "(Max is:" + << m_qualList.size() << ") . Ignoring it and" + << "returning default which is" << kDefaultQualityIndex; + return kDefaultQualityIndex; + } +} + +QList<EncoderSettings::OptionsGroup> EncoderOpusSettings::getOptionGroups() const { + return m_radioList; +} + +void EncoderOpusSettings::setGroupOption(QString groupCode, int optionIndex) { + bool found = false; + for (OptionsGroup group : m_radioList) { + if (groupCode == group.groupCode) { + found = true; + if (optionIndex < group.controlNames.size() || optionIndex == 1) { + m_pConfig->set( + ConfigKey(RECORDING_PREF_KEY, BITRATE_MODE_GROUP), + ConfigValue(optionIndex)); + } else { + kLogger.warning() + << "Received an index out of range for:" + << groupCode << ", index:" << optionIndex; + } + } + } + + if (!found) { + kLogger.warning() + << "Received an unknown groupCode on setGroupOption:" << groupCode; + } +} + +int EncoderOpusSettings::getSelectedOption(QString groupCode) const { + int value = m_pConfig->getValue( + ConfigKey(RECORDING_PREF_KEY, BITRATE_MODE_GROUP), 0); + + bool found = false; + for (OptionsGroup group : m_radioList) { + if (groupCode == group.groupCode) { + found = true; + if (value >= group.controlNames.size() && value > 1) { + kLogger.warning() + << "Value saved for" << groupCode + << "on preferences is out of range" << value << ". Returning 0"; + value = 0; + } + } + } + + if (!found) { + kLogger.warning() + << "Received an unknown groupCode on getSelectedOption:" << groupCode; + } + + return value; +} diff --git a/src/encoder/encoderopussettings.h b/src/encoder/encoderopussettings.h new file mode 100644 index 0000000000..576e4efc66 --- /dev/null +++ b/src/encoder/encoderopussettings.h @@ -0,0 +1,60 @@ +// encoderopussettings.h +// Create on August 15th 2017 by Palakis + +#ifndef ENCODER_ENCODEROPUSSETTINGS_H +#define ENCODER_ENCODEROPUSSETTINGS_H + +#include "encoder/encodersettings.h" +#include "encoder/encoder.h" + +#define OPUS_BITRATE_MODES_COUNT 3 +#define OPUS_BITRATE_CONSTRAINED_VBR 0 +#define OPUS_BITRATE_CBR 1 +#define OPUS_BITRATE_VBR 2 + +class EncoderOpusSettings: public EncoderSettings { + public: + EncoderOpusSettings(UserSettingsPointer pConfig); + virtual ~EncoderOpusSettings(); + + // Indicates that it uses the quality slider section of the preferences + bool usesQualitySlider() const override { + return true; + } + // Indicates that it uses the compression slider section of the preferences + bool usesCompressionSlider() const override { + return false; + } + // Indicates that it uses the radio button section of the preferences. + bool usesOptionGroups() const override { + return true; + } + + // Returns the list of quality values that it supports, to assign them to the slider + QList<int> getQualityValues() const override; + // Sets the quality value by its value + void setQualityByValue(int qualityValue) override; + // Sets the quality value by its index + void setQualityByIndex(int qualityIndex) override; + // Returns the current quality value + int getQuality() const override; + int getQualityIndex() const override; + + // Returns the list of radio options to show to the user + QList<OptionsGroup> getOptionGroups() const override; + // Selects the option by its index. If it is a single-element option, + // index 0 means disabled and 1 enabled. + void setGroupOption(QString groupCode, int optionIndex) override; + // Return the selected option of the group. If it is a single-element option, + // 0 means disabled and 1 enabled. + int getSelectedOption(QString groupCode) const override; + + static const QString BITRATE_MODE_GROUP; + + private: + UserSettingsPointer m_pConfig; + QList<int> m_qualList; + QList<OptionsGroup> m_radioList; +}; + +#endif // ENCODER_ENCODEROPUSSETTINGS_H |