mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-15 18:16:44 +02:00
Opus encoder handles arbitrary chunk sizes
This commit is contained in:
parent
10db57406b
commit
b8be0e5349
2 changed files with 72 additions and 14 deletions
|
@ -22,6 +22,8 @@
|
|||
#include "common/str_compat.hpp"
|
||||
#include "common/utils/string_utils.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace encoder
|
||||
{
|
||||
|
||||
|
@ -42,7 +44,7 @@ void assign(void* pointer, T val)
|
|||
|
||||
OpusEncoder::OpusEncoder(const std::string& codecOptions) : Encoder(codecOptions), enc_(nullptr)
|
||||
{
|
||||
headerChunk_.reset(new msg::CodecHeader("opus"));
|
||||
headerChunk_ = make_unique<msg::CodecHeader>("opus");
|
||||
}
|
||||
|
||||
|
||||
|
@ -150,28 +152,82 @@ void OpusEncoder::initEncoder()
|
|||
assign(payload + 4, SWAP_32(sampleFormat_.rate));
|
||||
assign(payload + 8, SWAP_16(sampleFormat_.bits));
|
||||
assign(payload + 10, SWAP_16(sampleFormat_.channels));
|
||||
|
||||
remainder_ = std::make_unique<msg::PcmChunk>(sampleFormat_, 10);
|
||||
remainder_max_size_ = remainder_->payloadSize;
|
||||
remainder_->payloadSize = 0;
|
||||
}
|
||||
|
||||
|
||||
// TODO:
|
||||
// handle variable chunk durations, now it's fixed to a "stream_buffer" value of
|
||||
// 5, 10, 20, 40, 60
|
||||
// Opus encoder can only handle chunk sizes of:
|
||||
// 5, 10, 20, 40, 60 ms
|
||||
// 240, 480, 960, 1920, 2880 frames
|
||||
// We will split the chunk into encodable sizes and store any remaining data in the remainder_ buffer
|
||||
// and encode the buffer content in the next iteration
|
||||
void OpusEncoder::encode(const msg::PcmChunk* chunk)
|
||||
{
|
||||
int samples_per_channel = chunk->getFrameCount();
|
||||
if (encoded_.size() < chunk->payloadSize)
|
||||
encoded_.resize(chunk->payloadSize);
|
||||
LOG(DEBUG) << "encode " << chunk->duration<std::chrono::milliseconds>().count() << "ms\n";
|
||||
uint32_t offset = 0;
|
||||
|
||||
LOG(ERROR) << "samples / channel: " << samples_per_channel << ", bytes: " << chunk->payloadSize << '\n';
|
||||
// encode
|
||||
// check if there is something left from the last call to encode and fill the remainder buffer to
|
||||
// an encodable size of 10ms
|
||||
if (remainder_->payloadSize > 0)
|
||||
{
|
||||
offset = std::min(static_cast<uint32_t>(remainder_max_size_ - remainder_->payloadSize), chunk->payloadSize);
|
||||
memcpy(remainder_->payload + remainder_->payloadSize, chunk->payload, offset);
|
||||
LOG(DEBUG) << "remainder buffer size: " << remainder_->payloadSize << "/" << remainder_max_size_ << ", appending " << offset << " bytes\n";
|
||||
remainder_->payloadSize += offset;
|
||||
|
||||
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';
|
||||
if (remainder_->payloadSize < remainder_max_size_)
|
||||
{
|
||||
LOG(DEBUG) << "not enough data to encode (" << remainder_->payloadSize << " of " << remainder_max_size_ << " bytes)"
|
||||
<< "\n";
|
||||
return;
|
||||
}
|
||||
encode(chunk->format, remainder_->payload, remainder_->payloadSize);
|
||||
remainder_->payloadSize = 0;
|
||||
}
|
||||
|
||||
// encode greedy 60ms, 40ms, 20ms, 10ms chunks
|
||||
std::vector<size_t> chunk_durations{60, 40, 20, 10};
|
||||
for (const auto duration : chunk_durations)
|
||||
{
|
||||
auto ms2bytes = [this](size_t ms) { return (ms * sampleFormat_.msRate() * sampleFormat_.frameSize); };
|
||||
uint32_t bytes = ms2bytes(duration);
|
||||
while (chunk->payloadSize - offset >= bytes)
|
||||
{
|
||||
LOG(DEBUG) << "encoding " << duration << "ms (" << bytes << "), offset: " << offset << ", chunk size: " << chunk->payloadSize - offset << "\n";
|
||||
encode(chunk->format, chunk->payload + offset, bytes);
|
||||
offset += bytes;
|
||||
}
|
||||
if (chunk->payloadSize == offset)
|
||||
break;
|
||||
}
|
||||
|
||||
// something is left (must be less than 10ms)
|
||||
if (chunk->payloadSize > offset)
|
||||
{
|
||||
memcpy(remainder_->payload + remainder_->payloadSize, chunk->payload + offset, chunk->payloadSize - offset);
|
||||
remainder_->payloadSize = chunk->payloadSize - offset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OpusEncoder::encode(const SampleFormat& format, const char* data, size_t size)
|
||||
{
|
||||
// void* buffer;
|
||||
// LOG(INFO) << "frames: " << chunk->readFrames(buffer, std::chrono::milliseconds(10)) << "\n";
|
||||
int samples_per_channel = size / format.frameSize;
|
||||
if (encoded_.size() < size)
|
||||
encoded_.resize(size);
|
||||
|
||||
opus_int32 len = opus_encode(enc_, (opus_int16*)data, samples_per_channel, encoded_.data(), size);
|
||||
LOG(DEBUG) << "Encode " << samples_per_channel << " frames, size " << size << " bytes, encoded: " << len << " bytes" << '\n';
|
||||
|
||||
if (len > 0)
|
||||
{
|
||||
// copy encoded data to chunk
|
||||
auto* opusChunk = new msg::PcmChunk(chunk->format, 0);
|
||||
auto* opusChunk = new msg::PcmChunk(format, 0);
|
||||
opusChunk->payloadSize = len;
|
||||
opusChunk->payload = (char*)realloc(opusChunk->payload, opusChunk->payloadSize);
|
||||
memcpy(opusChunk->payload, encoded_.data(), len);
|
||||
|
@ -179,8 +235,7 @@ void OpusEncoder::encode(const msg::PcmChunk* chunk)
|
|||
}
|
||||
else
|
||||
{
|
||||
LOG(ERROR) << "Failed to encode chunk: " << opus_strerror(len) << ", samples / channel: " << samples_per_channel << ", bytes: " << chunk->payloadSize
|
||||
<< '\n';
|
||||
LOG(ERROR) << "Failed to encode chunk: " << opus_strerror(len) << ", samples / channel: " << samples_per_channel << ", bytes: " << size << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue