feat: visualize retained network playback buffer
This commit is contained in:
@@ -4,8 +4,10 @@ extern "C" {
|
||||
#include <libavformat/avformat.h>
|
||||
}
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <cerrno>
|
||||
#include <condition_variable>
|
||||
#include <cstdint>
|
||||
@@ -48,7 +50,8 @@ public:
|
||||
operationSerial.store(serial.load(std::memory_order_acquire), std::memory_order_relaxed);
|
||||
deadline.store(now() + 15'000'000'000LL, std::memory_order_relaxed);
|
||||
}
|
||||
void start(std::array<int, 2> indices) {
|
||||
void start(std::array<int, 2> indices, double timelineOrigin) {
|
||||
origin = timelineOrigin;
|
||||
for (int slot = 0; slot < 2; ++slot) {
|
||||
lanes[slot].index = indices[slot];
|
||||
if (indices[slot] < 0) continue;
|
||||
@@ -69,6 +72,7 @@ public:
|
||||
void seek(int streamIndex, int64_t timestamp) {
|
||||
std::lock_guard lock(mutex);
|
||||
for (auto &lane : lanes) clear(lane);
|
||||
scratchSlot = -1;
|
||||
ended = false;
|
||||
failure = 0;
|
||||
seekIndex = streamIndex;
|
||||
@@ -81,11 +85,13 @@ public:
|
||||
std::lock_guard lock(mutex);
|
||||
skipAudio = discard;
|
||||
if (discard) clear(lanes[0]);
|
||||
if (discard && scratchSlot == 0) scratchSlot = -1;
|
||||
changed.notify_all();
|
||||
}
|
||||
void finish() {
|
||||
std::lock_guard lock(mutex);
|
||||
for (auto &lane : lanes) clear(lane);
|
||||
scratchSlot = -1;
|
||||
ended = true;
|
||||
seeking = false;
|
||||
failure = 0;
|
||||
@@ -100,13 +106,23 @@ public:
|
||||
std::lock_guard lock(mutex);
|
||||
return failure;
|
||||
}
|
||||
double bufferedEnd(int slot) const {
|
||||
std::lock_guard lock(mutex);
|
||||
if (failure || seeking) return 0;
|
||||
const auto &lane = lanes[slot];
|
||||
const double queued = lane.count ?
|
||||
lane.ends[(lane.first + lane.count - 1) % packetLimit] : 0;
|
||||
return std::max(queued, scratchSlot == slot ? scratchEnd : 0);
|
||||
}
|
||||
// 1: packet, 0: not yet available, AVERROR_EOF: this input is drained.
|
||||
int take(int slot, AVPacket *destination) {
|
||||
int take(int slot, AVPacket *destination, double &bufferedEnd) {
|
||||
std::lock_guard lock(mutex);
|
||||
if (failure) return failure;
|
||||
bufferedEnd = 0;
|
||||
auto &lane = lanes[slot];
|
||||
if (lane.count) {
|
||||
auto *packet = lane.packets[lane.first];
|
||||
bufferedEnd = lane.ends[lane.first];
|
||||
lane.bytes -= packetBytes(packet);
|
||||
av_packet_move_ref(destination, packet);
|
||||
lane.first = (lane.first + 1) % packetLimit;
|
||||
@@ -120,6 +136,7 @@ public:
|
||||
private:
|
||||
struct Lane {
|
||||
std::array<AVPacket *, packetLimit> packets{};
|
||||
std::array<double, packetLimit> ends{};
|
||||
int index = -1;
|
||||
std::size_t first = 0, count = 0, bytes = 0;
|
||||
};
|
||||
@@ -137,6 +154,8 @@ private:
|
||||
bool seeking = false, ended = false, skipAudio = false;
|
||||
int seekIndex = -1, failure = 0;
|
||||
int64_t seekTimestamp = 0;
|
||||
int scratchSlot = -1;
|
||||
double scratchEnd = 0, origin = 0;
|
||||
std::thread reader;
|
||||
|
||||
static int64_t now() {
|
||||
@@ -153,6 +172,20 @@ private:
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
double packetEnd(int slot, const AVPacket *packet) const {
|
||||
if (packet->pts == AV_NOPTS_VALUE || (packet->flags & AV_PKT_FLAG_DISCARD)) return 0;
|
||||
int64_t timestamp = packet->pts;
|
||||
if (slot == 1) {
|
||||
// PTS alone can jump to a future reference frame before its B-frames arrive.
|
||||
// Unknown DTS contributes no encoded-video estimate; decoded frames still count.
|
||||
if (packet->dts == AV_NOPTS_VALUE) return 0;
|
||||
timestamp = std::min(timestamp, packet->dts);
|
||||
}
|
||||
const double tick = av_q2d(format->streams[lanes[slot].index]->time_base);
|
||||
const double end = static_cast<double>(timestamp) * tick - origin +
|
||||
static_cast<double>(std::max<int64_t>(packet->duration, 0)) * tick;
|
||||
return std::isfinite(end) && end > 0 ? end : 0;
|
||||
}
|
||||
static void clear(Lane &lane) {
|
||||
while (lane.count) {
|
||||
av_packet_unref(lane.packets[lane.first]);
|
||||
@@ -233,6 +266,8 @@ private:
|
||||
playerWake.notify_one();
|
||||
continue;
|
||||
}
|
||||
scratchSlot = slot;
|
||||
scratchEnd = packetEnd(slot, scratch);
|
||||
changed.wait(lock, [&] {
|
||||
return stoppingNow() || serial.load(std::memory_order_acquire) != current ||
|
||||
(slot == 0 && skipAudio) ||
|
||||
@@ -241,11 +276,14 @@ private:
|
||||
if (stoppingNow()) return;
|
||||
if (serial.load(std::memory_order_acquire) != current || (slot == 0 && skipAudio)) {
|
||||
av_packet_unref(scratch);
|
||||
scratchSlot = -1;
|
||||
continue;
|
||||
}
|
||||
lane.bytes += bytes;
|
||||
lane.ends[(lane.first + lane.count) % packetLimit] = scratchEnd;
|
||||
av_packet_move_ref(lane.packets[(lane.first + lane.count) % packetLimit], scratch);
|
||||
++lane.count;
|
||||
scratchSlot = -1;
|
||||
playerWake.notify_one();
|
||||
}
|
||||
} catch (...) {
|
||||
|
||||
Reference in New Issue
Block a user