Skip to content

Commit

Permalink
Improve framerate counting
Browse files Browse the repository at this point in the history
The FPS counter was called only on new frames, so it could not print
values regularly, especially when there are very few FPS (when the
device surface does not change).

To the extreme, it was never able to display 0 fps.

Add a separate thread to print framerate every second.
  • Loading branch information
rom1v committed Jun 7, 2019
1 parent d104d3b commit e2a272b
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 51 deletions.
161 changes: 135 additions & 26 deletions app/src/fps_counter.c
Original file line number Diff line number Diff line change
@@ -1,60 +1,169 @@
#include "fps_counter.h"

#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_timer.h>

#include "lock_util.h"
#include "log.h"

void
#define FPS_COUNTER_INTERVAL_MS 1000

bool
fps_counter_init(struct fps_counter *counter) {
counter->started = false;
// no need to initialize the other fields, they are meaningful only when
// started is true
}
counter->mutex = SDL_CreateMutex();
if (!counter->mutex) {
return false;
}

void
fps_counter_start(struct fps_counter *counter) {
counter->started = true;
counter->slice_start = SDL_GetTicks();
counter->nr_rendered = 0;
counter->nr_skipped = 0;
counter->state_cond = SDL_CreateCond();
if (!counter->state_cond) {
SDL_DestroyMutex(counter->mutex);
return false;
}

counter->thread = NULL;
SDL_AtomicSet(&counter->started, 0);
// no need to initialize the other fields, they are unused until started

return true;
}

