From 83229dcdfe102d480907ddbd6485ef05e947a72f Mon Sep 17 00:00:00 2001 From: "Kwabena W. Agyeman" Date: Mon, 4 Dec 2023 21:33:22 -0500 Subject: [PATCH] modules/py_mjpeg: Calculate MJPEG FPS internally. --- .../01-Camera/01-Video-Recording/mjpeg.py | 4 +- .../mjpeg_on_face_detection.py | 4 +- .../01-Video-Recording/mjpeg_on_movement.py | 4 +- src/omv/imlib/imlib.h | 4 +- src/omv/imlib/mjpeg.c | 58 ++++++++++--------- src/omv/modules/py_mjpeg.c | 33 ++++++++--- 6 files changed, 63 insertions(+), 44 deletions(-) diff --git a/scripts/examples/01-Camera/01-Video-Recording/mjpeg.py b/scripts/examples/01-Camera/01-Video-Recording/mjpeg.py index 3d033fabe..bb6a83717 100644 --- a/scripts/examples/01-Camera/01-Video-Recording/mjpeg.py +++ b/scripts/examples/01-Camera/01-Video-Recording/mjpeg.py @@ -29,10 +29,10 @@ m = mjpeg.Mjpeg("example.mjpeg") clock = time.clock() # Create a clock object to track the FPS. for i in range(200): clock.tick() - m.add_frame(sensor.snapshot()) + m.write(sensor.snapshot()) print(clock.fps()) -m.close(clock.fps()) +m.close() led.off() raise (Exception("Please reset the camera to see the new file.")) diff --git a/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_face_detection.py b/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_face_detection.py index 313a97667..a3b16deea 100644 --- a/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_face_detection.py +++ b/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_face_detection.py @@ -60,9 +60,9 @@ while True: clock = time.clock() # Tracks FPS. for i in range(200): clock.tick() - m.add_frame(sensor.snapshot()) + m.write(sensor.snapshot()) print(clock.fps()) - m.close(clock.fps()) + m.close() led.off() print("Restarting...") diff --git a/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_movement.py b/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_movement.py index a3940808d..77dc76cd5 100644 --- a/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_movement.py +++ b/scripts/examples/01-Camera/01-Video-Recording/mjpeg_on_movement.py @@ -56,9 +56,9 @@ while True: clock = time.clock() # Tracks FPS. for i in range(200): clock.tick() - m.add_frame(sensor.snapshot()) + m.write(sensor.snapshot()) print(clock.fps()) - m.close(clock.fps()) + m.close() led.off() print("Restarting...") diff --git a/src/omv/imlib/imlib.h b/src/omv/imlib/imlib.h index e8353f98a..b224dc4a0 100644 --- a/src/omv/imlib/imlib.h +++ b/src/omv/imlib/imlib.h @@ -1182,8 +1182,8 @@ void mjpeg_open(FIL *fp, int width, int height); void mjpeg_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *bytes, image_t *img, int quality, rectangle_t *roi, int rgb_channel, int alpha, const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint); -void mjpeg_sync(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); -void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); +void mjpeg_sync(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg); +void mjpeg_close(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg); /* Point functions */ point_t *point_alloc(int16_t x, int16_t y); diff --git a/src/omv/imlib/mjpeg.c b/src/omv/imlib/mjpeg.c index 7dafafcba..23cdcdcb3 100644 --- a/src/omv/imlib/mjpeg.c +++ b/src/omv/imlib/mjpeg.c @@ -13,14 +13,16 @@ #include "file_utils.h" -#define SIZE_OFFSET (1 * 4) -#define MICROS_OFFSET (8 * 4) -#define FRAMES_OFFSET (12 * 4) -#define RATE_0_OFFSET (19 * 4) -#define LENGTH_0_OFFSET (21 * 4) -#define RATE_1_OFFSET (33 * 4) -#define LENGTH_1_OFFSET (35 * 4) -#define MOVI_OFFSET (54 * 4) +#define SIZE_OFFSET (1 * 4) +#define MICROS_OFFSET (8 * 4) +#define FRAMES_OFFSET (12 * 4) +#define RATE_0_OFFSET (19 * 4) +#define LENGTH_0_OFFSET (21 * 4) +#define RATE_1_OFFSET (33 * 4) +#define LENGTH_1_OFFSET (35 * 4) +#define MOVI_OFFSET (54 * 4) + +#define TIME_SCALE (1000) void mjpeg_open(FIL *fp, int width, int height) { file_write(fp, "RIFF", 4); // FOURCC fcc; - 0 @@ -43,7 +45,7 @@ void mjpeg_open(FIL *fp, int width, int height) { file_write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 15 file_write_long(fp, width); // DWORD dwWidth; - 16 file_write_long(fp, height); // DWORD dwHeight; - 17 - file_write_long(fp, 1000); // DWORD dwScale; - 18 + file_write_long(fp, TIME_SCALE); // DWORD dwScale; - 18 file_write_long(fp, 0); // DWORD dwRate; rate - updated on close - 19 file_write_long(fp, 0); // DWORD dwStart; - 20 file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 21 @@ -60,7 +62,7 @@ void mjpeg_open(FIL *fp, int width, int height) { file_write_short(fp, 0); // WORD wPriority; - 30 file_write_short(fp, 0); // WORD wLanguage; - 30.5 file_write_long(fp, 0); // DWORD dwInitialFrames; - 31 - file_write_long(fp, 1000); // DWORD dwScale; - 32 + file_write_long(fp, TIME_SCALE); // DWORD dwScale; - 32 file_write_long(fp, 0); // DWORD dwRate; rate - updated on close - 33 file_write_long(fp, 0); // DWORD dwStart; - 34 file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 35 @@ -96,7 +98,7 @@ void mjpeg_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *byt const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) { float xscale = width / ((float) roi->w); float yscale = height / ((float) roi->h); - // MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio + // MAX == KeepAspectRatioByExpanding - MIN == KeepAspectRatio float scale = IM_MIN(xscale, yscale); image_t dst_img = { @@ -196,43 +198,45 @@ void mjpeg_write(FIL *fp, int width, int height, uint32_t *frames, uint32_t *byt fb_alloc_free_till_mark(); } -void mjpeg_sync(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) { +void mjpeg_sync(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg) { uint32_t position = f_tell(fp); + // size of all mjpeg headers and jpegs. + uint32_t datasize = (frames * 8) + bytes; + // frames_per_second == rate / scale + uint32_t rate = IM_DIV((1000000 * TIME_SCALE), us_avg); + // video length == frames / frames_per_second + uint32_t length = IM_DIV((((uint64_t) frames) * TIME_SCALE), rate); // Needed file_seek(fp, SIZE_OFFSET); - file_write_long(fp, 216 + (*frames * 8) + *bytes); + file_write_long(fp, 216 + datasize); // Needed file_seek(fp, MICROS_OFFSET); - file_write_long(fp, (!fast_roundf(fps)) ? 0 : - fast_roundf(1000000 / fps)); - file_write_long(fp, (!(*frames)) ? 0 : - fast_roundf((((*frames * 8) + *bytes) * fps) / *frames)); + file_write_long(fp, us_avg); + file_write_long(fp, IM_DIV((((uint64_t) datasize) * us_avg), frames)); // Needed file_seek(fp, FRAMES_OFFSET); - file_write_long(fp, *frames); + file_write_long(fp, frames); // Probably not needed but writing it just in case. file_seek(fp, RATE_0_OFFSET); - file_write_long(fp, fast_roundf(fps * 1000)); + file_write_long(fp, rate); // Probably not needed but writing it just in case. file_seek(fp, LENGTH_0_OFFSET); - file_write_long(fp, (!fast_roundf(fps)) ? 0 : - fast_roundf((*frames * 1000) / fps)); + file_write_long(fp, length); // Probably not needed but writing it just in case. file_seek(fp, RATE_1_OFFSET); - file_write_long(fp, fast_roundf(fps * 1000)); + file_write_long(fp, rate); // Probably not needed but writing it just in case. file_seek(fp, LENGTH_1_OFFSET); - file_write_long(fp, (!fast_roundf(fps)) ? 0 : - fast_roundf((*frames * 1000) / fps)); + file_write_long(fp, length); // Needed file_seek(fp, MOVI_OFFSET); - file_write_long(fp, 4 + (*frames * 8) + *bytes); + file_write_long(fp, 4 + datasize); file_sync(fp); file_seek(fp, position); } -void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) { - mjpeg_sync(fp, frames, bytes, fps); +void mjpeg_close(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg) { + mjpeg_sync(fp, frames, bytes, us_avg); file_close(fp); } diff --git a/src/omv/modules/py_mjpeg.c b/src/omv/modules/py_mjpeg.c index 6915f6db6..80376f3e7 100644 --- a/src/omv/modules/py_mjpeg.c +++ b/src/omv/modules/py_mjpeg.c @@ -30,6 +30,8 @@ typedef struct py_mjpeg_obj { mp_obj_base_t base; uint32_t frames; uint32_t bytes; + uint32_t us_old; + uint32_t us_avg; uint32_t width; uint32_t height; bool closed; @@ -120,29 +122,44 @@ STATIC mp_obj_t py_mjpeg_write(uint n_args, const mp_obj_t *pos_args, mp_map_t * image, args[ARG_quality].u_int, &roi, args[ARG_channel].u_int, args[ARG_alpha].u_int, color_palette, alpha_palette, args[ARG_hint].u_int); + uint32_t ticks = mp_hal_ticks_us(); + + if (self->frames > 1) { + uint32_t ticks_diff = mp_hal_ticks_us() - self->us_old; + + if (self->frames <= 2) { + self->us_avg = ticks_diff; + } else { + uint64_t cumulative_average_n = ((uint64_t) self->us_avg) * (self->frames - 1); + self->us_avg = (cumulative_average_n + ticks_diff) / self->frames; + } + } + + self->us_old = ticks; + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_write_obj, 2, py_mjpeg_write); -STATIC mp_obj_t py_mjpeg_sync(mp_obj_t self_in, mp_obj_t fps_obj) { +STATIC mp_obj_t py_mjpeg_sync(mp_obj_t self_in) { py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->closed) { mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("MJPEG stream is closed")); } - mjpeg_sync(&self->fp, &self->frames, &self->bytes, mp_obj_get_float(fps_obj)); + mjpeg_sync(&self->fp, self->frames, self->bytes, self->us_avg); return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_sync_obj, py_mjpeg_sync); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_sync_obj, py_mjpeg_sync); -STATIC mp_obj_t py_mjpeg_close(mp_obj_t self_in, mp_obj_t fps_obj) { +STATIC mp_obj_t py_mjpeg_close(mp_obj_t self_in) { py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in); if (!self->closed) { - mjpeg_close(&self->fp, &self->frames, &self->bytes, mp_obj_get_float(fps_obj)); + mjpeg_close(&self->fp, self->frames, self->bytes, self->us_avg); } self->closed = true; return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_close_obj, py_mjpeg_close); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_close_obj, py_mjpeg_close); STATIC mp_obj_t py_mjpeg_open(uint n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_width, ARG_height }; @@ -157,12 +174,10 @@ STATIC mp_obj_t py_mjpeg_open(uint n_args, const mp_obj_t *pos_args, mp_map_t *k mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); py_mjpeg_obj_t *mjpeg = m_new_obj_with_finaliser(py_mjpeg_obj_t); + memset(mjpeg, 0, sizeof(py_mjpeg_obj_t)); mjpeg->base.type = &py_mjpeg_type; - mjpeg->frames = 0; - mjpeg->bytes = 0; mjpeg->width = (args[ARG_width].u_int == -1) ? framebuffer_get_width() : args[ARG_width].u_int; mjpeg->height = (args[ARG_height].u_int == -1) ? framebuffer_get_height() : args[ARG_height].u_int; - mjpeg->closed = false; file_open(&mjpeg->fp, path, false, FA_WRITE | FA_CREATE_ALWAYS); mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height);