modules/py_mjpeg: Calculate MJPEG FPS internally.

This commit is contained in:
Kwabena W. Agyeman 2023-12-04 21:33:22 -05:00
parent 96ffa1ffeb
commit 83229dcdfe
6 changed files with 63 additions and 44 deletions

View File

@ -29,10 +29,10 @@ m = mjpeg.Mjpeg("example.mjpeg")
clock = time.clock() # Create a clock object to track the FPS. clock = time.clock() # Create a clock object to track the FPS.
for i in range(200): for i in range(200):
clock.tick() clock.tick()
m.add_frame(sensor.snapshot()) m.write(sensor.snapshot())
print(clock.fps()) print(clock.fps())
m.close(clock.fps()) m.close()
led.off() led.off()
raise (Exception("Please reset the camera to see the new file.")) raise (Exception("Please reset the camera to see the new file."))

View File

@ -60,9 +60,9 @@ while True:
clock = time.clock() # Tracks FPS. clock = time.clock() # Tracks FPS.
for i in range(200): for i in range(200):
clock.tick() clock.tick()
m.add_frame(sensor.snapshot()) m.write(sensor.snapshot())
print(clock.fps()) print(clock.fps())
m.close(clock.fps()) m.close()
led.off() led.off()
print("Restarting...") print("Restarting...")

View File

@ -56,9 +56,9 @@ while True:
clock = time.clock() # Tracks FPS. clock = time.clock() # Tracks FPS.
for i in range(200): for i in range(200):
clock.tick() clock.tick()
m.add_frame(sensor.snapshot()) m.write(sensor.snapshot())
print(clock.fps()) print(clock.fps())
m.close(clock.fps()) m.close()
led.off() led.off()
print("Restarting...") print("Restarting...")

View File

@ -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, 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, 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); 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_sync(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg);
void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); void mjpeg_close(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg);
/* Point functions */ /* Point functions */
point_t *point_alloc(int16_t x, int16_t y); point_t *point_alloc(int16_t x, int16_t y);

View File

@ -22,6 +22,8 @@
#define LENGTH_1_OFFSET (35 * 4) #define LENGTH_1_OFFSET (35 * 4)
#define MOVI_OFFSET (54 * 4) #define MOVI_OFFSET (54 * 4)
#define TIME_SCALE (1000)
void mjpeg_open(FIL *fp, int width, int height) { void mjpeg_open(FIL *fp, int width, int height) {
file_write(fp, "RIFF", 4); // FOURCC fcc; - 0 file_write(fp, "RIFF", 4); // FOURCC fcc; - 0
file_write_long(fp, 0); // DWORD cb; size - updated on close - 1 file_write_long(fp, 0); // DWORD cb; size - updated on close - 1
@ -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, 0); // DWORD dwSuggestedBufferSize; - 15
file_write_long(fp, width); // DWORD dwWidth; - 16 file_write_long(fp, width); // DWORD dwWidth; - 16
file_write_long(fp, height); // DWORD dwHeight; - 17 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 dwRate; rate - updated on close - 19
file_write_long(fp, 0); // DWORD dwStart; - 20 file_write_long(fp, 0); // DWORD dwStart; - 20
file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 21 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 wPriority; - 30
file_write_short(fp, 0); // WORD wLanguage; - 30.5 file_write_short(fp, 0); // WORD wLanguage; - 30.5
file_write_long(fp, 0); // DWORD dwInitialFrames; - 31 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 dwRate; rate - updated on close - 33
file_write_long(fp, 0); // DWORD dwStart; - 34 file_write_long(fp, 0); // DWORD dwStart; - 34
file_write_long(fp, 0); // DWORD dwLength; length - updated on close - 35 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) { const uint16_t *color_palette, const uint8_t *alpha_palette, image_hint_t hint) {
float xscale = width / ((float) roi->w); float xscale = width / ((float) roi->w);
float yscale = height / ((float) roi->h); float yscale = height / ((float) roi->h);
// MAX == KeepAspectRationByExpanding - MIN == KeepAspectRatio // MAX == KeepAspectRatioByExpanding - MIN == KeepAspectRatio
float scale = IM_MIN(xscale, yscale); float scale = IM_MIN(xscale, yscale);
image_t dst_img = { 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(); 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); 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 // Needed
file_seek(fp, SIZE_OFFSET); file_seek(fp, SIZE_OFFSET);
file_write_long(fp, 216 + (*frames * 8) + *bytes); file_write_long(fp, 216 + datasize);
// Needed // Needed
file_seek(fp, MICROS_OFFSET); file_seek(fp, MICROS_OFFSET);
file_write_long(fp, (!fast_roundf(fps)) ? 0 : file_write_long(fp, us_avg);
fast_roundf(1000000 / fps)); file_write_long(fp, IM_DIV((((uint64_t) datasize) * us_avg), frames));
file_write_long(fp, (!(*frames)) ? 0 :
fast_roundf((((*frames * 8) + *bytes) * fps) / *frames));
// Needed // Needed
file_seek(fp, FRAMES_OFFSET); file_seek(fp, FRAMES_OFFSET);
file_write_long(fp, *frames); file_write_long(fp, frames);
// Probably not needed but writing it just in case. // Probably not needed but writing it just in case.
file_seek(fp, RATE_0_OFFSET); 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. // Probably not needed but writing it just in case.
file_seek(fp, LENGTH_0_OFFSET); file_seek(fp, LENGTH_0_OFFSET);
file_write_long(fp, (!fast_roundf(fps)) ? 0 : file_write_long(fp, length);
fast_roundf((*frames * 1000) / fps));
// Probably not needed but writing it just in case. // Probably not needed but writing it just in case.
file_seek(fp, RATE_1_OFFSET); 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. // Probably not needed but writing it just in case.
file_seek(fp, LENGTH_1_OFFSET); file_seek(fp, LENGTH_1_OFFSET);
file_write_long(fp, (!fast_roundf(fps)) ? 0 : file_write_long(fp, length);
fast_roundf((*frames * 1000) / fps));
// Needed // Needed
file_seek(fp, MOVI_OFFSET); file_seek(fp, MOVI_OFFSET);
file_write_long(fp, 4 + (*frames * 8) + *bytes); file_write_long(fp, 4 + datasize);
file_sync(fp); file_sync(fp);
file_seek(fp, position); file_seek(fp, position);
} }
void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) { void mjpeg_close(FIL *fp, uint32_t frames, uint32_t bytes, uint32_t us_avg) {
mjpeg_sync(fp, frames, bytes, fps); mjpeg_sync(fp, frames, bytes, us_avg);
file_close(fp); file_close(fp);
} }

View File

@ -30,6 +30,8 @@ typedef struct py_mjpeg_obj {
mp_obj_base_t base; mp_obj_base_t base;
uint32_t frames; uint32_t frames;
uint32_t bytes; uint32_t bytes;
uint32_t us_old;
uint32_t us_avg;
uint32_t width; uint32_t width;
uint32_t height; uint32_t height;
bool closed; 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, 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); 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; return mp_const_none;
} }
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_write_obj, 2, py_mjpeg_write); 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); py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->closed) { if (self->closed) {
mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("MJPEG stream is 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; 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); py_mjpeg_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (!self->closed) { 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; self->closed = true;
return mp_const_none; 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) { 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 }; 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); 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); 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->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->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->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); file_open(&mjpeg->fp, path, false, FA_WRITE | FA_CREATE_ALWAYS);
mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height); mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height);