diff --git a/app/src/screen.c b/app/src/screen.c index a9ee1fb0..126caf9b 100644 --- a/app/src/screen.c +++ b/app/src/screen.c @@ -308,15 +308,21 @@ screen_init(struct screen *screen, const struct screen_params *params) { .on_new_frame = sc_video_buffer_on_new_frame, }; - bool ok = sc_video_buffer_init(&screen->vb, &cbs, screen); + bool ok = sc_video_buffer_init(&screen->vb, 0, &cbs, screen); if (!ok) { LOGE("Could not initialize video buffer"); return false; } + ok = sc_video_buffer_start(&screen->vb); + if (!ok) { + LOGE("Could not start video_buffer"); + goto error_destroy_video_buffer; + } + if (!fps_counter_init(&screen->fps_counter)) { LOGE("Could not initialize FPS counter"); - goto error_destroy_video_buffer; + goto error_stop_and_join_video_buffer; } screen->frame_size = params->frame_size; @@ -457,6 +463,9 @@ error_destroy_window: SDL_DestroyWindow(screen->window); error_destroy_fps_counter: fps_counter_destroy(&screen->fps_counter); +error_stop_and_join_video_buffer: + sc_video_buffer_stop(&screen->vb); + sc_video_buffer_join(&screen->vb); error_destroy_video_buffer: sc_video_buffer_destroy(&screen->vb); @@ -475,11 +484,13 @@ screen_hide_window(struct screen *screen) { void screen_interrupt(struct screen *screen) { + sc_video_buffer_stop(&screen->vb); fps_counter_interrupt(&screen->fps_counter); } void screen_join(struct screen *screen) { + sc_video_buffer_join(&screen->vb); fps_counter_join(&screen->fps_counter); } diff --git a/app/src/v4l2_sink.c b/app/src/v4l2_sink.c index 21bbe404..8f8b98ee 100644 --- a/app/src/v4l2_sink.c +++ b/app/src/v4l2_sink.c @@ -159,16 +159,22 @@ sc_v4l2_sink_open(struct sc_v4l2_sink *vs) { .on_new_frame = sc_video_buffer_on_new_frame, }; - bool ok = sc_video_buffer_init(&vs->vb, &cbs, vs); + bool ok = sc_video_buffer_init(&vs->vb, 0, &cbs, vs); if (!ok) { LOGE("Could not initialize video buffer"); return false; } + ok = sc_video_buffer_start(&vs->vb); + if (!ok) { + LOGE("Could not start video buffer"); + goto error_video_buffer_destroy; + } + ok = sc_mutex_init(&vs->mutex); if (!ok) { LOGC("Could not create mutex"); - goto error_video_buffer_destroy; + goto error_video_buffer_stop_and_join; } ok = sc_cond_init(&vs->cond); @@ -293,6 +299,9 @@ error_cond_destroy: sc_cond_destroy(&vs->cond); error_mutex_destroy: sc_mutex_destroy(&vs->mutex); +error_video_buffer_stop_and_join: + sc_video_buffer_stop(&vs->vb); + sc_video_buffer_join(&vs->vb); error_video_buffer_destroy: sc_video_buffer_destroy(&vs->vb); @@ -306,7 +315,10 @@ sc_v4l2_sink_close(struct sc_v4l2_sink *vs) { sc_cond_signal(&vs->cond); sc_mutex_unlock(&vs->mutex); + sc_video_buffer_stop(&vs->vb); + sc_thread_join(&vs->thread, NULL); + sc_video_buffer_join(&vs->vb); av_packet_free(&vs->packet); av_frame_free(&vs->frame); diff --git a/app/src/video_buffer.c b/app/src/video_buffer.c index 664eb6c1..2b527db8 100644 --- a/app/src/video_buffer.c +++ b/app/src/video_buffer.c @@ -6,30 +6,37 @@ #include "util/log.h" -bool -sc_video_buffer_init(struct sc_video_buffer *vb, - const struct sc_video_buffer_callbacks *cbs, - void *cbs_userdata) { - bool ok = sc_frame_buffer_init(&vb->fb); - if (!ok) { - return false; +static struct sc_video_buffer_frame * +sc_video_buffer_frame_new(const AVFrame *frame) { + struct sc_video_buffer_frame *vb_frame = malloc(sizeof(*vb_frame)); + if (!vb_frame) { + return NULL; } - assert(cbs); - assert(cbs->on_new_frame); + vb_frame->frame = av_frame_alloc(); + if (!vb_frame->frame) { + free(vb_frame); + return NULL; + } - vb->cbs = cbs; - vb->cbs_userdata = cbs_userdata; - return true; + if (av_frame_ref(vb_frame->frame, frame)) { + av_frame_free(&vb_frame->frame); + free(vb_frame); + return NULL; + } + + return vb_frame; } -void -sc_video_buffer_destroy(struct sc_video_buffer *vb) { - sc_frame_buffer_destroy(&vb->fb); +static void +sc_video_buffer_frame_delete(struct sc_video_buffer_frame *vb_frame) { + av_frame_unref(vb_frame->frame); + av_frame_free(&vb_frame->frame); + free(vb_frame); } -bool -sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { +static bool +sc_video_buffer_offer(struct sc_video_buffer *vb, const AVFrame *frame) { bool previous_skipped; bool ok = sc_frame_buffer_push(&vb->fb, frame, &previous_skipped); if (!ok) { @@ -40,6 +47,143 @@ sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { return true; } +static int +run_buffering(void *data) { + struct sc_video_buffer *vb = data; + + assert(vb->buffering_ms); + + for (;;) { + sc_mutex_lock(&vb->b.mutex); + + while (!vb->b.stopped && sc_queue_is_empty(&vb->b.queue)) { + sc_cond_wait(&vb->b.queue_cond, &vb->b.mutex); + } + + if (vb->b.stopped) { + // Flush queue + while (!sc_queue_is_empty(&vb->b.queue)) { + struct sc_video_buffer_frame *vb_frame; + sc_queue_take(&vb->b.queue, next, &vb_frame); + sc_video_buffer_frame_delete(vb_frame); + } + break; + } + + struct sc_video_buffer_frame *vb_frame; + sc_queue_take(&vb->b.queue, next, &vb_frame); + + sc_mutex_unlock(&vb->b.mutex); + + usleep(vb->buffering_ms * 1000); + + sc_video_buffer_offer(vb, vb_frame->frame); + + sc_video_buffer_frame_delete(vb_frame); + } + + LOGD("Buffering thread ended"); + + return 0; +} + +bool +sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms, + const struct sc_video_buffer_callbacks *cbs, + void *cbs_userdata) { + bool ok = sc_frame_buffer_init(&vb->fb); + if (!ok) { + return false; + } + + if (buffering_ms) { + ok = sc_mutex_init(&vb->b.mutex); + if (!ok) { + LOGC("Could not create mutex"); + sc_frame_buffer_destroy(&vb->fb); + return false; + } + + ok = sc_cond_init(&vb->b.queue_cond); + if (!ok) { + LOGC("Could not create cond"); + sc_mutex_destroy(&vb->b.mutex); + sc_frame_buffer_destroy(&vb->fb); + return false; + } + + sc_queue_init(&vb->b.queue); + } + + assert(cbs); + assert(cbs->on_new_frame); + + vb->buffering_ms = buffering_ms; + vb->cbs = cbs; + vb->cbs_userdata = cbs_userdata; + return true; +} + +bool +sc_video_buffer_start(struct sc_video_buffer *vb) { + if (vb->buffering_ms) { + bool ok = + sc_thread_create(&vb->b.thread, run_buffering, "buffering", vb); + if (!ok) { + LOGE("Could not start buffering thread"); + return false; + } + } + + return true; +} + +void +sc_video_buffer_stop(struct sc_video_buffer *vb) { + if (vb->buffering_ms) { + sc_mutex_lock(&vb->b.mutex); + vb->b.stopped = true; + sc_mutex_unlock(&vb->b.mutex); + } +} + +void +sc_video_buffer_join(struct sc_video_buffer *vb) { + if (vb->buffering_ms) { + sc_thread_join(&vb->b.thread, NULL); + } +} + +void +sc_video_buffer_destroy(struct sc_video_buffer *vb) { + sc_frame_buffer_destroy(&vb->fb); + if (vb->buffering_ms) { + sc_cond_destroy(&vb->b.queue_cond); + sc_mutex_destroy(&vb->b.mutex); + } +} + +bool +sc_video_buffer_push(struct sc_video_buffer *vb, const AVFrame *frame) { + if (!vb->buffering_ms) { + // no buffering + return sc_video_buffer_offer(vb, frame); + } + + struct sc_video_buffer_frame *vb_frame = sc_video_buffer_frame_new(frame); + if (!vb_frame) { + LOGE("Could not allocate frame"); + return false; + } + + sc_mutex_lock(&vb->b.mutex); + sc_queue_push(&vb->b.queue, next, vb_frame); + sc_cond_signal(&vb->b.queue_cond); + sc_mutex_unlock(&vb->b.mutex); + + return true; +} + void sc_video_buffer_consume(struct sc_video_buffer *vb, AVFrame *dst) { sc_frame_buffer_consume(&vb->fb, dst); diff --git a/app/src/video_buffer.h b/app/src/video_buffer.h index 6f258980..0633ff73 100644 --- a/app/src/video_buffer.h +++ b/app/src/video_buffer.h @@ -6,13 +6,33 @@ #include #include "frame_buffer.h" +#include "util/queue.h" +#include "util/thread.h" // forward declarations typedef struct AVFrame AVFrame; +struct sc_video_buffer_frame { + AVFrame *frame; + struct sc_video_buffer_frame *next; +}; + +struct sc_video_buffer_frame_queue SC_QUEUE(struct sc_video_buffer_frame); + struct sc_video_buffer { struct sc_frame_buffer fb; + unsigned buffering_ms; + + // only if buffering_ms > 0 + struct { + sc_thread thread; + sc_mutex mutex; + sc_cond queue_cond; + struct sc_video_buffer_frame_queue queue; + bool stopped; + } b; // buffering + const struct sc_video_buffer_callbacks *cbs; void *cbs_userdata; }; @@ -23,10 +43,19 @@ struct sc_video_buffer_callbacks { }; bool -sc_video_buffer_init(struct sc_video_buffer *vb, +sc_video_buffer_init(struct sc_video_buffer *vb, unsigned buffering_ms, const struct sc_video_buffer_callbacks *cbs, void *cbs_userdata); +bool +sc_video_buffer_start(struct sc_video_buffer *vb); + +void +sc_video_buffer_stop(struct sc_video_buffer *vb); + +void +sc_video_buffer_join(struct sc_video_buffer *vb); + void sc_video_buffer_destroy(struct sc_video_buffer *vb);