Draw video decoder/render stats

This commit is contained in:
rock88 2020-05-14 23:58:02 +03:00
parent b006d99be2
commit 7fb1564171
11 changed files with 169 additions and 15 deletions

View file

@ -28,6 +28,9 @@ extern struct GamePadState game_pad_state;
#define GAME_PAD_COMBO(KEY) \
((game_pad_state.buttonFlags & LB_FLAG) && (game_pad_state.buttonFlags & RB_FLAG) && (game_pad_state.buttonFlags & KEY))
#define GAME_PAD_COMBO_R(KEY) \
(game_pad_state.leftTrigger && game_pad_state.rightTrigger && (game_pad_state.buttonFlags & KEY))
class InputController {
public:
static InputController* controller() {

View file

@ -82,7 +82,9 @@ void MoonlightSession::connection_rumble(unsigned short controller, unsigned sho
}
void MoonlightSession::connection_status_update(int connection_status) {
if (m_active_session) {
m_active_session->m_connection_status_is_poor = connection_status == CONN_STATUS_POOR;
}
}
// MARK: Video decoder callbacks
@ -224,6 +226,7 @@ void MoonlightSession::start(std::function<void(bool)> callback) {
});
});
} else {
LOG_FMT("Failed to start stream: %s\n", result.error().c_str());
callback(false);
}
});
@ -242,5 +245,8 @@ void MoonlightSession::draw() {
if (auto frame = m_video_decoder->frame()) {
m_video_renderer->draw(m_config.width, m_config.height, frame);
}
m_session_stats.video_decode_stats = *m_video_decoder->video_decode_stats();
m_session_stats.video_render_stats = *m_video_renderer->video_render_stats();
}
}

View file

@ -4,6 +4,11 @@
#include "IFFmpegVideoDecoder.hpp"
#pragma once
struct SessionStats {
VideoDecodeStats video_decode_stats;
VideoRenderStats video_render_stats;
};
class MoonlightSession {
public:
MoonlightSession(const std::string &address, int app_id);
@ -30,6 +35,14 @@ public:
return m_is_active;
}
bool connection_status_is_poor() const {
return m_connection_status_is_poor;
}
SessionStats* session_stats() const {
return (SessionStats*)&m_session_stats;
}
private:
static void connection_stage_starting(int);
static void connection_stage_complete(int);
@ -64,4 +77,7 @@ private:
IAudioRenderer* m_audio_renderer = nullptr;
bool m_is_active = true;
bool m_connection_status_is_poor = false;
SessionStats m_session_stats = {};
};

View file

