mirror of
https://github.com/rock88/moonlight-nx
synced 2025-02-17 04:58:31 +00:00
Draw video decoder/render stats
This commit is contained in:
parent
b006d99be2
commit
7fb1564171
11 changed files with 169 additions and 15 deletions
|
@ -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() {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = {};
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -19,5 +19,5 @@ public:
|
|||
private:
|
||||
MoonlightSession* m_session;
|
||||
LoadingOverlay* m_loader;
|
||||
bool m_connection_status_is_poor = false;
|
||||
bool m_draw_stats = false;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue