Opus improve error handling

This commit is contained in:
badaix 2019-11-03 20:23:54 +01:00
parent 6cd3b7df67
commit 7dffa6ed65
2 changed files with 31 additions and 21 deletions

View file

@ -61,7 +61,7 @@ bool OpusDecoder::decode(msg::PcmChunk* chunk)
if (frame_size < 0) if (frame_size < 0)
{ {
LOG(ERROR) << "Failed to decode chunk: " << frame_size << '\n' << " IN size: " << chunk->payloadSize << '\n' << " OUT size: " << pcm_.size() << '\n'; LOG(ERROR) << "Failed to decode chunk: " << opus_strerror(frame_size) << ", IN size: " << chunk->payloadSize << ", OUT size: " << pcm_.size() << '\n';
return false; return false;
} }
else else
@ -98,13 +98,13 @@ SampleFormat OpusDecoder::setHeader(msg::CodecHeader* chunk)
memcpy(&channels, chunk->payload + 10, sizeof(channels)); memcpy(&channels, chunk->payload + 10, sizeof(channels));
sample_format_.setFormat(SWAP_32(rate), SWAP_16(bits), SWAP_16(channels)); sample_format_.setFormat(SWAP_32(rate), SWAP_16(bits), SWAP_16(channels));
LOG(INFO) << "Opus sampleformat: " << sample_format_.getFormat() << "\n"; LOG(DEBUG) << "Opus sampleformat: " << sample_format_.getFormat() << "\n";
// create the decoder // create the decoder
int error; int error;
dec_ = opus_decoder_create(sample_format_.rate, sample_format_.channels, &error); dec_ = opus_decoder_create(sample_format_.rate, sample_format_.channels, &error);
if (error != 0) if (error != 0)
throw SnapException("Failed to initialize Opus decoder: " + cpt::to_string(error)); throw SnapException("Failed to initialize Opus decoder: " + std::string(opus_strerror(error)));
return sample_format_; return sample_format_;
} }

View file

