diff --git a/src/Makefile b/src/Makefile index d743039ae..ac4292d16 100755 --- a/src/Makefile +++ b/src/Makefile @@ -140,10 +140,11 @@ OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/img/,\ rgb2rgb_tab.o \ median.o \ point.o \ + rectangle.o \ bmp.o \ ppm.o \ gif.o \ - rectangle.o \ + mjpeg.o \ fast.o \ freak.o \ template.o \ @@ -161,6 +162,7 @@ OBJ += $(addprefix $(BUILD)/$(OMV_DIR)/py/, \ py_time.o \ mlx90620.o \ py_gif.o \ + py_mjpeg.o \ ) ifeq ($(TARGET), OPENMV1) diff --git a/src/omv/Makefile b/src/omv/Makefile index a78f388db..209ed65c3 100644 --- a/src/omv/Makefile +++ b/src/omv/Makefile @@ -43,10 +43,11 @@ SRCS += $(addprefix img/, \ rgb2rgb_tab.c \ median.c \ point.c \ + rectangle.c \ bmp.c \ ppm.c \ gif.c \ - rectangle.c \ + mjpeg.c \ fast.c \ freak.c \ template.c \ @@ -64,6 +65,7 @@ SRCS += $(addprefix py/, \ py_time.c \ mlx90620.c \ py_gif.c \ + py_mjpeg.c \ ) OBJS = $(addprefix $(BUILD)/, $(SRCS:.c=.o)) diff --git a/src/omv/fb_alloc.c b/src/omv/fb_alloc.c index ff657dc5a..eef5b1a7b 100644 --- a/src/omv/fb_alloc.c +++ b/src/omv/fb_alloc.c @@ -54,7 +54,7 @@ void *fb_alloc0(uint32_t size) void *fb_alloc_all(uint32_t *size) { - int temp = pointer - ((char *) FB_PIXELS()) - sizeof(uint32_t); + int32_t temp = pointer - ((char *) FB_PIXELS()) - sizeof(uint32_t); if (temp < sizeof(uint32_t)) { *size = 0; diff --git a/src/omv/img/imlib.h b/src/omv/img/imlib.h index f9204fa37..78db0a158 100644 --- a/src/omv/img/imlib.h +++ b/src/omv/img/imlib.h @@ -173,6 +173,8 @@ extern const int8_t lab_table[196608]; __typeof__ (img1) _img1 = (img1); \ (_img0->w==_img1->w)&&(_img0->h==_img1->h)&&(_img0->bpp==_img1->bpp); }) +#define IM_JPEG_QUALITY (50) + // Main Ram = 196,608 bytes // - // struct framebuffer = 20 bytes @@ -434,6 +436,11 @@ void gif_open(FIL *fp, int width, int height, bool color, bool loop); void gif_add_frame(FIL *fp, image_t *img, uint16_t delay); void gif_close(FIL *fp); +/* MJPEG functions */ +void mjpeg_open(FIL *fp, int width, int height); +void mjpeg_add_frame(FIL *fp, uint32_t *frames, uint32_t *bytes, image_t *img); +void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps); + /* Basic image functions */ int imlib_get_pixel(image_t *img, int x, int y); void imlib_set_pixel(image_t *img, int x, int y, int p); diff --git a/src/omv/img/mjpeg.c b/src/omv/img/mjpeg.c new file mode 100644 index 000000000..60bb64b50 --- /dev/null +++ b/src/omv/img/mjpeg.c @@ -0,0 +1,149 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * A super simple MJPEG encoder. + * + */ +#include "ff_wrapper.h" +#include "imlib.h" +#include "fb_alloc.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) + +void mjpeg_open(FIL *fp, int width, int height) +{ + write_data(fp, "RIFF", 4); // FOURCC fcc; - 0 + write_long(fp, 0); // DWORD cb; size - updated on close - 1 + write_data(fp, "AVI ", 4); // FOURCC fcc; - 2 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 3 + write_long(fp, 192); // DWORD cb; - 4 + write_data(fp, "hdrl", 4); // FOURCC fcc; - 5 + + write_data(fp, "avih", 4); // FOURCC fcc; - 6 + write_long(fp, 56); // DWORD cb; - 7 + write_long(fp, 0); // DWORD dwMicroSecPerFrame; micros - updated on close - 8 + write_long(fp, 0); // DWORD dwMaxBytesPerSec; updated on close - 9 + write_long(fp, 4); // DWORD dwPaddingGranularity; - 10 + write_long(fp, 0); // DWORD dwFlags; - 11 + write_long(fp, 0); // DWORD dwTotalFrames; frames - updated on close - 12 + write_long(fp, 0); // DWORD dwInitialFrames; - 13 + write_long(fp, 1); // DWORD dwStreams; - 14 + write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 15 + write_long(fp, width); // DWORD dwWidth; - 16 + write_long(fp, height); // DWORD dwHeight; - 17 + write_long(fp, 1000); // DWORD dwScale; - 18 + write_long(fp, 0); // DWORD dwRate; rate - updated on close - 19 + write_long(fp, 0); // DWORD dwStart; - 20 + write_long(fp, 0); // DWORD dwLength; length - updated on close - 21 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 22 + write_long(fp, 116); // DWORD cb; - 23 + write_data(fp, "strl", 4); // FOURCC fcc; - 24 + + write_data(fp, "strh", 4); // FOURCC fcc; - 25 + write_long(fp, 56); // DWORD cb; - 26 + write_data(fp, "vids", 4); // FOURCC fccType; - 27 + write_data(fp, "MJPG", 4); // FOURCC fccHandler; - 28 + write_long(fp, 0); // DWORD dwFlags; - 29 + write_word(fp, 0); // WORD wPriority; - 30 + write_word(fp, 0); // WORD wLanguage; - 30.5 + write_long(fp, 0); // DWORD dwInitialFrames; - 31 + write_long(fp, 1000); // DWORD dwScale; - 32 + write_long(fp, 0); // DWORD dwRate; rate - updated on close - 33 + write_long(fp, 0); // DWORD dwStart; - 34 + write_long(fp, 0); // DWORD dwLength; length - updated on close - 35 + write_long(fp, 0); // DWORD dwSuggestedBufferSize; - 36 + write_long(fp, IM_JPEG_QUALITY * 100); // DWORD dwQuality; - 37 + write_long(fp, 0); // DWORD dwSampleSize; - 38 + write_word(fp, 0); // short int left; - 39 + write_word(fp, 0); // short int top; - 39.5 + write_word(fp, 0); // short int right; - 40 + write_word(fp, 0); // short int bottom; - 40.5 + + write_data(fp, "strf", 4); // FOURCC fcc; - 41 + write_long(fp, 40); // DWORD cb; - 42 + write_long(fp, 40); // DWORD biSize; - 43 + write_long(fp, width); // LONG biWidth; - 44 + write_long(fp, height); // LONG biHeight; - 45 + write_word(fp, 1); // WORD biPlanes; - 46 + write_word(fp, 24); // WORD biBitCount; - 46.5 + write_data(fp, "MJPG", 4); // DWORD biCompression; - 47 + write_long(fp, 0); // DWORD biSizeImage; - 48 + write_long(fp, 0); // LONG biXPelsPerMeter; - 49 + write_long(fp, 0); // LONG biYPelsPerMeter; - 50 + write_long(fp, 0); // DWORD biClrUsed; - 51 + write_long(fp, 0); // DWORD biClrImportant; - 52 + + write_data(fp, "LIST", 4); // FOURCC fcc; - 53 + write_long(fp, 0); // DWORD cb; movi - updated on close - 54 + write_data(fp, "movi", 4); // FOURCC fcc; - 55 +} + +void mjpeg_add_frame(FIL *fp, uint32_t *frames, uint32_t *bytes, image_t *img) +{ + write_data(fp, "00dc", 4); // FOURCC fcc; + *frames += 1; + if (IM_IS_JPEG(img)) { + int pad = (((img->bpp + 3) / 4) * 4) - img->bpp; + write_long(fp, img->bpp + pad); // DWORD cb; + write_data(fp, img->pixels, img->bpp + pad); // reading past okay + *bytes += img->bpp + pad; + } else { + uint32_t size; + uint8_t *buffer = fb_alloc_all(&size); + image_t out = { .w=img->w, .h=img->h, .bpp=size, .pixels=buffer }; + // When jpeg_compress needs more memory than in currently allocated it + // will try to realloc. MP will detect that the pointer is outside of + // the heap and return NULL which will cause an out of memory error. + jpeg_compress(img, &out, IM_JPEG_QUALITY); + int pad = (((out.bpp + 3) / 4) * 4) - out.bpp; + write_long(fp, out.bpp + pad); // DWORD cb; + write_data(fp, out.pixels, out.bpp + pad); // reading past okay + *bytes += out.bpp + pad; + fb_free(); + } +} + +void mjpeg_close(FIL *fp, uint32_t *frames, uint32_t *bytes, float fps) +{ + // Needed + file_seek(fp, SIZE_OFFSET); + write_long(fp, 216 + (*frames * 8) + *bytes); + // Needed + file_seek(fp, MICROS_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf(1000000 / fps)); + write_long(fp, (!(*frames)) ? 0 : + fast_roundf((((*frames * 8) + *bytes) * fps) / *frames)); + // Needed + file_seek(fp, FRAMES_OFFSET); + write_long(fp, *frames); + // Probably not needed but writing it just in case. + file_seek(fp, RATE_0_OFFSET); + write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_0_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf((*frames * 1000) / fps)); + // Probably not needed but writing it just in case. + file_seek(fp, RATE_1_OFFSET); + write_long(fp, fast_roundf(fps * 1000)); + // Probably not needed but writing it just in case. + file_seek(fp, LENGTH_1_OFFSET); + write_long(fp, (!fast_roundf(fps)) ? 0 : + fast_roundf((*frames * 1000) / fps)); + // Needed + file_seek(fp, MOVI_OFFSET); + write_long(fp, 4 + (*frames * 8) + *bytes); + file_close(fp); +} diff --git a/src/omv/py/py_mjpeg.c b/src/omv/py/py_mjpeg.c new file mode 100644 index 000000000..94a16136e --- /dev/null +++ b/src/omv/py/py_mjpeg.c @@ -0,0 +1,119 @@ +/* + * This file is part of the OpenMV project. + * Copyright (c) 2013/2014 Ibrahim Abdelkader + * This work is licensed under the MIT license, see the file LICENSE for details. + * + * MJPEG Python module. + * + */ +#include "mp.h" +#include "ff_wrapper.h" +#include "framebuffer.h" +#include "sensor.h" +#include "py_assert.h" +#include "py_helper.h" +#include "py_image.h" + +static const mp_obj_type_t py_mjpeg_type; // forward declare +// Mjpeg class +typedef struct py_mjpeg_obj { + mp_obj_base_t base; + int width; + int height; + uint32_t frames; + uint32_t bytes; + FIL fp; +} py_mjpeg_obj_t; + +static mp_obj_t py_mjpeg_open(uint n_args, const mp_obj_t *args, mp_map_t *kw_args) +{ + py_mjpeg_obj_t *mjpeg = m_new_obj(py_mjpeg_obj_t); + mjpeg->width = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_width), fb->w); + mjpeg->height = py_helper_lookup_int(kw_args, MP_OBJ_NEW_QSTR(MP_QSTR_height), fb->h); + mjpeg->frames = 0; // private + mjpeg->bytes = 0; // private + mjpeg->base.type = &py_mjpeg_type; + + file_write_open(&mjpeg->fp, mp_obj_str_get_str(args[0])); + mjpeg_open(&mjpeg->fp, mjpeg->width, mjpeg->height); + return mjpeg; +} + +static mp_obj_t py_mjpeg_width(mp_obj_t mjpeg_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(arg_mjpeg->width); +} + +static mp_obj_t py_mjpeg_height(mp_obj_t mjpeg_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(arg_mjpeg->height); +} + +static mp_obj_t py_mjpeg_size(mp_obj_t mjpeg_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + return mp_obj_new_int(f_size(&arg_mjpeg->fp)); +} + +static mp_obj_t py_mjpeg_add_frame(mp_obj_t mjpeg_obj, mp_obj_t img_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + image_t *arg_img = py_image_cobj(img_obj); + PY_ASSERT_FALSE_MSG((arg_mjpeg->width != arg_img->w) + || (arg_mjpeg->height != arg_img->h), + "Unexpected image geometry"); + + mjpeg_add_frame(&arg_mjpeg->fp, &arg_mjpeg->frames, &arg_mjpeg->bytes, arg_img); + return mp_const_none; +} + +static mp_obj_t py_mjpeg_close(mp_obj_t mjpeg_obj, mp_obj_t fps_obj) +{ + py_mjpeg_obj_t *arg_mjpeg = mjpeg_obj; + mjpeg_close(&arg_mjpeg->fp, &arg_mjpeg->frames, &arg_mjpeg->bytes, mp_obj_get_float(fps_obj)); + return mp_const_none; +} + +static void py_mjpeg_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) +{ + py_mjpeg_obj_t *self = self_in; + mp_printf(print, "", self->width, self->height); +} + +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_width_obj, py_mjpeg_width); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_height_obj, py_mjpeg_height); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(py_mjpeg_size_obj, py_mjpeg_size); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_add_frame_obj, py_mjpeg_add_frame); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(py_mjpeg_close_obj, py_mjpeg_close); +static const mp_map_elem_t locals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR_width), (mp_obj_t)&py_mjpeg_width_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_height), (mp_obj_t)&py_mjpeg_height_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_size), (mp_obj_t)&py_mjpeg_size_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_add_frame), (mp_obj_t)&py_mjpeg_add_frame_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_close), (mp_obj_t)&py_mjpeg_close_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(locals_dict, locals_dict_table); + +static const mp_obj_type_t py_mjpeg_type = { + { &mp_type_type }, + .name = MP_QSTR_Mjpeg, + .print = py_mjpeg_print, + .locals_dict = (mp_obj_t)&locals_dict, +}; + +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(py_mjpeg_open_obj, 1, py_mjpeg_open); +static const mp_map_elem_t globals_dict_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_mjpeg) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_Mjpeg), (mp_obj_t)&py_mjpeg_open_obj }, + { NULL, NULL }, +}; +STATIC MP_DEFINE_CONST_DICT(globals_dict, globals_dict_table); + +const mp_obj_module_t mjpeg_module = { + .base = { &mp_type_module }, + .name = MP_QSTR_mjpeg, + .globals = (mp_obj_t)&globals_dict, +}; diff --git a/usr/examples/mjpeg.py b/usr/examples/mjpeg.py new file mode 100644 index 000000000..d3a75eab2 --- /dev/null +++ b/usr/examples/mjpeg.py @@ -0,0 +1,31 @@ +# Mjpeg recording example: +# +# You can use your OpenMV Cam to record mjpeg files. You can either feed the +# recorder object JPEG frames or RGB565/Grayscale frames. Once you've finished +# recording an Mjpeg file you can use VLC to play it. If you're on Ubuntu then +# the built-in video player will work too. + +import sensor, image, time, mjpeg + +sensor.reset() +sensor.set_framesize(sensor.QVGA) +sensor.set_pixformat(sensor.RGB565) # you can also use grayscale + +# Warm up the cam +for i in range(10): + sensor.snapshot() + +# FPS clock +clock = time.clock() +mjpeg = mjpeg.Mjpeg("/test.mjpeg") # video setup to use current resolution + +for i in range(300): + clock.tick() + img = sensor.snapshot() + mjpeg.add_frame(img) + # Print FPS. + # Note: Actual FPS is higher, the IDE slows down streaming. + print(clock.fps()) + +mjpeg.close(clock.fps()) +print("done")