Compare commits
4 Commits
develop
...
buffering.
Author | SHA1 | Date | |
---|---|---|---|
86c4cbcf9e | |||
7917dc313e | |||
d743e03f44 | |||
9217cfa079 |
@ -10,6 +10,7 @@ src = [
|
||||
'src/event_converter.c',
|
||||
'src/file_handler.c',
|
||||
'src/fps_counter.c',
|
||||
'src/frame_buffer.c',
|
||||
'src/input_manager.c',
|
||||
'src/opengl.c',
|
||||
'src/receiver.c',
|
||||
|
88
app/src/frame_buffer.c
Normal file
88
app/src/frame_buffer.c
Normal file
@ -0,0 +1,88 @@
|
||||
#include "frame_buffer.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
sc_frame_buffer_init(struct sc_frame_buffer *fb) {
|
||||
fb->pending_frame = av_frame_alloc();
|
||||
if (!fb->pending_frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fb->tmp_frame = av_frame_alloc();
|
||||
if (!fb->tmp_frame) {
|
||||
av_frame_free(&fb->pending_frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = sc_mutex_init(&fb->mutex);
|
||||
if (!ok) {
|
||||
av_frame_free(&fb->pending_frame);
|
||||
av_frame_free(&fb->tmp_frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there is initially no frame, so consider it has already been consumed
|
||||
fb->pending_frame_consumed = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_frame_buffer_destroy(struct sc_frame_buffer *fb) {
|
||||
sc_mutex_destroy(&fb->mutex);
|
||||
av_frame_free(&fb->pending_frame);
|
||||
av_frame_free(&fb->tmp_frame);
|
||||
}
|
||||
|
||||
static inline void
|
||||
swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
||||
AVFrame *tmp = *lhs;
|
||||
*lhs = *rhs;
|
||||
*rhs = tmp;
|
||||
}
|
||||
|
||||
bool
|
||||
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
||||
bool *previous_frame_skipped) {
|
||||
sc_mutex_lock(&fb->mutex);
|
||||
|
||||
// Use a temporary frame to preserve pending_frame in case of error.
|
||||
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
||||
int r = av_frame_ref(fb->tmp_frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that av_frame_ref() succeeded, we can replace the previous
|
||||
// pending_frame
|
||||
swap_frames(&fb->pending_frame, &fb->tmp_frame);
|
||||
av_frame_unref(fb->tmp_frame);
|
||||
|
||||
if (previous_frame_skipped) {
|
||||
*previous_frame_skipped = !fb->pending_frame_consumed;
|
||||
}
|
||||
fb->pending_frame_consumed = false;
|
||||
|
||||
sc_mutex_unlock(&fb->mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst) {
|
||||
sc_mutex_lock(&fb->mutex);
|
||||
assert(!fb->pending_frame_consumed);
|
||||
fb->pending_frame_consumed = true;
|
||||
|
||||
av_frame_move_ref(dst, fb->pending_frame);
|
||||
// av_frame_move_ref() resets its source frame, so no need to call
|
||||
// av_frame_unref()
|
||||
|
||||
sc_mutex_unlock(&fb->mutex);
|
||||
}
|
44
app/src/frame_buffer.h
Normal file
44
app/src/frame_buffer.h
Normal file
@ -0,0 +1,44 @@
|
||||
#ifndef SC_FRAME_BUFFER_H
|
||||
#define SC_FRAME_BUFFER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "util/thread.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
/**
|
||||
* A frame buffer holds 1 pending frame, which is the last frame received from
|
||||
* the producer (typically, the decoder).
|
||||
*
|
||||
* If a pending frame has not been consumed when the producer pushes a new
|
||||
* frame, then it is lost. The intent is to always provide access to the very
|
||||
* last frame to minimize latency.
|
||||
*/
|
||||
|
||||
struct sc_frame_buffer {
|
||||
AVFrame *pending_frame;
|
||||
AVFrame *tmp_frame; // To preserve the pending frame on error
|
||||
|
||||
sc_mutex mutex;
|
||||
|
||||
bool pending_frame_consumed;
|
||||
};
|
||||
|
||||
bool
|
||||
sc_frame_buffer_init(struct sc_frame_buffer *fb);
|
||||
|
||||
void
|
||||
sc_frame_buffer_destroy(struct sc_frame_buffer *fb);
|
||||
|
||||
bool
|
||||
sc_frame_buffer_push(struct sc_frame_buffer *fb, const AVFrame *frame,
|
||||
bool *skipped);
|
||||
|
||||
void
|
||||
sc_frame_buffer_consume(struct sc_frame_buffer *fb, AVFrame *dst);
|
||||
|
||||
#endif
|
@ -276,7 +276,7 @@ screen_frame_sink_push(struct sc_frame_sink *sink, const AVFrame *frame) {
|
||||
struct screen *screen = DOWNCAST(sink);
|
||||
|
||||
bool previous_frame_skipped;
|
||||
bool ok = video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
|
||||
bool ok = sc_video_buffer_push(&screen->vb, frame, &previous_frame_skipped);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@ -304,7 +304,7 @@ screen_init(struct screen *screen, const struct screen_params *params) {
|
||||
screen->fullscreen = false;
|
||||
screen->maximized = false;
|
||||
|
||||
bool ok = video_buffer_init(&screen->vb);
|
||||
bool ok = sc_video_buffer_init(&screen->vb);
|
||||
if (!ok) {
|
||||
LOGE("Could not initialize video buffer");
|
||||
return false;
|
||||
@ -454,7 +454,7 @@ error_destroy_window:
|
||||
error_destroy_fps_counter:
|
||||
fps_counter_destroy(&screen->fps_counter);
|
||||
error_destroy_video_buffer:
|
||||
video_buffer_destroy(&screen->vb);
|
||||
sc_video_buffer_destroy(&screen->vb);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -489,7 +489,7 @@ screen_destroy(struct screen *screen) {
|
||||
SDL_DestroyRenderer(screen->renderer);
|
||||
SDL_DestroyWindow(screen->window);
|
||||
fps_counter_destroy(&screen->fps_counter);
|
||||
video_buffer_destroy(&screen->vb);
|
||||
sc_video_buffer_destroy(&screen->vb);
|
||||
}
|
||||
|
||||
static void
|
||||
@ -595,7 +595,7 @@ update_texture(struct screen *screen, const AVFrame *frame) {
|
||||
static bool
|
||||
screen_update_frame(struct screen *screen) {
|
||||
av_frame_unref(screen->frame);
|
||||
video_buffer_consume(&screen->vb, screen->frame);
|
||||
sc_video_buffer_consume(&screen->vb, screen->frame);
|
||||
AVFrame *frame = screen->frame;
|
||||
|
||||
fps_counter_add_rendered_frame(&screen->fps_counter);
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include "coords.h"
|
||||
#include "fps_counter.h"
|
||||
#include "opengl.h"
|
||||
#include "trait/frame_sink.h"
|
||||
#include "video_buffer.h"
|
||||
@ -19,7 +20,7 @@ struct screen {
|
||||
bool open; // track the open/close state to assert correct behavior
|
||||
#endif
|
||||
|
||||
struct video_buffer vb;
|
||||
struct sc_video_buffer vb;
|
||||
struct fps_counter fps_counter;
|
||||
|
||||
SDL_Window *window;
|
||||
|
@ -121,7 +121,7 @@ run_v4l2_sink(void *data) {
|
||||
break;
|
||||
}
|
||||
|
||||
video_buffer_consume(&vs->vb, vs->frame);
|
||||
sc_video_buffer_consume(&vs->vb, vs->frame);
|
||||
vs->has_frame = false;
|
||||
|
||||
sc_mutex_unlock(&vs->mutex);
|
||||
@ -141,7 +141,7 @@ run_v4l2_sink(void *data) {
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_open(struct sc_v4l2_sink *vs) {
|
||||
bool ok = video_buffer_init(&vs->vb);
|
||||
bool ok = sc_video_buffer_init(&vs->vb);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
@ -275,7 +275,7 @@ error_cond_destroy:
|
||||
error_mutex_destroy:
|
||||
sc_mutex_destroy(&vs->mutex);
|
||||
error_video_buffer_destroy:
|
||||
video_buffer_destroy(&vs->vb);
|
||||
sc_video_buffer_destroy(&vs->vb);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -297,14 +297,14 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) {
|
||||
avformat_free_context(vs->format_ctx);
|
||||
sc_cond_destroy(&vs->cond);
|
||||
sc_mutex_destroy(&vs->mutex);
|
||||
video_buffer_destroy(&vs->vb);
|
||||
sc_video_buffer_destroy(&vs->vb);
|
||||
}
|
||||
|
||||
static bool
|
||||
sc_v4l2_sink_push(struct sc_v4l2_sink *vs, const AVFrame *frame) {
|
||||
sc_mutex_lock(&vs->mutex);
|
||||
|
||||
bool ok = video_buffer_push(&vs->vb, frame, NULL);
|
||||
bool ok = sc_video_buffer_push(&vs->vb, frame, NULL);
|
||||
if (!ok) {
|
||||
return false;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
struct sc_v4l2_sink {
|
||||
struct sc_frame_sink frame_sink; // frame sink trait
|
||||
|
||||
struct video_buffer vb;
|
||||
struct sc_video_buffer vb;
|
||||
AVFormatContext *format_ctx;
|
||||
AVCodecContext *encoder_ctx;
|
||||
|
||||
|
@ -7,82 +7,22 @@
|
||||
#include "util/log.h"
|
||||
|
||||
bool
|
||||
video_buffer_init(struct video_buffer *vb) {
|
||||
vb->pending_frame = av_frame_alloc();
|
||||
if (!vb->pending_frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vb->tmp_frame = av_frame_alloc();
|
||||
if (!vb->tmp_frame) {
|
||||
av_frame_free(&vb->pending_frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = sc_mutex_init(&vb->mutex);
|
||||
if (!ok) {
|
||||
av_frame_free(&vb->pending_frame);
|
||||
av_frame_free(&vb->tmp_frame);
|
||||
return false;
|
||||
}
|
||||
|
||||
// there is initially no frame, so consider it has already been consumed
|
||||
vb->pending_frame_consumed = true;
|
||||
|
||||
return true;
|
||||
sc_video_buffer_init(struct sc_video_buffer *vb) {
|
||||
return sc_frame_buffer_init(&vb->fb);
|
||||
}
|
||||
|
||||
void
|
||||
video_buffer_destroy(struct video_buffer *vb) {
|
||||
sc_mutex_destroy(&vb->mutex);
|
||||
av_frame_free(&vb->pending_frame);
|
||||
av_frame_free(&vb->tmp_frame);
|
||||
}
|
||||
|
||||
static inline void
|
||||
swap_frames(AVFrame **lhs, AVFrame **rhs) {
|
||||
AVFrame *tmp = *lhs;
|
||||
*lhs = *rhs;
|
||||
*rhs = tmp;
|
||||
sc_video_buffer_destroy(struct sc_video_buffer *vb) {
|
||||
sc_frame_buffer_destroy(&vb->fb);
|
||||
}
|
||||
|
||||
bool
|
||||
video_buffer_push(struct video_buffer *vb, const AVFrame *frame,
|
||||
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame,
|
||||
bool *previous_frame_skipped) {
|
||||
sc_mutex_lock(&vb->mutex);
|
||||
|
||||
// Use a temporary frame to preserve pending_frame in case of error.
|
||||
// tmp_frame is an empty frame, no need to call av_frame_unref() beforehand.
|
||||
int r = av_frame_ref(vb->tmp_frame, frame);
|
||||
if (r) {
|
||||
LOGE("Could not ref frame: %d", r);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now that av_frame_ref() succeeded, we can replace the previous
|
||||
// pending_frame
|
||||
swap_frames(&vb->pending_frame, &vb->tmp_frame);
|
||||
av_frame_unref(vb->tmp_frame);
|
||||
|
||||
if (previous_frame_skipped) {
|
||||
*previous_frame_skipped = !vb->pending_frame_consumed;
|
||||
}
|
||||
vb->pending_frame_consumed = false;
|
||||
|
||||
sc_mutex_unlock(&vb->mutex);
|
||||
|
||||
return true;
|
||||
return sc_frame_buffer_push(&vb->fb, frame, previous_frame_skipped);
|
||||
}
|
||||
|
||||
void
|
||||
video_buffer_consume(struct video_buffer *vb, AVFrame *dst) {
|
||||
sc_mutex_lock(&vb->mutex);
|
||||
assert(!vb->pending_frame_consumed);
|
||||
vb->pending_frame_consumed = true;
|
||||
|
||||
av_frame_move_ref(dst, vb->pending_frame);
|
||||
// av_frame_move_ref() resets its source frame, so no need to call
|
||||
// av_frame_unref()
|
||||
|
||||
sc_mutex_unlock(&vb->mutex);
|
||||
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) {
|
||||
sc_frame_buffer_consume(&vb->fb, dst);
|
||||
}
|
||||
|
@ -1,50 +1,30 @@
|
||||
#ifndef VIDEO_BUFFER_H
|
||||
#define VIDEO_BUFFER_H
|
||||
#ifndef SC_VIDEO_BUFFER_H
|
||||
#define SC_VIDEO_BUFFER_H
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "fps_counter.h"
|
||||
#include "util/thread.h"
|
||||
#include "frame_buffer.h"
|
||||
|
||||
// forward declarations
|
||||
typedef struct AVFrame AVFrame;
|
||||
|
||||
/**
|
||||
* A video buffer holds 1 pending frame, which is the last frame received from
|
||||
* the producer (typically, the decoder).
|
||||
*
|
||||
* If a pending frame has not been consumed when the producer pushes a new
|
||||
* frame, then it is lost. The intent is to always provide access to the very
|
||||
* last frame to minimize latency.
|
||||
*
|
||||
* The producer and the consumer typically do not live in the same thread.
|
||||
* That's the reason why the callback on_frame_available() does not provide the
|
||||
* frame as parameter: the consumer might post an event to its own thread to
|
||||
* retrieve the pending frame from there, and that frame may have changed since
|
||||
* the callback if producer pushed a new one in between.
|
||||
*/
|
||||
|
||||
struct video_buffer {
|
||||
AVFrame *pending_frame;
|
||||
AVFrame *tmp_frame; // To preserve the pending frame on error
|
||||
|
||||
sc_mutex mutex;
|
||||
|
||||
bool pending_frame_consumed;
|
||||
struct sc_video_buffer {
|
||||
struct sc_frame_buffer fb;
|
||||
};
|
||||
|
||||
bool
|
||||
video_buffer_init(struct video_buffer *vb);
|
||||
sc_video_buffer_init(struct sc_video_buffer *vb);
|
||||
|
||||
void
|
||||
video_buffer_destroy(struct video_buffer *vb);
|
||||
sc_video_buffer_destroy(struct sc_video_buffer *vb);
|
||||
|
||||
bool
|
||||
video_buffer_push(struct video_buffer *vb, const AVFrame *frame, bool *skipped);
|
||||
sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame,
|
||||
bool *skipped);
|
||||
|
||||
void
|
||||
video_buffer_consume(struct video_buffer *vb, AVFrame *dst);
|
||||
sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst);
|
||||
|
||||
#endif
|
||||
|
Reference in New Issue
Block a user