@ -23,6 +23,8 @@
#include "common/utils/string_utils.hpp" #include "common/utils/string_utils.hpp"
#define ID_OPUS 0x4F505553 #define ID_OPUS 0x4F505553
static constexpr opus_int32 const_min_bitrate = 6000;
static constexpr opus_int32 const_max_bitrate = 512000;
namespace namespace
{ {
@ -37,7 +39,6 @@ void assign(void* pointer, T val)
// TODO: // TODO:
// - handle variable chunk durations (now it's fixed to 10ms) // - handle variable chunk durations (now it's fixed to 10ms)
// const msg::SampleFormat& format);
OpusEncoder::OpusEncoder(const std::string& codecOptions) : Encoder(codecOptions), enc_(nullptr) OpusEncoder::OpusEncoder(const std::string& codecOptions) : Encoder(codecOptions), enc_(nullptr)
{ {
headerChunk_.reset(new msg::CodecHeader("opus")); headerChunk_.reset(new msg::CodecHeader("opus"));
@ -53,15 +54,16 @@ OpusEncoder::~OpusEncoder()
std::string OpusEncoder::getAvailableOptions() const std::string OpusEncoder::getAvailableOptions() const
{ {
return "BR:[6 - 512|MAX|AUTO],COMPLEXITY:[1-10]"; return "BITRATE:[" + cpt::to_string(const_min_bitrate) + " - " + cpt::to_string(const_max_bitrate) + "|MAX|AUTO],COMPLEXITY:[1-10]";
} }
std::string OpusEncoder::getDefaultOptions() const std::string OpusEncoder::getDefaultOptions() const
{ {
return "BR:192,COMPLEXITY:10"; return "BITRATE:192000,COMPLEXITY:10";
} }
std::string OpusEncoder::name() const std::string OpusEncoder::name() const
{ {
return "opus"; return "opus";
@ -70,15 +72,22 @@ std::string OpusEncoder::name() const
void OpusEncoder::initEncoder() void OpusEncoder::initEncoder()
{ {
// Opus is quite restrictive in sample rate and bit depth
// It can handle mono signals, but we will check for stereo
if ((sampleFormat_.rate != 48000) || (sampleFormat_.bits != 16) || (sampleFormat_.channels != 2))
throw SnapException("Opus sampleformat must be 48000:16:2");
opus_int32 bitrate = 192000; opus_int32 bitrate = 192000;
opus_int32 complexity = 10; opus_int32 complexity = 10;
// parse options: bitrate and complexity
auto options = utils::string::split(codecOptions_, ','); auto options = utils::string::split(codecOptions_, ',');
for (const auto& option : options) for (const auto& option : options)
{ {
auto kv = utils::string::split(option, ':'); auto kv = utils::string::split(option, ':');
if (kv.size() == 2) if (kv.size() == 2)
{ {
if (kv.front() == "BR") if (kv.front() == "BITRATE")
{ {
if (kv.back() == "MAX") if (kv.back() == "MAX")
bitrate = OPUS_BITRATE_MAX; bitrate = OPUS_BITRATE_MAX;
@ -89,13 +98,14 @@ void OpusEncoder::initEncoder()
try try
{ {
bitrate = cpt::stoi(kv.back()); bitrate = cpt::stoi(kv.back());
if ((bitrate < 6) || (bitrate > 512)) if ((bitrate < const_min_bitrate) || (bitrate > const_max_bitrate))
throw SnapException("Opus bitrate must be between 6 and 512"); throw SnapException("Opus bitrate must be between " + cpt::to_string(const_min_bitrate) + " and " +
bitrate *= 1000; cpt::to_string(const_max_bitrate));
} }
catch (const std::invalid_argument&) catch (const std::invalid_argument&)
{ {
throw SnapException("Opus error parsing bitrate (must be between 6 and 512): " + kv.back()); throw SnapException("Opus error parsing bitrate (must be between " + cpt::to_string(const_min_bitrate) + " and " +
cpt::to_string(const_max_bitrate) + "): " + kv.back());
} }
} }
} }
@ -125,14 +135,11 @@ void OpusEncoder::initEncoder()
enc_ = opus_encoder_create(sampleFormat_.rate, sampleFormat_.channels, OPUS_APPLICATION_RESTRICTED_LOWDELAY, &error); enc_ = opus_encoder_create(sampleFormat_.rate, sampleFormat_.channels, OPUS_APPLICATION_RESTRICTED_LOWDELAY, &error);
if (error != 0) if (error != 0)
{ {
throw SnapException("Failed to initialize Opus encoder: " + cpt::to_string(error)); throw SnapException("Failed to initialize Opus encoder: " + std::string(opus_strerror(error)));
} }
opus_encoder_ctl(enc_, OPUS_SET_BITRATE(bitrate)); // max 512000, OPUS_BITRATE_MAX, OPUS_AUTO opus_encoder_ctl(enc_, OPUS_SET_BITRATE(bitrate));
opus_encoder_ctl(enc_, OPUS_SET_COMPLEXITY(complexity)); // max 10 opus_encoder_ctl(enc_, OPUS_SET_COMPLEXITY(complexity));
if ((sampleFormat_.rate != 48000) || (sampleFormat_.bits != 16) || (sampleFormat_.channels != 2))
throw SnapException("Opus sampleformat must be 48000:16:2");
// create some opus pseudo header to let the decoder know about the sample format // create some opus pseudo header to let the decoder know about the sample format
headerChunk_->payloadSize = 12; headerChunk_->payloadSize = 12;
@ -147,12 +154,14 @@ void OpusEncoder::initEncoder()
void OpusEncoder::encode(const msg::PcmChunk* chunk) void OpusEncoder::encode(const msg::PcmChunk* chunk)
{ {
int samples = chunk->payloadSize / 4; int samples_per_channel = chunk->getFrameCount();
if (encoded_.size() < chunk->payloadSize) if (encoded_.size() < chunk->payloadSize)
encoded_.resize(chunk->payloadSize); encoded_.resize(chunk->payloadSize);
LOG(ERROR) << "samples / channel: " << samples_per_channel << ", bytes: " << chunk->payloadSize << '\n';
// encode // encode
opus_int32 len = opus_encode(enc_, (opus_int16*)chunk->payload, samples, encoded_.data(), chunk->payloadSize);
opus_int32 len = opus_encode(enc_, (opus_int16*)chunk->payload, samples_per_channel, encoded_.data(), chunk->payloadSize);
LOG(DEBUG) << "Encoded: size " << chunk->payloadSize << " bytes, encoded: " << len << " bytes" << '\n'; LOG(DEBUG) << "Encoded: size " << chunk->payloadSize << " bytes, encoded: " << len << " bytes" << '\n';
if (len > 0) if (len > 0)
@ -162,10 +171,11 @@ void OpusEncoder::encode(const msg::PcmChunk* chunk)
opusChunk->payloadSize = len; opusChunk->payloadSize = len;
opusChunk->payload = (char*)realloc(opusChunk->payload, opusChunk->payloadSize); opusChunk->payload = (char*)realloc(opusChunk->payload, opusChunk->payloadSize);
memcpy(opusChunk->payload, encoded_.data(), len); memcpy(opusChunk->payload, encoded_.data(), len);
listener_->onChunkEncoded(this, opusChunk, (double)samples / ((double)sampleFormat_.rate / 1000.)); listener_->onChunkEncoded(this, opusChunk, (double)samples_per_channel / ((double)sampleFormat_.rate / 1000.));
} }
else else
{ {
LOG(ERROR) << "Failed to encode chunk: " << len << '\n' << " Frame size: " << samples / 2 << '\n' << " Max bytes: " << chunk->payloadSize << '\n'; LOG(ERROR) << "Failed to encode chunk: " << opus_strerror(len) << ", samples / channel: " << samples_per_channel << ", bytes: " << chunk->payloadSize
<< '\n';
} }
} }