void
fps_counter_stop(struct fps_counter *counter) {
counter->started = false;
fps_counter_destroy(struct fps_counter *counter) {
SDL_DestroyCond(counter->state_cond);
SDL_DestroyMutex(counter->mutex);
}

// must be called with mutex locked
static void
display_fps(struct fps_counter *counter) {
unsigned rendered_per_second =
counter->nr_rendered * 1000 / FPS_COUNTER_INTERVAL_MS;
if (counter->nr_skipped) {
LOGI("%d fps (+%d frames skipped)", counter->nr_rendered,
LOGI("%u fps (+%u frames skipped)", rendered_per_second,
counter->nr_skipped);
} else {
LOGI("%d fps", counter->nr_rendered);
LOGI("%u fps", rendered_per_second);
}
}

// must be called with mutex locked
static void
check_expired(struct fps_counter *counter) {
uint32_t now = SDL_GetTicks();
if (now - counter->slice_start >= 1000) {
display_fps(counter);
// add a multiple of one second
uint32_t elapsed_slices = (now - counter->slice_start) / 1000;
counter->slice_start += 1000 * elapsed_slices;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
check_interval_expired(struct fps_counter *counter, uint32_t now) {
if (now < counter->next_timestamp) {
return;
}

display_fps(counter);
counter->nr_rendered = 0;
counter->nr_skipped = 0;
// add a multiple of the interval
uint32_t elapsed_slices =
(now - counter->next_timestamp) / FPS_COUNTER_INTERVAL_MS + 1;
counter->next_timestamp += FPS_COUNTER_INTERVAL_MS * elapsed_slices;
}

static int
run_fps_counter(void *data) {
struct fps_counter *counter = data;

mutex_lock(counter->mutex);
while (!counter->interrupted) {
while (!counter->interrupted && !SDL_AtomicGet(&counter->started)) {
cond_wait(counter->state_cond, counter->mutex);
}
while (!counter->interrupted && SDL_AtomicGet(&counter->started)) {
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);

SDL_assert(counter->next_timestamp > now);
uint32_t remaining = counter->next_timestamp - now;

// ignore the reason (timeout or signaled), we just loop anyway
cond_wait_timeout(counter->state_cond, counter->mutex, remaining);
}
}
mutex_unlock(counter->mutex);
return 0;
}

bool
fps_counter_start(struct fps_counter *counter) {
mutex_lock(counter->mutex);
counter->next_timestamp = SDL_GetTicks() + FPS_COUNTER_INTERVAL_MS;
counter->nr_rendered = 0;
counter->nr_skipped = 0;
mutex_unlock(counter->mutex);

SDL_AtomicSet(&counter->started, 1);
cond_signal(counter->state_cond);

// counter->thread is always accessed from the same thread, no need to lock
if (!counter->thread) {
counter->thread =
SDL_CreateThread(run_fps_counter, "fps counter", counter);
if (!counter->thread) {
LOGE("Could not start FPS counter thread");
return false;
}
}

return true;
}

void
fps_counter_stop(struct fps_counter *counter) {
SDL_AtomicSet(&counter->started, 0);
cond_signal(counter->state_cond);
}

bool
fps_counter_is_started(struct fps_counter *counter) {
return SDL_AtomicGet(&counter->started);
}

void
fps_counter_interrupt(struct fps_counter *counter) {
if (!counter->thread) {
return;
}

mutex_lock(counter->mutex);
counter->interrupted = true;
mutex_unlock(counter->mutex);
// wake up blocking wait
cond_signal(counter->state_cond);
}

void
fps_counter_join(struct fps_counter *counter) {
if (counter->thread) {
SDL_WaitThread(counter->thread, NULL);
}
}

void
fps_counter_add_rendered_frame(struct fps_counter *counter) {
check_expired(counter);
if (!SDL_AtomicGet(&counter->started)) {
return;
}

mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_rendered;
mutex_unlock(counter->mutex);
}

void
fps_counter_add_skipped_frame(struct fps_counter *counter) {
check_expired(counter);
if (!SDL_AtomicGet(&counter->started)) {
return;
}

mutex_lock(counter->mutex);
uint32_t now = SDL_GetTicks();
check_interval_expired(counter, now);
++counter->nr_skipped;
mutex_unlock(counter->mutex);
}
36 changes: 31 additions & 5 deletions app/src/fps_counter.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,49 @@

#include <stdbool.h>
#include <stdint.h>
#include <SDL2/SDL_atomic.h>
#include <SDL2/SDL_mutex.h>
#include <SDL2/SDL_thread.h>

struct fps_counter {
bool started;
uint32_t slice_start; // initialized by SDL_GetTicks()
int nr_rendered;
int nr_skipped;
SDL_Thread *thread;
SDL_mutex *mutex;
SDL_cond *state_cond;

// atomic so that we can check without locking the mutex
// if the FPS counter is disabled, we don't want to lock unnecessarily
SDL_atomic_t started;

// the following fields are protected by the mutex
bool interrupted;
unsigned nr_rendered;
unsigned nr_skipped;
uint32_t next_timestamp;
};

void
bool
fps_counter_init(struct fps_counter *counter);

void
fps_counter_destroy(struct fps_counter *counter);

bool
fps_counter_start(struct fps_counter *counter);

void
fps_counter_stop(struct fps_counter *counter);

bool
fps_counter_is_started(struct fps_counter *counter);

// request to stop the thread (on quit)
// must be called before fps_counter_join()
void
fps_counter_interrupt(struct fps_counter *counter);

void
fps_counter_join(struct fps_counter *counter);

void
fps_counter_add_rendered_frame(struct fps_counter *counter);

Expand Down
21 changes: 13 additions & 8 deletions app/src/input_manager.c
Original file line number Diff line number Diff line change
Expand Up @@ -172,16 +172,19 @@ set_screen_power_mode(struct controller *controller,
}

static void
switch_fps_counter_state(struct video_buffer *vb) {
mutex_lock(vb->mutex);
if (vb->fps_counter.started) {
switch_fps_counter_state(struct fps_counter *fps_counter) {
// the started state can only be written from the current thread, so there
// is no ToCToU issue
if (fps_counter_is_started(fps_counter)) {
fps_counter_stop(fps_counter);
LOGI("FPS counter stopped");
fps_counter_stop(&vb->fps_counter);
} else {
LOGI("FPS counter started");
fps_counter_start(&vb->fps_counter);
if (fps_counter_start(fps_counter)) {
LOGI("FPS counter started");
} else {
LOGE("FPS counter starting failed");
}
}
mutex_unlock(vb->mutex);
}

static void
Expand Down Expand Up @@ -339,7 +342,9 @@ input_manager_process_key(struct input_manager *input_manager,
return;
case SDLK_i:
if (ctrl && !meta && !shift && !repeat && down) {
switch_fps_counter_state(input_manager->video_buffer);
struct fps_counter *fps_counter =
input_manager->video_buffer->fps_counter;
switch_fps_counter_state(fps_counter);
}
return;
case SDLK_n:
Expand Down
18 changes: 17 additions & 1 deletion app/src/scrcpy.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

static struct server server = SERVER_INITIALIZER;
static struct screen screen = SCREEN_INITIALIZER;
static struct fps_counter fps_counter;
static struct video_buffer video_buffer;
static struct stream stream;
static struct decoder decoder;
Expand Down Expand Up @@ -293,6 +294,7 @@ scrcpy(const struct scrcpy_options *options) {

bool ret = false;

bool fps_counter_initialized = false;
bool video_buffer_initialized = false;
bool file_handler_initialized = false;
bool recorder_initialized = false;
Expand Down Expand Up @@ -320,7 +322,13 @@ scrcpy(const struct scrcpy_options *options) {

struct decoder *dec = NULL;
if (options->display) {
if (!video_buffer_init(&video_buffer, options->render_expired_frames)) {
if (!fps_counter_init(&fps_counter)) {
goto end;
}
fps_counter_initialized = true;

if (!video_buffer_init(&video_buffer, &fps_counter,
options->render_expired_frames)) {
goto end;
}
video_buffer_initialized = true;
Expand Down Expand Up @@ -414,6 +422,9 @@ scrcpy(const struct scrcpy_options *options) {
if (file_handler_initialized) {
file_handler_stop(&file_handler);
}
if (fps_counter_initialized) {
fps_counter_interrupt(&fps_counter);
}

// shutdown the sockets and kill the server
server_stop(&server);
Expand Down Expand Up @@ -443,6 +454,11 @@ scrcpy(const struct scrcpy_options *options) {
video_buffer_destroy(&video_buffer);
}

if (fps_counter_initialized) {
fps_counter_join(&fps_counter);
fps_counter_destroy(&fps_counter);
}

if (options->show_touches) {
if (!show_touches_waited) {
// wait the process which enabled "show touches"
Expand Down
16 changes: 7 additions & 9 deletions app/src/video_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
#include "log.h"

bool
video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
video_buffer_init(struct video_buffer *vb, struct fps_counter *fps_counter,
bool render_expired_frames) {
vb->fps_counter = fps_counter;

if (!(vb->decoding_frame = av_frame_alloc())) {
goto error_0;
}
Expand All @@ -37,7 +40,6 @@ video_buffer_init(struct video_buffer *vb, bool render_expired_frames) {
// there is initially no rendering frame, so consider it has already been
// consumed
vb->rendering_frame_consumed = true;
fps_counter_init(&vb->fps_counter);

return true;

Expand Down Expand Up @@ -75,10 +77,8 @@ video_buffer_offer_decoded_frame(struct video_buffer *vb,
while (!vb->rendering_frame_consumed && !vb->interrupted) {
cond_wait(vb->rendering_frame_consumed_cond, vb->mutex);
}
} else {
if (vb->fps_counter.started && !vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(&vb->fps_counter);
}
} else if (!vb->rendering_frame_consumed) {
fps_counter_add_skipped_frame(vb->fps_counter);
}

video_buffer_swap_frames(vb);
Expand All @@ -93,9 +93,7 @@ const AVFrame *
video_buffer_consume_rendered_frame(struct video_buffer *vb) {
SDL_assert(!vb->rendering_frame_consumed);
vb->rendering_frame_consumed = true;
if (vb->fps_counter.started) {
fps_counter_add_rendered_frame(&vb->fps_counter);
}
fps_counter_add_rendered_frame(vb->fps_counter);
if (vb->render_expired_frames) {
// unblock video_buffer_offer_decoded_frame()
cond_signal(vb->rendering_frame_consumed_cond);
Expand Down
Loading

0 comments on commit e2a272b

Please sign in to comment.