@ -30,6 +30,8 @@ FFmpegVideoDecoder::~FFmpegVideoDecoder() {
}
int FFmpegVideoDecoder::setup(int video_format, int width, int height, int redraw_rate, void *context, int dr_flags) {
m_stream_fps = redraw_rate;
av_log_set_level(AV_LOG_QUIET);
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58,10,100)
avcodec_register_all();
@ -37,7 +39,7 @@ int FFmpegVideoDecoder::setup(int video_format, int width, int height, int redra
av_init_packet(&m_packet);
int perf_lvl = SLICE_THREADING;
int perf_lvl = LOW_LATENCY_DECODE & SLICE_THREADING;
switch (video_format) {
case VIDEO_FORMAT_H264:
@ -72,7 +74,7 @@ int FFmpegVideoDecoder::setup(int video_format, int width, int height, int redra
else
m_decoder_context->thread_type = FF_THREAD_FRAME;
m_decoder_context->thread_count = 2;
m_decoder_context->thread_count = 4;
m_decoder_context->width = width;
m_decoder_context->height = height;
@ -139,19 +141,50 @@ void FFmpegVideoDecoder::cleanup() {
int FFmpegVideoDecoder::submit_decode_unit(PDECODE_UNIT decode_unit) {
if (decode_unit->fullLength < DECODER_BUFFER_SIZE) {
PLENTRY entry = decode_unit->bufferList;
if (!m_last_frame) {
m_video_decode_stats.measurement_start_timestamp = LiGetMillis();
m_last_frame = decode_unit->frameNumber;
}
else {
// Any frame number greater than m_LastFrameNumber + 1 represents a dropped frame
m_video_decode_stats.network_dropped_frames += decode_unit->frameNumber - (m_last_frame + 1);
m_video_decode_stats.total_frames += decode_unit->frameNumber - (m_last_frame + 1);
m_last_frame = decode_unit->frameNumber;
}
m_video_decode_stats.received_frames++;
m_video_decode_stats.total_frames++;
int length = 0;
while (entry != NULL) {
memcpy(m_ffmpeg_buffer + length, entry->data, entry->length);
length += entry->length;
entry = entry->next;
}
decode(m_ffmpeg_buffer, length);
if (pthread_mutex_lock(&m_mutex) == 0) {
m_frame = get_frame(true);
m_video_decode_stats.total_reassembly_time += LiGetMillis() - decode_unit->receiveTimeMs;
m_frames_in++;
uint64_t before_decode = LiGetMillis();
if (decode(m_ffmpeg_buffer, length) == 0) {
m_frames_out++;
// Push event!!
pthread_mutex_unlock(&m_mutex);
m_video_decode_stats.total_decode_time += LiGetMillis() - before_decode;
// Also count the frame-to-frame delay if the decoder is delaying frames
// until a subsequent frame is submitted.
m_video_decode_stats.total_decode_time += (m_frames_in - m_frames_out) * (1000 / m_stream_fps);
m_video_decode_stats.decoded_frames++;
if (pthread_mutex_lock(&m_mutex) == 0) {
m_frame = get_frame(true);
// Push event!!
pthread_mutex_unlock(&m_mutex);
}
}
}
return DR_OK;
@ -194,3 +227,11 @@ AVFrame* FFmpegVideoDecoder::get_frame(bool native_frame) {
AVFrame* FFmpegVideoDecoder::frame() const {
return m_frame;
}
VideoDecodeStats* FFmpegVideoDecoder::video_decode_stats() {
uint64_t now = LiGetMillis();
m_video_decode_stats.total_fps = (float)m_video_decode_stats.total_frames / ((float)(now - m_video_decode_stats.measurement_start_timestamp) / 1000);
m_video_decode_stats.received_fps = (float)m_video_decode_stats.received_frames / ((float)(now - m_video_decode_stats.measurement_start_timestamp) / 1000);
m_video_decode_stats.decoded_fps = (float)m_video_decode_stats.decoded_frames / ((float)(now - m_video_decode_stats.measurement_start_timestamp) / 1000);
return (VideoDecodeStats*)&m_video_decode_stats;
}

View file

@ -22,6 +22,7 @@ public:
int submit_decode_unit(PDECODE_UNIT decode_unit) override;
int capabilities() const override;
AVFrame* frame() const override;
VideoDecodeStats* video_decode_stats() override;
private:
int decode(char* indata, int inlen);
@ -34,8 +35,14 @@ private:
AVCodecContext* m_decoder_context = nullptr;
AVFrame** m_frames = nullptr;
int m_frames_count;
int m_stream_fps = 0;
int m_frames_in = 0;
int m_frames_out = 0;
int m_frames_count = 0;
int m_current_frame = 0, m_next_frame = 0;
uint32_t m_last_frame = 0;
VideoDecodeStats m_video_decode_stats = {};
char* m_ffmpeg_buffer = nullptr;
AVFrame* m_frame = nullptr;

View file

@ -5,6 +5,19 @@ extern "C" {
#include <libavcodec/avcodec.h>
}
struct VideoDecodeStats {
uint32_t received_frames;
uint32_t decoded_frames;
uint32_t total_frames;
uint32_t network_dropped_frames;
uint32_t total_reassembly_time;
uint32_t total_decode_time;
float total_fps;
float received_fps;
float decoded_fps;
uint64_t measurement_start_timestamp;
};
class IFFmpegVideoDecoder {
public:
virtual ~IFFmpegVideoDecoder() {};
@ -15,4 +28,5 @@ public:
virtual int submit_decode_unit(PDECODE_UNIT decode_unit) = 0;
virtual int capabilities() const = 0;
virtual AVFrame* frame() const = 0;
virtual VideoDecodeStats* video_decode_stats() = 0;
};

View file

@ -150,6 +150,12 @@ void GLVideoRenderer::initialize() {
}
void GLVideoRenderer::draw(int width, int height, AVFrame *frame) {
if (!m_video_render_stats.rendered_frames) {
m_video_render_stats.measurement_start_timestamp = LiGetMillis();
}
uint32_t before_render = LiGetMillis();
if (!m_is_initialized) {
initialize();
m_is_initialized = true;
@ -194,4 +200,12 @@ void GLVideoRenderer::draw(int width, int height, AVFrame *frame) {
glBindVertexArray(m_vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
m_video_render_stats.total_render_time += LiGetMillis() - before_render;
m_video_render_stats.rendered_frames++;
}
VideoRenderStats* GLVideoRenderer::video_render_stats() {
m_video_render_stats.rendered_fps = (float)m_video_render_stats.rendered_frames / ((float)(LiGetMillis() - m_video_render_stats.measurement_start_timestamp) / 1000);
return (VideoRenderStats*)&m_video_render_stats;
}

View file

@ -9,6 +9,8 @@ public:
void draw(int width, int height, AVFrame *frame) override;
VideoRenderStats* video_render_stats() override;
private:
void initialize();
@ -18,4 +20,5 @@ private:
GLuint m_vbo, m_vao;
int m_width = 0, m_height = 0;
int m_yuvmat_location, m_offset_location;
VideoRenderStats m_video_render_stats = {};
};

View file

@ -5,8 +5,16 @@ extern "C" {
#include <libavcodec/avcodec.h>
}
struct VideoRenderStats {
uint32_t rendered_frames;
uint64_t total_render_time;
float rendered_fps;
double measurement_start_timestamp;
};
class IVideoRenderer {
public:
virtual ~IVideoRenderer() {};
virtual void draw(int width, int height, AVFrame* frame) = 0;
virtual VideoRenderStats* video_render_stats() = 0;
};

View file

@ -24,11 +24,13 @@ StreamWindow::StreamWindow(Widget *parent, const std::string &address, int app_i
m_loader = add<LoadingOverlay>();
m_session->start([this](auto result) {
if (m_loader) {
m_loader->dispose();
m_loader = NULL;
}
if (result) {
if (m_loader) {
m_loader->dispose();
m_loader = NULL;
}
//
} else {
auto app = static_cast<Application *>(screen());
app->pop_window();
@ -51,7 +53,7 @@ void StreamWindow::draw(NVGcontext *ctx) {
nvgRestore(ctx);
if (m_connection_status_is_poor) {
if (m_session->connection_status_is_poor()) {
nvgFillColor(ctx, Color(255, 255, 255, 200));
nvgFontSize(ctx, 20);
nvgFontFace(ctx, "icons");
@ -61,6 +63,40 @@ void StreamWindow::draw(NVGcontext *ctx) {
nvgText(ctx, 50, height() - 28, "Bad connection...", NULL);
}
if (m_draw_stats) {
static char output[1024];
int offset = 0;
auto stats = m_session->session_stats();
offset += sprintf(&output[offset],
"Estimated host PC frame rate: %.2f FPS\n"
"Incoming frame rate from network: %.2f FPS\n"
"Decoding frame rate: %.2f FPS\n"
"Rendering frame rate: %.2f FPS\n",
stats->video_decode_stats.total_fps,
stats->video_decode_stats.received_fps,
stats->video_decode_stats.decoded_fps,
stats->video_render_stats.rendered_fps);
offset += sprintf(&output[offset],
"Frames dropped by your network connection: %.2f%%\n"
"Average receive time: %.2f ms\n"
"Average decoding time: %.2f ms\n"
"Average rendering time: %.2f ms\n",
(float)stats->video_decode_stats.network_dropped_frames / stats->video_decode_stats.total_frames * 100,
(float)stats->video_decode_stats.total_reassembly_time / stats->video_decode_stats.received_frames,
(float)stats->video_decode_stats.total_decode_time / stats->video_decode_stats.decoded_frames,
(float)stats->video_render_stats.total_render_time / stats->video_render_stats.rendered_frames);
nvgFillColor(ctx, Color(0, 255, 0, 255));
nvgFontFace(ctx, "sans-bold");
nvgFontSize(ctx, 20);
nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM);
nvgTextBox(ctx, 20, 30, width(), output, NULL);
}
// TODO: Get out of here...
if (GAME_PAD_COMBO(DOWN_FLAG)) {
async([this] { this->terminate(true); });
@ -68,6 +104,12 @@ void StreamWindow::draw(NVGcontext *ctx) {
async([this] { this->terminate(false); });
}
if (!m_draw_stats && GAME_PAD_COMBO_R(LEFT_FLAG)) {
m_draw_stats = true;
} else if (m_draw_stats && GAME_PAD_COMBO_R(LEFT_FLAG)) {
m_draw_stats = false;
}
InputController::controller()->send_to_stream();
}

View file

@ -19,5 +19,5 @@ public:
private:
MoonlightSession* m_session;
LoadingOverlay* m_loader;
bool m_connection_status_is_poor = false;
bool m_draw_stats = false;
};