encoder/Interface: pass std::span to Write() and Read()

This commit is contained in:
Max Kellermann 2022-07-11 22:14:48 +02:00
parent 28e044a36a
commit 7e14f8f830
18 changed files with 118 additions and 115 deletions

View File

@ -21,6 +21,7 @@
#define MPD_ENCODER_INTERFACE_HXX #define MPD_ENCODER_INTERFACE_HXX
#include <cstddef> #include <cstddef>
#include <span>
struct AudioFormat; struct AudioFormat;
struct Tag; struct Tag;
@ -91,18 +92,19 @@ public:
* @param data the buffer containing PCM samples * @param data the buffer containing PCM samples
* @param length the length of the buffer in bytes * @param length the length of the buffer in bytes
*/ */
virtual void Write(const void *data, std::size_t length) = 0; virtual void Write(std::span<const std::byte> src) = 0;
/** /**
* Reads encoded data from the encoder. * Reads encoded data from the encoder.
* *
* Call this repeatedly until no more data is returned. * Call this repeatedly until no more data is returned.
* *
* @param dest the destination buffer to copy to * @param buffer a buffer that can be used to write data into
* @param length the maximum length of the destination buffer *
* @return the number of bytes written to #dest * @return the portion of the buffer that was filled (but may
* also point to a different buffer, e.g. one owned by this object)
*/ */
virtual std::size_t Read(void *dest, std::size_t length) noexcept = 0; virtual std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept = 0;
}; };
class PreparedEncoder { class PreparedEncoder {

View File

@ -28,12 +28,12 @@ EncoderToOutputStream(OutputStream &os, Encoder &encoder)
/* read from the encoder */ /* read from the encoder */
std::byte buffer[32768]; std::byte buffer[32768];
size_t nbytes = encoder.Read(buffer, sizeof(buffer)); const auto r = encoder.Read(std::span{buffer});
if (nbytes == 0) if (r.empty())
return; return;
/* write everything to the stream */ /* write everything to the stream */
os.Write(buffer, nbytes); os.Write(r.data(), r.size());
} }
} }

View File

@ -71,10 +71,10 @@ public:
void SendTag(const Tag &tag) override; void SendTag(const Tag &tag) override;
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
size_t Read(void *dest, size_t length) noexcept override { std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
return output_buffer.Read((std::byte *)dest, length); return buffer.first(output_buffer.Read(buffer.data(), buffer.size()));
} }
private: private:
@ -267,27 +267,27 @@ pcm16_to_flac(int32_t *out, const int16_t *in, std::size_t num_samples) noexcept
} }
void void
FlacEncoder::Write(const void *data, size_t length) FlacEncoder::Write(std::span<const std::byte> src)
{ {
void *exbuffer; void *exbuffer;
const void *buffer = nullptr; const void *buffer = nullptr;
/* format conversion */ /* format conversion */
const std::size_t num_frames = length / audio_format.GetFrameSize(); const std::size_t num_frames = src.size() / audio_format.GetFrameSize();
const std::size_t num_samples = num_frames * audio_format.channels; const std::size_t num_samples = num_frames * audio_format.channels;
switch (audio_format.format) { switch (audio_format.format) {
case SampleFormat::S8: case SampleFormat::S8:
exbuffer = expand_buffer.Get(length * 4); exbuffer = expand_buffer.Get(src.size() * 4);
pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)data, pcm8_to_flac((int32_t *)exbuffer, (const int8_t *)src.data(),
num_samples); num_samples);
buffer = exbuffer; buffer = exbuffer;
break; break;
case SampleFormat::S16: case SampleFormat::S16:
exbuffer = expand_buffer.Get(length * 2); exbuffer = expand_buffer.Get(src.size() * 2);
pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)data, pcm16_to_flac((int32_t *)exbuffer, (const int16_t *)src.data(),
num_samples); num_samples);
buffer = exbuffer; buffer = exbuffer;
break; break;
@ -296,7 +296,7 @@ FlacEncoder::Write(const void *data, size_t length)
case SampleFormat::S32: case SampleFormat::S32:
/* nothing need to be done; format is the same for /* nothing need to be done; format is the same for
both mpd and libFLAC */ both mpd and libFLAC */
buffer = data; buffer = src.data();
break; break;
default: default:

View File

@ -23,6 +23,7 @@
#include "util/NumberParser.hxx" #include "util/NumberParser.hxx"
#include "util/ReusableArray.hxx" #include "util/ReusableArray.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/SpanCast.hxx"
#include <lame/lame.h> #include <lame/lame.h>
@ -167,14 +168,14 @@ LameEncoder::~LameEncoder() noexcept
} }
void void
LameEncoder::Write(std::span<const std::byte> src) LameEncoder::Write(std::span<const std::byte> _src)
{ {
const auto *src = (const int16_t*)data; const auto src = FromBytesStrict<const int16_t>(_src);
assert(output_begin == output_end); assert(output_begin == output_end);
const std::size_t num_frames = length / audio_format.GetFrameSize(); const std::size_t num_samples = src.size();
const std::size_t num_samples = length / audio_format.GetSampleSize(); const std::size_t num_frames = num_samples / audio_format.channels;
/* worst-case formula according to LAME documentation */ /* worst-case formula according to LAME documentation */
const std::size_t output_buffer_size = 5 * num_samples / 4 + 7200; const std::size_t output_buffer_size = 5 * num_samples / 4 + 7200;
@ -183,7 +184,7 @@ LameEncoder::Write(std::span<const std::byte> src)
/* this is for only 16-bit audio */ /* this is for only 16-bit audio */
int bytes_out = lame_encode_buffer_interleaved(gfp, int bytes_out = lame_encode_buffer_interleaved(gfp,
const_cast<short *>(src), const_cast<short *>(src.data()),
num_frames, num_frames,
dest, output_buffer_size); dest, output_buffer_size);
@ -194,19 +195,18 @@ LameEncoder::Write(std::span<const std::byte> src)
output_end = dest + bytes_out; output_end = dest + bytes_out;
} }
size_t std::span<const std::byte>
LameEncoder::Read(void *dest, size_t length) noexcept LameEncoder::Read(std::span<std::byte> buffer) noexcept
{ {
const auto begin = output_begin; const auto begin = output_begin;
assert(begin <= output_end); assert(begin <= output_end);
const std::size_t remainning = output_end - begin; const std::size_t remainning = output_end - begin;
if (length > remainning) const std::size_t nbytes = std::min(remainning, buffer.size());
length = remainning;
memcpy(dest, begin, length); memcpy(buffer.data(), begin, nbytes);
output_begin = begin + length; output_begin = begin + nbytes;
return length; return buffer.first(nbytes);
} }
const EncoderPlugin lame_encoder_plugin = { const EncoderPlugin lame_encoder_plugin = {

View File

@ -29,12 +29,12 @@ public:
:Encoder(false) {} :Encoder(false) {}
/* virtual methods from class Encoder */ /* virtual methods from class Encoder */
void Write(const void *data, size_t length) override { void Write(std::span<const std::byte> src) override {
buffer.Append({(const std::byte *)data, length}); buffer.Append(src);
} }
size_t Read(void *dest, size_t length) noexcept override { std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
return buffer.Read((std::byte *)dest, length); return b.first(buffer.Read(b.data(), b.size()));
} }
}; };

View File

@ -50,7 +50,7 @@ public:
flush = true; flush = true;
} }
size_t Read(void *dest, size_t length) noexcept override { std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
ogg_page page; ogg_page page;
bool success = stream.PageOut(page); bool success = stream.PageOut(page);
if (!success) { if (!success) {
@ -60,10 +60,11 @@ public:
} }
if (!success) if (!success)
return 0; return {};
} }
return ReadPage(page, dest, length); return buffer.first(ReadPage(page, buffer.data(),
buffer.size()));
} }
}; };

View File

