diff --git a/app/meson.build b/app/meson.build index 6c94f33e..7efd94a1 100644 --- a/app/meson.build +++ b/app/meson.build @@ -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', diff --git a/app/src/frame_buffer.c b/app/src/frame_buffer.c new file mode 100644 index 00000000..33ca6227 --- /dev/null +++ b/app/src/frame_buffer.c @@ -0,0 +1,88 @@ +#include "frame_buffer.h" + +#include +#include +#include + +#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); +} diff --git a/app/src/frame_buffer.h b/app/src/frame_buffer.h new file mode 100644 index 00000000..f97261cd --- /dev/null +++ b/app/src/frame_buffer.h @@ -0,0 +1,44 @@ +#ifndef SC_FRAME_BUFFER_H +#define SC_FRAME_BUFFER_H + +#include "common.h" + +#include + +#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 diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index a1d09cb8..9a5fed43 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -8,81 +8,21 @@ bool sc_video_buffer_init(struct sc_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; + return sc_frame_buffer_init(&vb->fb); } void sc_video_buffer_destroy(struct sc_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_frame_buffer_destroy(&vb->fb); } bool 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 sc_video_buffer_consume(struct sc_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_frame_buffer_consume(&vb->fb, dst); } diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index f4364e08..9befd26d 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -5,27 +5,13 @@ #include -#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. - */ - struct sc_video_buffer { - AVFrame *pending_frame; - AVFrame *tmp_frame; // To preserve the pending frame on error - - sc_mutex mutex; - - bool pending_frame_consumed; + struct sc_frame_buffer fb; }; bool