feat: visualize retained network playback buffer
This commit is contained in:
+56
-3
@@ -61,6 +61,8 @@ struct Track {
|
||||
AVPacket *packet = nullptr;
|
||||
bool packetPending = false;
|
||||
bool receiving = false;
|
||||
double packetBufferedEnd = 0;
|
||||
double decoderBufferedEnd = 0;
|
||||
bool sourceEnded = false;
|
||||
bool drainSent = false;
|
||||
bool drained = false;
|
||||
@@ -247,6 +249,8 @@ struct Player {
|
||||
if (last.repetitions == std::numeric_limits<uint64_t>::max()) return false;
|
||||
++last.repetitions;
|
||||
state.seek_pending = true;
|
||||
state.buffered_start = state.buffered_end = 0;
|
||||
++state.revision;
|
||||
wake.notify_one();
|
||||
return true;
|
||||
}
|
||||
@@ -260,6 +264,11 @@ struct Player {
|
||||
state.status = PLAYER_OPENING;
|
||||
state.revision = revision;
|
||||
}
|
||||
if (commands.back().action == Action::Seek || commands.back().action == Action::Step ||
|
||||
commands.back().action == Action::Close) {
|
||||
state.buffered_start = state.buffered_end = 0;
|
||||
++state.revision;
|
||||
}
|
||||
if (commands.back().action == Action::Seek || commands.back().action == Action::Step)
|
||||
state.seek_pending = true;
|
||||
wake.notify_one();
|
||||
@@ -298,6 +307,41 @@ struct Player {
|
||||
throw std::runtime_error(std::string(operation) + ": " + details);
|
||||
}
|
||||
|
||||
double retainedTrackEnd(int slot) const {
|
||||
const auto &track = tracks[slot];
|
||||
double end = slot == 0 ? std::max(audioWriteEnd, resampleNextPts) : videoEnd;
|
||||
if (track.packetPending) end = std::max(end, track.packetBufferedEnd);
|
||||
end = std::max(end, track.decoderBufferedEnd);
|
||||
if (track.input) end = std::max(end, track.input->bufferedEnd(slot));
|
||||
return end;
|
||||
}
|
||||
|
||||
void publishBufferedRange() {
|
||||
state.buffered_start = state.buffered_end = 0;
|
||||
if (!network || state.seek_pending || frameStepping ||
|
||||
(status != PLAYER_PAUSED && status != PLAYER_PLAYING) ||
|
||||
!std::isfinite(duration) || duration <= 0 ||
|
||||
!std::isfinite(position) || position < 0 || position >= duration) return;
|
||||
const bool audio = tracks[0].codec != nullptr;
|
||||
const bool video = tracks[1].codec != nullptr;
|
||||
if (!audio && !video) return;
|
||||
const double audioEnd = audio ? retainedTrackEnd(0) : 0;
|
||||
const double videoEnd = video ? retainedTrackEnd(1) : 0;
|
||||
double end = audio ? audioEnd : videoEnd;
|
||||
if (audio && video) {
|
||||
const bool audioFinished = audioDrained();
|
||||
const bool videoFinished = tracks[1].drained;
|
||||
// Exhausted audio is synthesized as silence; exhausted video holds its last frame.
|
||||
// Neither shorter track caps the other's retained tail, but EOF is not a full cache.
|
||||
end = audioFinished ? (videoFinished ? std::max(audioEnd, videoEnd) : videoEnd) :
|
||||
(videoFinished ? audioEnd : std::min(audioEnd, videoEnd));
|
||||
}
|
||||
end = std::min(end, duration);
|
||||
if (!std::isfinite(end) || end <= position) return;
|
||||
state.buffered_start = position;
|
||||
state.buffered_end = end;
|
||||
}
|
||||
|
||||
void publish() {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if (activeEpoch != requestedEpoch.load(std::memory_order_relaxed)) return;
|
||||
@@ -315,6 +359,7 @@ struct Player {
|
||||
return (command.action == Action::Seek || command.action == Action::Step) &&
|
||||
command.epoch == activeEpoch;
|
||||
});
|
||||
publishBufferedRange();
|
||||
state.video_aspect_ratio = 0;
|
||||
if (const auto *video = tracks[1].codec; video && video->width > 0 && video->height > 0) {
|
||||
const auto &track = tracks[1];
|
||||
@@ -401,6 +446,7 @@ struct Player {
|
||||
track.drainSent = track.drained = false;
|
||||
av_packet_unref(track.packet);
|
||||
track.packetPending = track.receiving = track.sourceEnded = false;
|
||||
track.packetBufferedEnd = track.decoderBufferedEnd = 0;
|
||||
track.countedTimestamp = AV_NOPTS_VALUE;
|
||||
track.timestampOccurrence = 0;
|
||||
const AVStream *stream = track.stream();
|
||||
@@ -562,7 +608,7 @@ struct Player {
|
||||
std::array<int, 2> indices{-1, -1};
|
||||
for (int slot = 0; slot < 2; ++slot)
|
||||
if (tracks[slot].input == input.get()) indices[slot] = tracks[slot].index;
|
||||
input->start(indices);
|
||||
input->start(indices, origin);
|
||||
}
|
||||
}
|
||||
status = PLAYER_PAUSED;
|
||||
@@ -1000,9 +1046,11 @@ struct Player {
|
||||
const int result = avcodec_receive_frame(track.codec, decoded);
|
||||
if (result == AVERROR(EAGAIN)) {
|
||||
track.receiving = false;
|
||||
track.decoderBufferedEnd = 0;
|
||||
} else if (result == AVERROR_EOF) {
|
||||
track.drained = true;
|
||||
track.receiving = false;
|
||||
track.decoderBufferedEnd = 0;
|
||||
} else {
|
||||
check(result, "Decoding media");
|
||||
if (slot == 0) convertAudio(); else queueVideo(stepping);
|
||||
@@ -1015,6 +1063,7 @@ struct Player {
|
||||
const int result = avcodec_send_packet(track.codec, track.packet);
|
||||
if (result != AVERROR(EAGAIN)) {
|
||||
check(result, "Sending media packet to decoder");
|
||||
track.decoderBufferedEnd = std::max(track.decoderBufferedEnd, track.packetBufferedEnd);
|
||||
av_packet_unref(track.packet);
|
||||
track.packetPending = false;
|
||||
}
|
||||
@@ -1023,7 +1072,7 @@ struct Player {
|
||||
return true;
|
||||
}
|
||||
if (track.input && !track.sourceEnded) {
|
||||
const int result = track.input->take(slot, track.packet);
|
||||
const int result = track.input->take(slot, track.packet, track.packetBufferedEnd);
|
||||
if (result == AVERROR_EOF) track.sourceEnded = true;
|
||||
else if (result == 1) track.packetPending = true;
|
||||
else check(result, "Reading network media packet");
|
||||
@@ -1304,7 +1353,11 @@ PlayerState player_get_state(Player *player) {
|
||||
try {
|
||||
if (player) {
|
||||
std::lock_guard<std::mutex> lock(player->mutex);
|
||||
return player->state;
|
||||
PlayerState result = player->state;
|
||||
if (result.seek_pending ||
|
||||
player->publishedEpoch != player->requestedEpoch.load(std::memory_order_relaxed))
|
||||
result.buffered_start = result.buffered_end = 0;
|
||||
return result;
|
||||
}
|
||||
} catch (const std::exception &exception) {
|
||||
PlayerState result{};
|
||||
|
||||
Reference in New Issue
Block a user