@ -61,7 +61,7 @@ public:
/* virtual methods from class Encoder */ /* virtual methods from class Encoder */
void End() override; void End() override;
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
void PreTag() override; void PreTag() override;
void SendTag(const Tag &tag) override; void SendTag(const Tag &tag) override;
@ -272,10 +272,8 @@ OpusEncoder::WriteSilence(unsigned fill_frames)
} }
void void
OpusEncoder::Write(const void *_data, size_t length) OpusEncoder::Write(std::span<const std::byte> src)
{ {
const auto *data = (const std::byte *)_data;
if (lookahead > 0) { if (lookahead > 0) {
/* generate some silence at the beginning of the /* generate some silence at the beginning of the
stream */ stream */
@ -286,14 +284,12 @@ OpusEncoder::Write(const void *_data, size_t length)
lookahead = 0; lookahead = 0;
} }
while (length > 0) { while (!src.empty()) {
size_t nbytes = buffer_size - buffer_position; const std::size_t nbytes = std::min(buffer_size - buffer_position,
if (nbytes > length) src.size());
nbytes = length;
memcpy(buffer + buffer_position, data, nbytes); memcpy(buffer + buffer_position, src.data(), nbytes);
data += nbytes; src = src.subspan(nbytes);
length -= nbytes;
buffer_position += nbytes; buffer_position += nbytes;
if (buffer_position == buffer_size) if (buffer_position == buffer_size)

View File

@ -22,6 +22,7 @@
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/DynamicFifoBuffer.hxx" #include "util/DynamicFifoBuffer.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/SpanCast.hxx"
extern "C" extern "C"
{ {
@ -75,10 +76,10 @@ public:
void Flush() override; void Flush() override;
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
size_t Read(void *dest, size_t length) noexcept override { std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override {
return output_buffer.Read((std::byte *)dest, length); return buffer.first(output_buffer.Read(buffer.data(), buffer.size()));
} }
}; };
@ -165,24 +166,24 @@ ShineEncoder::WriteChunk(bool flush)
} }
void void
ShineEncoder::Write(const void *_data, size_t length) ShineEncoder::Write(std::span<const std::byte> _src)
{ {
const auto *data = (const int16_t*)_data; const auto src = FromBytesStrict<const int16_t>(_src);
length /= sizeof(*data) * audio_format.channels; const std::size_t nframes = src.size() / audio_format.channels;
size_t written = 0; size_t written = 0;
if (input_pos > SHINE_MAX_SAMPLES) if (input_pos > SHINE_MAX_SAMPLES)
input_pos = 0; input_pos = 0;
/* write all data to de-interleaved buffers */ /* write all data to de-interleaved buffers */
while (written < length) { while (written < nframes) {
for (; for (;
written < length && input_pos < frame_size; written < nframes && input_pos < frame_size;
written++, input_pos++) { written++, input_pos++) {
const size_t base = const size_t base =
written * audio_format.channels; written * audio_format.channels;
stereo[0][input_pos] = data[base]; stereo[0][input_pos] = src[base];
stereo[1][input_pos] = data[base + 1]; stereo[1][input_pos] = src[base + 1];
} }
/* write if chunk is filled */ /* write if chunk is filled */
WriteChunk(false); WriteChunk(false);

View File

@ -22,6 +22,7 @@
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/NumberParser.hxx" #include "util/NumberParser.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/SpanCast.hxx"
#include "util/Domain.hxx" #include "util/Domain.hxx"
#include "Log.hxx" #include "Log.hxx"
@ -67,8 +68,8 @@ public:
flush = true; flush = true;
} }
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
size_t Read(void *dest, size_t length) noexcept override; std::span<const std::byte> Read(std::span<std::byte> buffer) noexcept override;
}; };
class PreparedTwolameEncoder final : public PreparedEncoder { class PreparedTwolameEncoder final : public PreparedEncoder {
@ -187,16 +188,16 @@ TwolameEncoder::~TwolameEncoder() noexcept
} }
void void
TwolameEncoder::Write(const void *data, size_t length) TwolameEncoder::Write(std::span<const std::byte> _src)
{ {
const auto *src = (const int16_t*)data; const auto src = FromBytesStrict<const int16_t>(_src);
assert(output_buffer_position == output_buffer_length); assert(output_buffer_position == output_buffer_length);
const std::size_t num_frames = length / audio_format.GetFrameSize(); const std::size_t num_frames = src.size() / audio_format.channels;
int bytes_out = twolame_encode_buffer_interleaved(options, int bytes_out = twolame_encode_buffer_interleaved(options,
src, num_frames, src.data(), num_frames,
output_buffer, output_buffer,
sizeof(output_buffer)); sizeof(output_buffer));
if (bytes_out < 0) if (bytes_out < 0)
@ -206,8 +207,8 @@ TwolameEncoder::Write(const void *data, size_t length)
output_buffer_position = 0; output_buffer_position = 0;
} }
size_t std::span<const std::byte>
TwolameEncoder::Read(void *dest, size_t length) noexcept TwolameEncoder::Read(std::span<std::byte> buffer) noexcept
{ {
assert(output_buffer_position <= output_buffer_length); assert(output_buffer_position <= output_buffer_length);
@ -224,14 +225,13 @@ TwolameEncoder::Read(void *dest, size_t length) noexcept
const std::size_t remainning = output_buffer_length - output_buffer_position; const std::size_t remainning = output_buffer_length - output_buffer_position;
if (length > remainning) const std::size_t nbytes = std::min(remainning, buffer.size());
length = remainning;
memcpy(dest, output_buffer + output_buffer_position, length); memcpy(buffer.data(), output_buffer + output_buffer_position, nbytes);
output_buffer_position += length; output_buffer_position += nbytes;
return length; return buffer.first(nbytes);
} }
const EncoderPlugin twolame_encoder_plugin = { const EncoderPlugin twolame_encoder_plugin = {

View File

@ -55,7 +55,7 @@ public:
void PreTag() override; void PreTag() override;
void SendTag(const Tag &tag) override; void SendTag(const Tag &tag) override;
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
private: private:
void HeaderOut(vorbis_comment &vc); void HeaderOut(vorbis_comment &vc);
@ -245,14 +245,14 @@ interleaved_to_vorbis_buffer(float **dest, const float *src,
} }
void void
VorbisEncoder::Write(const void *data, size_t length) VorbisEncoder::Write(std::span<const std::byte> src)
{ {
std::size_t num_frames = length / audio_format.GetFrameSize(); std::size_t num_frames = src.size() / audio_format.GetFrameSize();
/* this is for only 16-bit audio */ /* this is for only 16-bit audio */
interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames), interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&vd, num_frames),
(const float *)data, (const float *)(const void *)src.data(),
num_frames, num_frames,
audio_format.channels); audio_format.channels);

View File

@ -36,10 +36,10 @@ public:
explicit WaveEncoder(AudioFormat &audio_format) noexcept; explicit WaveEncoder(AudioFormat &audio_format) noexcept;
/* virtual methods from class Encoder */ /* virtual methods from class Encoder */
void Write(const void *data, size_t length) override; void Write(std::span<const std::byte> src) override;
size_t Read(void *dest, size_t length) noexcept override { std::span<const std::byte> Read(std::span<std::byte> b) noexcept override {
return buffer.Read((std::byte *)dest, length); return b.first(buffer.Read(b.data(), b.size()));
} }
}; };
@ -183,8 +183,9 @@ pcm24_to_wave(uint8_t *dst8, const uint32_t *src32, size_t length) noexcept
} }
void void
WaveEncoder::Write(const void *src, size_t length) WaveEncoder::Write(std::span<const std::byte> src)
{ {
std::size_t length = src.size();
std::byte *dst = buffer.Write(length); std::byte *dst = buffer.Write(length);
if (IsLittleEndian()) { if (IsLittleEndian()) {
@ -192,29 +193,33 @@ WaveEncoder::Write(const void *src, size_t length)
case 8: case 8:
case 16: case 16:
case 32:// optimized cases case 32:// optimized cases
memcpy(dst, src, length); memcpy(dst, src.data(), length);
break; break;
case 24: case 24:
length = pcm24_to_wave((uint8_t *)dst, length = pcm24_to_wave((uint8_t *)dst,
(const uint32_t *)src, length); (const uint32_t *)(const void *)src.data(),
length);
break; break;
} }
} else { } else {
switch (bits) { switch (bits) {
case 8: case 8:
memcpy(dst, src, length); memcpy(dst, src.data(), length);
break; break;
case 16: case 16:
length = pcm16_to_wave((uint16_t *)dst, length = pcm16_to_wave((uint16_t *)dst,
(const uint16_t *)src, length); (const uint16_t *)(const void *)src.data(),
length);
break; break;
case 24: case 24:
length = pcm24_to_wave((uint8_t *)dst, length = pcm24_to_wave((uint8_t *)dst,
(const uint32_t *)src, length); (const uint32_t *)(const void *)src.data(),
length);
break; break;
case 32: case 32:
length = pcm32_to_wave((uint32_t *)dst, length = pcm32_to_wave((uint32_t *)dst,
(const uint32_t *)src, length); (const uint32_t *)(const void *)src.data(),
length);
break; break;
} }
} }

