Update the vendored kernel to the bounded objects16 render-ahead
This commit is contained in:
@@ -39,12 +39,12 @@ typedef struct joc_stream joc_stream;
|
|||||||
|
|
||||||
typedef enum joc_stream_input {
|
typedef enum joc_stream_input {
|
||||||
JOC_STREAM_IN_EAC3 = 0, /* bare E-AC-3 syncframes (the metadata stream) */
|
JOC_STREAM_IN_EAC3 = 0, /* bare E-AC-3 syncframes (the metadata stream) */
|
||||||
JOC_STREAM_IN_PCM_OBJECTS16 = 1, /* 16-channel objects16, decoded by the host */
|
JOC_STREAM_IN_PCM_OBJECTS16 = 1, /* objects16 frames, planar [16][1536] each */
|
||||||
JOC_STREAM_IN_CORE_PCM = 3 /* the 5.1 core PCM of the pushed E-AC-3 frames */
|
JOC_STREAM_IN_CORE_PCM = 3 /* the 5.1 core PCM of the pushed E-AC-3 frames */
|
||||||
} joc_stream_input;
|
} joc_stream_input;
|
||||||
|
|
||||||
typedef enum joc_stream_output {
|
typedef enum joc_stream_output {
|
||||||
JOC_STREAM_OUT_PCM_OBJECTS16 = 0, /* planar [16][samples] float32 */
|
JOC_STREAM_OUT_PCM_OBJECTS16 = 0, /* objects16 frames, planar [16][1536] each */
|
||||||
JOC_STREAM_OUT_SPEAKER = 1, /* interleaved [samples][channels] f32 */
|
JOC_STREAM_OUT_SPEAKER = 1, /* interleaved [samples][channels] f32 */
|
||||||
JOC_STREAM_OUT_BINAURAL = 2 /* interleaved [samples][2] f32 */
|
JOC_STREAM_OUT_BINAURAL = 2 /* interleaved [samples][2] f32 */
|
||||||
} joc_stream_output;
|
} joc_stream_output;
|
||||||
|
|||||||
@@ -121,11 +121,9 @@
|
|||||||
|
|
||||||
* x64 gets the AVX2 and AVX-512 units, JOC_SIMD_HAVE_SSE2 / _AVX2 /
|
* x64 gets the AVX2 and AVX-512 units, JOC_SIMD_HAVE_SSE2 / _AVX2 /
|
||||||
_AVX512, and a per-file /arch for those two files only;
|
_AVX512, and a per-file /arch for those two files only;
|
||||||
* Win32 (x86) gets the AVX2 unit and JOC_SIMD_HAVE_AVX2. AVX2 is not an
|
* Win32 (x86) gets the AVX2 unit and JOC_SIMD_HAVE_AVX2: AVX2 is not an
|
||||||
x86-64-only ISA and MSVC accepts /arch:AVX2 for x86, so gating it on the
|
x86-64-only ISA. AVX-512 stays x64-only, because 32-bit mode addresses
|
||||||
pointer size left the 32-bit component on the scalar reference, which is
|
ZMM0-7 only.
|
||||||
too slow to hold a 4096-frame read inside its own 85.3 ms of audio.
|
|
||||||
AVX-512 stays x64-only: 32-bit mode addresses ZMM0-7 only.
|
|
||||||
* src\simd\kernels_intrin_neon.cpp is excluded everywhere here: the CMake
|
* src\simd\kernels_intrin_neon.cpp is excluded everywhere here: the CMake
|
||||||
build lists it only for aarch64 (CMakeLists.txt lines 177-180), which no
|
build lists it only for aarch64 (CMakeLists.txt lines 177-180), which no
|
||||||
configuration of this project targets.
|
configuration of this project targets.
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ namespace joc::simd {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
// ---------------------------------------------------------------------- x86 --
|
// ---------------------------------------------------------------------- x86 --
|
||||||
// Both pointer sizes are probed: AVX2 is not an x86-64-only ISA, and gating this
|
// AVX2 is not an x86-64-only ISA, so both pointer sizes are probed.
|
||||||
// on _M_X64 / __x86_64__ left every 32-bit x86 build reporting "no features",
|
|
||||||
// which pinned the dispatcher to the scalar kernels.
|
|
||||||
#if defined(_M_X64) || defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)
|
#if defined(_M_X64) || defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)
|
||||||
|
|
||||||
#if defined(_M_X64) || defined(_M_IX86)
|
#if defined(_M_X64) || defined(_M_IX86)
|
||||||
|
|||||||
@@ -38,7 +38,10 @@ void Stream::reset_state() {
|
|||||||
reader_ = eac3::FrameReader();
|
reader_ = eac3::FrameReader();
|
||||||
metadata_.clear();
|
metadata_.clear();
|
||||||
bed_pending_.clear();
|
bed_pending_.clear();
|
||||||
|
bed_read_offset_ = 0;
|
||||||
objects16_.clear();
|
objects16_.clear();
|
||||||
|
objects_pending_.clear();
|
||||||
|
objects_read_offset_ = 0;
|
||||||
output_.clear();
|
output_.clear();
|
||||||
read_offset_ = 0;
|
read_offset_ = 0;
|
||||||
info_ = Info();
|
info_ = Info();
|
||||||
@@ -245,54 +248,82 @@ Status Stream::push_objects16(const float* planar16, std::size_t samples, std::s
|
|||||||
if (planar16 == nullptr || samples == 0u) {
|
if (planar16 == nullptr || samples == 0u) {
|
||||||
return Status::success();
|
return Status::success();
|
||||||
}
|
}
|
||||||
// Rendered immediately: the host has already done the JOC rebuild.
|
// Queued as whole frames, each planar [16][kFrameSamples]: the shape the
|
||||||
|
// objects16 output writes, so a host can feed a batch back unchanged.
|
||||||
|
constexpr std::size_t kFrameValues =
|
||||||
|
static_cast<std::size_t>(JOC_OUTPUT_CHANNELS) * kFrameSamples;
|
||||||
for (std::size_t offset = 0; offset < samples; offset += kFrameSamples) {
|
for (std::size_t offset = 0; offset < samples; offset += kFrameSamples) {
|
||||||
const std::size_t count = std::min(kFrameSamples, samples - offset);
|
const std::size_t count = std::min(kFrameSamples, samples - offset);
|
||||||
std::vector<float> frame(static_cast<std::size_t>(JOC_OUTPUT_CHANNELS) * kFrameSamples,
|
const std::size_t base = objects_pending_.size();
|
||||||
0.0f);
|
const float* frame = planar16 + (offset / kFrameSamples) * kFrameValues;
|
||||||
|
objects_pending_.resize(base + kFrameValues, 0.0f);
|
||||||
for (std::size_t channel = 0; channel < JOC_OUTPUT_CHANNELS; ++channel) {
|
for (std::size_t channel = 0; channel < JOC_OUTPUT_CHANNELS; ++channel) {
|
||||||
std::memcpy(frame.data() + channel * kFrameSamples,
|
std::memcpy(objects_pending_.data() + base + channel * kFrameSamples,
|
||||||
planar16 + channel * samples + offset, count * sizeof(float));
|
frame + channel * kFrameSamples, count * sizeof(float));
|
||||||
}
|
}
|
||||||
++info_.frames_in;
|
}
|
||||||
info_.samples_in += count;
|
return process_objects16_frames(false);
|
||||||
const Status rendered = render_objects16(frame);
|
}
|
||||||
|
|
||||||
|
Status Stream::process_objects16_frames(bool drain_all) {
|
||||||
|
constexpr std::size_t kFrameValues =
|
||||||
|
static_cast<std::size_t>(JOC_OUTPUT_CHANNELS) * kFrameSamples;
|
||||||
|
while (objects_pending_.size() - objects_read_offset_ >= kFrameValues) {
|
||||||
|
// Leave the rest queued, in order, for a later push or for flush().
|
||||||
|
if (!drain_all && buffered_samples() >= kMaxRenderAheadSamples) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const auto first = objects_pending_.begin() +
|
||||||
|
static_cast<std::ptrdiff_t>(objects_read_offset_);
|
||||||
|
objects_frame_.assign(first, first + static_cast<std::ptrdiff_t>(kFrameValues));
|
||||||
|
objects_read_offset_ += kFrameValues;
|
||||||
|
const Status rendered = render_objects16(objects_frame_);
|
||||||
if (!rendered.ok()) {
|
if (!rendered.ok()) {
|
||||||
return rendered;
|
return rendered;
|
||||||
}
|
}
|
||||||
if (count != kFrameSamples) {
|
++info_.frames_in;
|
||||||
break; // a partial frame is dropped; the host should push whole frames
|
info_.samples_in += kFrameSamples;
|
||||||
}
|
}
|
||||||
|
if (objects_read_offset_ == objects_pending_.size()) {
|
||||||
|
objects_pending_.clear();
|
||||||
|
objects_read_offset_ = 0;
|
||||||
|
} else if (objects_read_offset_ >= (1u << 20)) {
|
||||||
|
// Erasing from the front moves the remainder, so it is only worth doing
|
||||||
|
// once the consumed prefix is large enough to pay for the move.
|
||||||
|
objects_pending_.erase(
|
||||||
|
objects_pending_.begin(),
|
||||||
|
objects_pending_.begin() + static_cast<std::ptrdiff_t>(objects_read_offset_));
|
||||||
|
objects_read_offset_ = 0;
|
||||||
}
|
}
|
||||||
return Status::success();
|
return Status::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
Status Stream::process_ready_frames(bool drain_all) {
|
Status Stream::process_ready_frames(bool drain_all) {
|
||||||
while (bed_pending_.size() / kBedChannels >= kFrameSamples && !metadata_.empty()) {
|
while (bed_pending_.size() - bed_read_offset_ >= kFrameSamples * kBedChannels &&
|
||||||
// Stop before rendering what the caller is not about to take: the frames
|
!metadata_.empty()) {
|
||||||
// stay queued, in order, and are rendered by a later push or by flush().
|
// Leave the rest queued, in order, for a later push or for flush().
|
||||||
if (!drain_all && buffered_samples() >= kMaxRenderAheadSamples) {
|
if (!drain_all && buffered_samples() >= kMaxRenderAheadSamples) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
const FrameMetadata entry = metadata_.front();
|
const FrameMetadata entry = metadata_.front();
|
||||||
metadata_.pop_front();
|
metadata_.pop_front();
|
||||||
|
|
||||||
std::vector<float> bed5(static_cast<std::size_t>(JOC_CORE_CHANNELS) * kFrameSamples, 0.0f);
|
const float* bed = bed_pending_.data() + bed_read_offset_;
|
||||||
std::vector<float> lfe(kFrameSamples, 0.0f);
|
bed5_.resize(static_cast<std::size_t>(JOC_CORE_CHANNELS) * kFrameSamples);
|
||||||
|
lfe_.resize(kFrameSamples);
|
||||||
for (std::size_t sample = 0; sample < kFrameSamples; ++sample) {
|
for (std::size_t sample = 0; sample < kFrameSamples; ++sample) {
|
||||||
for (std::size_t channel = 0; channel < JOC_CORE_CHANNELS; ++channel) {
|
for (std::size_t channel = 0; channel < JOC_CORE_CHANNELS; ++channel) {
|
||||||
bed5[channel * kFrameSamples + sample] =
|
bed5_[channel * kFrameSamples + sample] =
|
||||||
bed_pending_[sample * kBedChannels + kCoreChannels[channel]];
|
bed[sample * kBedChannels + kCoreChannels[channel]];
|
||||||
}
|
}
|
||||||
lfe[sample] = bed_pending_[sample * kBedChannels + kLfeChannel];
|
lfe_[sample] = bed[sample * kBedChannels + kLfeChannel];
|
||||||
}
|
}
|
||||||
bed_pending_.erase(bed_pending_.begin(),
|
bed_read_offset_ += kFrameSamples * kBedChannels;
|
||||||
bed_pending_.begin() + static_cast<std::ptrdiff_t>(kFrameSamples *
|
compact_bed_pending();
|
||||||
kBedChannels));
|
|
||||||
|
|
||||||
std::string error;
|
std::string error;
|
||||||
const Status rebuilt = joc::rebuild_objects16(rebuilder_, entry.params, bed5.data(),
|
const Status rebuilt = joc::rebuild_objects16(rebuilder_, entry.params, bed5_.data(),
|
||||||
lfe.data(), gain_, &objects16_, &error);
|
lfe_.data(), gain_, &objects16_, &error);
|
||||||
if (!rebuilt.ok()) {
|
if (!rebuilt.ok()) {
|
||||||
return Status::fail(rebuilt.code(), stage::kDsp, error);
|
return Status::fail(rebuilt.code(), stage::kDsp, error);
|
||||||
}
|
}
|
||||||
@@ -307,6 +338,22 @@ Status Stream::process_ready_frames(bool drain_all) {
|
|||||||
return Status::success();
|
return Status::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Stream::compact_bed_pending() {
|
||||||
|
if (bed_read_offset_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bed_read_offset_ == bed_pending_.size()) {
|
||||||
|
bed_pending_.clear();
|
||||||
|
bed_read_offset_ = 0;
|
||||||
|
} else if (bed_read_offset_ >= (1u << 20)) {
|
||||||
|
// Erasing from the front moves the remainder, so it is only worth doing
|
||||||
|
// once the consumed prefix is large enough to pay for the move.
|
||||||
|
bed_pending_.erase(bed_pending_.begin(),
|
||||||
|
bed_pending_.begin() + static_cast<std::ptrdiff_t>(bed_read_offset_));
|
||||||
|
bed_read_offset_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Status Stream::render_objects16(const std::vector<float>& objects16) {
|
Status Stream::render_objects16(const std::vector<float>& objects16) {
|
||||||
if (config_.output == JOC_STREAM_OUT_PCM_OBJECTS16) {
|
if (config_.output == JOC_STREAM_OUT_PCM_OBJECTS16) {
|
||||||
output_.insert(output_.end(), objects16.begin(), objects16.end());
|
output_.insert(output_.end(), objects16.begin(), objects16.end());
|
||||||
@@ -324,6 +371,7 @@ Status Stream::render_objects16(const std::vector<float>& objects16) {
|
|||||||
if (!stepped.ok()) {
|
if (!stepped.ok()) {
|
||||||
return Status::fail(stepped.code(), stage::kRender, error);
|
return Status::fail(stepped.code(), stage::kRender, error);
|
||||||
}
|
}
|
||||||
|
output_.reserve(output_.size() + speaker_.output.size());
|
||||||
for (const double value : speaker_.output) {
|
for (const double value : speaker_.output) {
|
||||||
output_.push_back(static_cast<float>(value));
|
output_.push_back(static_cast<float>(value));
|
||||||
}
|
}
|
||||||
@@ -344,13 +392,13 @@ Status Stream::render_objects16(const std::vector<float>& objects16) {
|
|||||||
if (!submitted.ok()) {
|
if (!submitted.ok()) {
|
||||||
return submitted;
|
return submitted;
|
||||||
}
|
}
|
||||||
std::vector<double> produced;
|
binaural_.take_output(&produced_);
|
||||||
binaural_.take_output(&produced);
|
output_.reserve(output_.size() + produced_.size());
|
||||||
for (const double value : produced) {
|
for (const double value : produced_) {
|
||||||
output_.push_back(static_cast<float>(value));
|
output_.push_back(static_cast<float>(value));
|
||||||
}
|
}
|
||||||
info_.frames_out++;
|
info_.frames_out++;
|
||||||
info_.samples_out += produced.size() / 2u;
|
info_.samples_out += produced_.size() / 2u;
|
||||||
return Status::success();
|
return Status::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,16 +419,15 @@ Status Stream::render_rosella_objects16(const std::vector<float>& objects16) {
|
|||||||
if (!submitted.ok()) {
|
if (!submitted.ok()) {
|
||||||
return submitted;
|
return submitted;
|
||||||
}
|
}
|
||||||
std::vector<double> produced;
|
rosella_.take_output(&produced_);
|
||||||
rosella_.take_output(&produced);
|
if (!produced_.empty()) {
|
||||||
if (!produced.empty()) {
|
rosella_pending_.insert(rosella_pending_.end(), produced_.begin(), produced_.end());
|
||||||
rosella_pending_.insert(rosella_pending_.end(), produced.begin(), produced.end());
|
|
||||||
}
|
}
|
||||||
release_rosella_output(kFrameSamples);
|
release_rosella_output(kFrameSamples);
|
||||||
info_.frames_out++;
|
info_.frames_out++;
|
||||||
// Counted as the runtime produces it, which is also how the SOFA path counts:
|
// Counted as the runtime produces it, which is also how the SOFA path counts:
|
||||||
// the totals are identical, only the frame they appear on differs.
|
// the totals are identical, only the frame they appear on differs.
|
||||||
info_.samples_out += produced.size() / 2u;
|
info_.samples_out += produced_.size() / 2u;
|
||||||
return Status::success();
|
return Status::success();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +440,7 @@ void Stream::release_rosella_output(std::size_t limit) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const std::size_t values = count * 2u;
|
const std::size_t values = count * 2u;
|
||||||
|
output_.reserve(output_.size() + values);
|
||||||
for (std::size_t index = 0; index < values; ++index) {
|
for (std::size_t index = 0; index < values; ++index) {
|
||||||
output_.push_back(static_cast<float>(rosella_pending_[rosella_read_offset_ + index]));
|
output_.push_back(static_cast<float>(rosella_pending_[rosella_read_offset_ + index]));
|
||||||
}
|
}
|
||||||
@@ -434,13 +482,16 @@ Status Stream::pull(float* destination, std::size_t capacity_samples, std::size_
|
|||||||
}
|
}
|
||||||
|
|
||||||
Status Stream::flush() {
|
Status Stream::flush() {
|
||||||
// Input has ended, so the render-ahead bound has nothing left to wait for:
|
// Input has ended, so drain what the cap held back: nothing else will
|
||||||
// every frame still queued has to reach the renderer before its tail is
|
// trigger rendering.
|
||||||
// drained, or the end of the file would be dropped.
|
|
||||||
const Status remaining = process_ready_frames(true);
|
const Status remaining = process_ready_frames(true);
|
||||||
if (!remaining.ok()) {
|
if (!remaining.ok()) {
|
||||||
return remaining;
|
return remaining;
|
||||||
}
|
}
|
||||||
|
const Status objects = process_objects16_frames(true);
|
||||||
|
if (!objects.ok()) {
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
if (binaural_ready_) {
|
if (binaural_ready_) {
|
||||||
std::vector<double> tail;
|
std::vector<double> tail;
|
||||||
const Status drained =
|
const Status drained =
|
||||||
@@ -448,6 +499,7 @@ Status Stream::flush() {
|
|||||||
if (!drained.ok()) {
|
if (!drained.ok()) {
|
||||||
return drained;
|
return drained;
|
||||||
}
|
}
|
||||||
|
output_.reserve(output_.size() + tail.size());
|
||||||
for (const double value : tail) {
|
for (const double value : tail) {
|
||||||
output_.push_back(static_cast<float>(value));
|
output_.push_back(static_cast<float>(value));
|
||||||
}
|
}
|
||||||
@@ -464,6 +516,7 @@ Status Stream::flush() {
|
|||||||
// tail only sounds after it. The program samples were already counted by
|
// tail only sounds after it. The program samples were already counted by
|
||||||
// render_rosella_objects16, so only the tail is added here.
|
// render_rosella_objects16, so only the tail is added here.
|
||||||
release_rosella_output(rosella_pending_samples());
|
release_rosella_output(rosella_pending_samples());
|
||||||
|
output_.reserve(output_.size() + tail.size());
|
||||||
for (const double value : tail) {
|
for (const double value : tail) {
|
||||||
output_.push_back(static_cast<float>(value));
|
output_.push_back(static_cast<float>(value));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,13 +85,8 @@ public:
|
|||||||
: 0u;
|
: 0u;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A push renders every frame it makes ready, and the caller decides how far
|
// Cap on rendered samples that have not been pulled. A push renders what it
|
||||||
// its demuxer runs ahead of playback. Without a bound, a demuxer that runs
|
// makes ready, so a caller that feeds faster than it pulls renders ahead.
|
||||||
// far ahead turns its whole read-ahead burst into latency on whichever pull()
|
|
||||||
// happens to follow it: the samples are not wasted, but they are rendered at
|
|
||||||
// the worst possible moment. Rendering therefore stops once this many
|
|
||||||
// samples are rendered and unpulled; flush() lifts the bound so the frames
|
|
||||||
// still waiting when the input ends are drained rather than dropped.
|
|
||||||
static constexpr std::size_t kMaxRenderAheadSamples = 16384;
|
static constexpr std::size_t kMaxRenderAheadSamples = 16384;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -106,6 +101,10 @@ private:
|
|||||||
return rosella_ready_ ? (rosella_pending_.size() - rosella_read_offset_) / 2u : 0u;
|
return rosella_ready_ ? (rosella_pending_.size() - rosella_read_offset_) / 2u : 0u;
|
||||||
}
|
}
|
||||||
void reset_state();
|
void reset_state();
|
||||||
|
// Drops the bed samples that have already been rendered, keeping the rest.
|
||||||
|
void compact_bed_pending();
|
||||||
|
// Renders the queued objects16 frames, bounded by kMaxRenderAheadSamples.
|
||||||
|
Status process_objects16_frames(bool drain_all);
|
||||||
|
|
||||||
Config config_;
|
Config config_;
|
||||||
Info info_;
|
Info info_;
|
||||||
@@ -113,7 +112,12 @@ private:
|
|||||||
std::deque<FrameMetadata> metadata_;
|
std::deque<FrameMetadata> metadata_;
|
||||||
FrameMetadata pending_metadata_;
|
FrameMetadata pending_metadata_;
|
||||||
std::vector<float> bed_pending_;
|
std::vector<float> bed_pending_;
|
||||||
std::vector<std::uint8_t> frame_copy_;
|
std::size_t bed_read_offset_ = 0;
|
||||||
|
std::vector<float> bed5_;
|
||||||
|
std::vector<float> lfe_;
|
||||||
|
std::vector<float> objects_pending_;
|
||||||
|
std::size_t objects_read_offset_ = 0;
|
||||||
|
std::vector<float> objects_frame_;
|
||||||
std::vector<float> objects16_;
|
std::vector<float> objects16_;
|
||||||
std::vector<float> output_;
|
std::vector<float> output_;
|
||||||
std::size_t read_offset_ = 0;
|
std::size_t read_offset_ = 0;
|
||||||
@@ -125,6 +129,7 @@ private:
|
|||||||
hrtf::RosellaRuntime rosella_;
|
hrtf::RosellaRuntime rosella_;
|
||||||
std::vector<double> rosella_pending_;
|
std::vector<double> rosella_pending_;
|
||||||
std::size_t rosella_read_offset_ = 0;
|
std::size_t rosella_read_offset_ = 0;
|
||||||
|
std::vector<double> produced_;
|
||||||
bool speaker_enabled_ = false;
|
bool speaker_enabled_ = false;
|
||||||
bool binaural_enabled_ = false;
|
bool binaural_enabled_ = false;
|
||||||
bool binaural_ready_ = false;
|
bool binaural_ready_ = false;
|
||||||
|
|||||||
+9
-24
@@ -20,15 +20,10 @@ namespace joc_decode {
|
|||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t kEac3Chunk = 96u * 1024u; // bytes read per push
|
constexpr std::size_t kEac3Chunk = 96u * 1024u; // bytes read per push
|
||||||
// The core renders every frame it is handed, and it renders it during the push,
|
// The core renders during the push, so a read queues about as much as it is
|
||||||
// so a read must not queue more frames than the caller is about to take: a 96 KB
|
// about to consume rather than everything it just read.
|
||||||
// read is around thirty syncframes, i.e. a second of audio rendered to satisfy
|
|
||||||
// one 4096-frame read. This is that read (2.67 syncframes) rounded up, so each
|
|
||||||
// read hands the core about as much as it is about to consume.
|
|
||||||
constexpr std::uint64_t kEac3FramesPerRead = 3u;
|
constexpr std::uint64_t kEac3FramesPerRead = 3u;
|
||||||
// Rendered audio the caller has not taken yet. Once this much is waiting there
|
// Rendered audio the caller has not taken yet.
|
||||||
// is nothing to gain from queueing more input: the core would render it now and
|
|
||||||
// the caller would not ask for it for several more reads.
|
|
||||||
constexpr std::size_t kMaxRenderedAheadSamples = 4096u;
|
constexpr std::size_t kMaxRenderedAheadSamples = 4096u;
|
||||||
constexpr std::size_t kBedFramesChunk = 8192u; // staging capacity, in frames
|
constexpr std::size_t kBedFramesChunk = 8192u; // staging capacity, in frames
|
||||||
constexpr std::size_t kBedChannels = 6; // ffmpeg -ac 6
|
constexpr std::size_t kBedChannels = 6; // ffmpeg -ac 6
|
||||||
@@ -984,11 +979,7 @@ std::size_t Engine::read(float* destination, std::size_t frames, std::string* er
|
|||||||
impl.eac3_eof = true;
|
impl.eac3_eof = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The core renders during the push, so input is queued only while the
|
// Queue more input only while less than one read's worth is waiting.
|
||||||
// caller still has less than one read's worth of rendered audio waiting.
|
|
||||||
// Pushing past that is what turns a single read into a second of work:
|
|
||||||
// the samples are rendered early rather than wrongly, and the read that
|
|
||||||
// pays for them overruns its own audio.
|
|
||||||
std::size_t rendered_ahead = 0;
|
std::size_t rendered_ahead = 0;
|
||||||
{
|
{
|
||||||
joc_stream_status_info pending{};
|
joc_stream_status_info pending{};
|
||||||
@@ -1049,13 +1040,9 @@ std::size_t Engine::read(float* destination, std::size_t frames, std::string* er
|
|||||||
offset += bytes;
|
offset += bytes;
|
||||||
++complete;
|
++complete;
|
||||||
}
|
}
|
||||||
// The chunk is cut at a frame boundary for two independent
|
// Both cuts land on a frame boundary because the renderer's output
|
||||||
// reasons: a configured input limit has to stop exactly where it
|
// depends on how many frames it was given: the input limit has to
|
||||||
// says, and a read must not queue more frames than it is about to
|
// stop where it says, and a read queues only its own budget.
|
||||||
// consume. Both cuts land on a frame boundary because the
|
|
||||||
// renderer's output depends on how many frames it was given, so a
|
|
||||||
// cut that overshoots would not reproduce a run that stopped
|
|
||||||
// earlier.
|
|
||||||
std::uint64_t allowed = complete;
|
std::uint64_t allowed = complete;
|
||||||
if (impl.settings.input_frame_limit != 0) {
|
if (impl.settings.input_frame_limit != 0) {
|
||||||
const std::uint64_t room =
|
const std::uint64_t room =
|
||||||
@@ -1096,10 +1083,8 @@ std::size_t Engine::read(float* destination, std::size_t frames, std::string* er
|
|||||||
std::memmove(impl.eac3_buffer.data(), impl.eac3_buffer.data() + push_bytes,
|
std::memmove(impl.eac3_buffer.data(), impl.eac3_buffer.data() + push_bytes,
|
||||||
impl.eac3_carry);
|
impl.eac3_carry);
|
||||||
}
|
}
|
||||||
// The pipe can end while complete syncframes are still waiting in
|
// The pipe can end with complete syncframes still buffered; they
|
||||||
// the buffer: they are queued by the next pass, so the input is
|
// are queued next, so end of input is only real once none remain.
|
||||||
// only over once nothing but a partial frame is left. Ending here
|
|
||||||
// instead would drop them, and with them the end of the file.
|
|
||||||
if (got == 0 && pushed_frames >= complete) impl.eac3_eof = true;
|
if (got == 0 && pushed_frames >= complete) impl.eac3_eof = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user