feat: adopt IINA-style playback window
This commit is contained in:
+41
-3
@@ -244,6 +244,12 @@ struct Output::Impl {
|
||||
AudioUnit unit = nullptr;
|
||||
std::array<float, capacity * 2> samples{};
|
||||
std::atomic<uint64_t> read{0}, written{0};
|
||||
static_assert(std::atomic<float>::is_always_lock_free);
|
||||
std::atomic<float> targetGain{1};
|
||||
// Callback-owned while running; start initializes gain before callbacks begin.
|
||||
double currentGain = 1, gainStep = 0;
|
||||
float rampTarget = 1;
|
||||
size_t rampRemaining = 0;
|
||||
double origin = 0;
|
||||
double latency = 0;
|
||||
std::atomic<uint64_t> clockVersion{0};
|
||||
@@ -260,9 +266,31 @@ struct Output::Impl {
|
||||
uint64_t w = self.written.load(std::memory_order_acquire);
|
||||
size_t frames = std::min<uint64_t>(count, w - r);
|
||||
float *destination = static_cast<float *>(buffers->mBuffers[0].mData);
|
||||
size_t first = std::min(frames, capacity - size_t(r % capacity));
|
||||
std::memcpy(destination, self.samples.data() + (r % capacity) * 2, first * 2 * sizeof(float));
|
||||
std::memcpy(destination + first * 2, self.samples.data(), (frames - first) * 2 * sizeof(float));
|
||||
float target = self.targetGain.load(std::memory_order_relaxed);
|
||||
if (target != self.rampTarget) {
|
||||
self.rampTarget = target;
|
||||
self.rampRemaining = 240;
|
||||
self.gainStep = (target - self.currentGain) / 240;
|
||||
}
|
||||
// Stable mute consumes the ring normally, leaving the zeroed output alone.
|
||||
if (self.currentGain != 0 || target != 0) {
|
||||
size_t first = std::min(frames, capacity - size_t(r % capacity));
|
||||
std::memcpy(destination, self.samples.data() + (r % capacity) * 2, first * 2 * sizeof(float));
|
||||
std::memcpy(destination + first * 2, self.samples.data(), (frames - first) * 2 * sizeof(float));
|
||||
size_t frame = 0;
|
||||
while (frame < frames && self.rampRemaining) {
|
||||
self.currentGain += self.gainStep;
|
||||
if (--self.rampRemaining == 0) self.currentGain = target;
|
||||
float gain = static_cast<float>(self.currentGain);
|
||||
destination[frame * 2] *= gain;
|
||||
destination[frame * 2 + 1] *= gain;
|
||||
++frame;
|
||||
}
|
||||
// Unity is bit-identical to the decoded PCM; do not scale underrun silence.
|
||||
if (self.currentGain != 1)
|
||||
for (size_t sample = frame * 2; sample < frames * 2; ++sample)
|
||||
destination[sample] *= static_cast<float>(self.currentGain);
|
||||
}
|
||||
#ifdef PLAYER_AUDIO_CHECK
|
||||
player_check_audio(destination, count);
|
||||
#endif
|
||||
@@ -345,6 +373,10 @@ bool Output::configure_audio(std::string &error) {
|
||||
bool Output::start(std::string &error) {
|
||||
if (!impl->unit) return true; // Video-only playback does not open an audio device.
|
||||
if (impl->running) return true;
|
||||
impl->rampTarget = impl->targetGain.load(std::memory_order_relaxed);
|
||||
impl->currentGain = impl->rampTarget;
|
||||
impl->rampRemaining = 0;
|
||||
impl->gainStep = 0;
|
||||
OSStatus status = AudioOutputUnitStart(impl->unit);
|
||||
if (status != noErr) { error = "Could not start Core Audio (" + std::to_string(status) + ")"; return false; }
|
||||
impl->running = true;
|
||||
@@ -357,6 +389,12 @@ void Output::pause() {
|
||||
impl->running = false;
|
||||
impl->frozen.store(position, std::memory_order_release);
|
||||
}
|
||||
void Output::set_volume(float normalized0to1) noexcept {
|
||||
impl->targetGain.store(normalized0to1, std::memory_order_relaxed);
|
||||
}
|
||||
float Output::volume() const noexcept {
|
||||
return impl->targetGain.load(std::memory_order_relaxed);
|
||||
}
|
||||
void Output::reset() {
|
||||
impl->read.store(0); impl->written.store(0);
|
||||
impl->origin = 0;
|
||||
|
||||
Reference in New Issue
Block a user