View File

@ -333,7 +333,7 @@ RecorderOutput::Play(const void *chunk, size_t size)
return size; return size;
} }
encoder->Write(chunk, size); encoder->Write({(const std::byte *)chunk, size});
EncoderToFile(); EncoderToFile();

View File

@ -326,12 +326,14 @@ static void
EncoderToShout(shout_t *shout_conn, Encoder &encoder) EncoderToShout(shout_t *shout_conn, Encoder &encoder)
{ {
while (true) { while (true) {
uint8_t buffer[32768]; std::byte buffer[32768];
size_t nbytes = encoder.Read(buffer, sizeof(buffer)); const auto e = encoder.Read(std::span{buffer});
if (nbytes == 0) if (e.empty() == 0)
return; return;
int err = shout_send(shout_conn, buffer, nbytes); int err = shout_send(shout_conn,
(const unsigned char *)e.data(),
e.size());
HandleShoutError(shout_conn, err); HandleShoutError(shout_conn, err);
} }
} }
@ -414,7 +416,7 @@ ShoutOutput::Delay() const noexcept
size_t size_t
ShoutOutput::Play(const void *chunk, size_t size) ShoutOutput::Play(const void *chunk, size_t size)
{ {
encoder->Write(chunk, size); encoder->Write({(const std::byte *)chunk, size});
WritePage(); WritePage();
return size; return size;
} }
@ -422,9 +424,9 @@ ShoutOutput::Play(const void *chunk, size_t size)
bool bool
ShoutOutput::Pause() ShoutOutput::Pause()
{ {
static char silence[1020]; static std::byte silence[1020];
encoder->Write(silence, sizeof(silence)); encoder->Write(std::span{silence});
WritePage(); WritePage();
return true; return true;

View File

@ -40,6 +40,7 @@
#include <queue> #include <queue>
#include <list> #include <list>
#include <memory> #include <memory>
#include <span>
struct ConfigBlock; struct ConfigBlock;
class EventLoop; class EventLoop;
@ -249,7 +250,7 @@ public:
* *
* Throws on error. * Throws on error.
*/ */
void EncodeAndPlay(const void *chunk, size_t size); void EncodeAndPlay(std::span<const std::byte> src);
void SendTag(const Tag &tag) override; void SendTag(const Tag &tag) override;

View File

@ -161,14 +161,13 @@ HttpdOutput::ReadPage() noexcept
size_t size = 0; size_t size = 0;
do { do {
size_t nbytes = encoder->Read(buffer + size, const auto r = encoder->Read(std::span{buffer}.subspan(size));
sizeof(buffer) - size); if (r.empty())
if (nbytes == 0)
break; break;
unflushed_input = 0; unflushed_input = 0;
size += nbytes; size += r.size();
} while (size < sizeof(buffer)); } while (size < sizeof(buffer));
if (size == 0) if (size == 0)
@ -301,11 +300,11 @@ HttpdOutput::BroadcastFromEncoder() noexcept
} }
inline void inline void
HttpdOutput::EncodeAndPlay(const void *chunk, size_t size) HttpdOutput::EncodeAndPlay(std::span<const std::byte> src)
{ {
encoder->Write(chunk, size); encoder->Write(src);
unflushed_input += size; unflushed_input += src.size();
BroadcastFromEncoder(); BroadcastFromEncoder();
} }
@ -316,7 +315,7 @@ HttpdOutput::Play(const void *chunk, size_t size)
pause = false; pause = false;
if (LockHasClients()) if (LockHasClients())
EncodeAndPlay(chunk, size); EncodeAndPlay({(const std::byte *)chunk, size});
if (!timer->IsStarted()) if (!timer->IsStarted())
timer->Start(); timer->Start();

