Added a new mjpeg module for Mjpeg support.

The built-in mjpeg module allows you to record videos seamlessly. It
will automatically compress the frame buffer using the extra space in the
main ram. So... you don't have to pass it jpeg images. Gets 7 FPS at
320x240 while connected to the computer too (it has to compress the
frame twice in this situation).

Anyway, the module work like Gif.
This commit is contained in:
Kwabena W. Agyeman 2016-03-05 20:02:01 -05:00
parent acc35704d8
commit 5316b26d83
7 changed files with 313 additions and 3 deletions

View File

@ -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)

View File

@ -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))

View File

@ -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;

View File

@ -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);

149
src/omv/img/mjpeg.c Normal file
View File

@ -0,0 +1,149 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* 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);
}

119
src/omv/py/py_mjpeg.c Normal file
View File

@ -0,0 +1,119 @@
/*
* This file is part of the OpenMV project.
* Copyright (c) 2013/2014 Ibrahim Abdelkader <i.abdalkader@gmail.com>
* 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, "<mjpeg width:%d height:%d>", 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,
};

31
usr/examples/mjpeg.py Normal file
View File

@ -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")