View File

@ -128,9 +128,7 @@ ReadEncoder(Encoder &encoder) noexcept
{ {
std::byte buffer[4096]; std::byte buffer[4096];
size_t nbytes = encoder.Read(buffer, sizeof(buffer)); return AllocatedArray<std::byte>{encoder.Read(std::span{buffer})};
const std::span<const std::byte> src{buffer, nbytes};
return AllocatedArray<std::byte>{src};
} }
inline void inline void
@ -313,7 +311,7 @@ SnapcastOutput::Play(const void *chunk, size_t size)
if (!LockHasClients()) if (!LockHasClients())
return size; return size;
encoder->Write(chunk, size); encoder->Write({(const std::byte *)chunk, size});
unflushed_input += size; unflushed_input += size;
if (unflushed_input >= 65536) { if (unflushed_input >= 65536) {
@ -332,8 +330,8 @@ SnapcastOutput::Play(const void *chunk, size_t size)
while (true) { while (true) {
std::byte buffer[32768]; std::byte buffer[32768];
size_t nbytes = encoder->Read(buffer, sizeof(buffer)); const auto payload = encoder->Read(std::span{buffer});
if (nbytes == 0) if (payload.empty())
break; break;
unflushed_input = 0; unflushed_input = 0;
@ -342,7 +340,6 @@ SnapcastOutput::Play(const void *chunk, size_t size)
if (chunks.empty()) if (chunks.empty())
inject_event.Schedule(); inject_event.Schedule();
const std::span<const std::byte> payload{buffer, nbytes};
chunks.push(std::make_shared<SnapcastChunk>(now, AllocatedArray{payload})); chunks.push(std::make_shared<SnapcastChunk>(now, AllocatedArray{payload}));
} }

View File

@ -37,7 +37,6 @@
int main(int argc, char **argv) int main(int argc, char **argv)
try { try {
const char *encoder_name; const char *encoder_name;
static char buffer[32768];
/* parse command line */ /* parse command line */
@ -79,9 +78,10 @@ try {
/* do it */ /* do it */
static std::byte buffer[32768];
ssize_t nbytes; ssize_t nbytes;
while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) { while ((nbytes = read(0, buffer, sizeof(buffer))) > 0) {
encoder->Write(buffer, nbytes); encoder->Write(std::span{buffer}.first(nbytes));
EncoderToOutputStream(os, *encoder); EncoderToOutputStream(os, *encoder);
} }

View File

@ -33,8 +33,6 @@
#include <stddef.h> #include <stddef.h>
static uint8_t zero[256];
int int
main([[maybe_unused]] int argc, [[maybe_unused]] char **argv) main([[maybe_unused]] int argc, [[maybe_unused]] char **argv)
try { try {
@ -61,7 +59,8 @@ try {
/* write a block of data */ /* write a block of data */
encoder->Write(zero, sizeof(zero)); static constexpr std::byte zero[256]{};
encoder->Write(std::span{zero});
EncoderToOutputStream(os, *encoder); EncoderToOutputStream(os, *encoder);
@ -86,7 +85,7 @@ try {
/* write another block of data */ /* write another block of data */
encoder->Write(zero, sizeof(zero)); encoder->Write(std::span{zero});
/* finish */ /* finish */