/** */ /* * Copyright (C) 2013 Magic Lantern Team * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../ime_base/ime_base.h" #include "../trace/trace.h" #include "../mlv_rec/mlv.h" #include "../file_man/file_man.h" #include "../lv_rec/lv_rec.h" #include "../raw_twk/raw_twk.h" #include "../silent/lossless.h" #include "console.h" /* uncomment for live debug messages */ //~ #define trace_write(trace, fmt, ...) { printf(fmt, ## __VA_ARGS__); printf("\n"); msleep(500); } #define MAX_PATH 100 /* declare the video.bmp we have added */ /* icon from: http://www.softicons.com/free-icons/system-icons/touch-of-gold-icons-by-skingcito/video-black-icon */ EXTLD(video_bmp); /* only works if the raw_rec/mlv_rec module has its config variable non-static */ static int video_enabled_dummy = 0; extern int WEAK_FUNC(video_enabled_dummy) raw_video_enabled; extern int WEAK_FUNC(video_enabled_dummy) mlv_video_enabled; static char *movie_filename_dummy = ""; extern char *WEAK_FUNC(movie_filename_dummy) raw_movie_filename; extern char *WEAK_FUNC(movie_filename_dummy) mlv_movie_filename; static int raw_exposure_adjust_dummy = 0; extern int WEAK_FUNC(raw_exposure_adjust_dummy) raw_exposure_adjust; static int res_x = 0; static int res_y = 0; static int frame_count = 0; static int frame_size = 0; static int bits_per_pixel = 0; static unsigned int fps1000 = 0; /* used for RAW playback */ static volatile uint32_t mlv_play_render_abort = 0; static volatile uint32_t mlv_play_rendering = 0; static volatile uint32_t mlv_play_stopfile = 0; static CONFIG_INT("play.quality", mlv_play_quality, 0); /* range: 0-1, RAW_PREVIEW_* in raw.h */ static CONFIG_INT("play.exact_fps", mlv_play_exact_fps, 0); static int mlv_play_zoom = 0; static int mlv_play_zoom_x_pct = 0; static int mlv_play_zoom_y_pct = 0; static uint32_t mlv_play_trace_ctx = TRACE_ERROR; /* OSD menu items */ #define MLV_PLAY_MENU_IDLE 0 #define MLV_PLAY_MENU_FADEIN 1 #define MLV_PLAY_MENU_FADEOUT 2 #define MLV_PLAY_MENU_SHOWN 3 #define MLV_PLAY_MENU_HIDDEN 4 static uint32_t mlv_play_osd_state = MLV_PLAY_MENU_IDLE; static int32_t mlv_play_osd_x = 360; static int32_t mlv_play_osd_y = 0; static uint32_t mlv_play_render_timestep = 10; static uint32_t mlv_play_idle_timestep = 1000; static uint32_t mlv_play_osd_force_redraw = 0; static uint32_t mlv_play_osd_idle = 1000; static uint32_t mlv_play_osd_item = 0; static uint32_t mlv_play_paused = 0; static uint32_t mlv_play_info = 1; static uint32_t mlv_play_timer_stop = 1; static uint32_t mlv_play_frames_skipped = 0; /* this structure is used to build the mlv_xref_t table */ typedef struct { uint64_t frameTime; uint64_t frameOffset; uint16_t fileNumber; uint16_t frameType; } frame_xref_t; typedef struct { char fullPath[MAX_PATH]; uint32_t fileSize; uint32_t timestamp; } playlist_entry_t; #define SCREEN_MSG_LEN 64 typedef struct { char topLeft[SCREEN_MSG_LEN]; char topRight[SCREEN_MSG_LEN]; char botLeft[SCREEN_MSG_LEN]; char botRight[SCREEN_MSG_LEN]; } screen_msg_t; typedef struct { uint32_t frameSize; void *frameBuffer; void *frameBufferAligned; screen_msg_t messages; uint16_t xRes; uint16_t yRes; uint16_t bitDepth; uint16_t blackLevel; uint16_t whiteLevel; } frame_buf_t; /* set up two queues - one with empty buffers and one with buffers to render */ static struct msg_queue *mlv_play_queue_empty; static struct msg_queue *mlv_play_queue_render; static struct msg_queue *mlv_play_queue_osd; static struct msg_queue *mlv_play_queue_fps; /* queue for playlist item submits by scanner tasks */ static struct msg_queue *mlv_playlist_queue; static struct msg_queue *mlv_playlist_scan_queue; static playlist_entry_t *mlv_playlist = NULL; static uint32_t mlv_playlist_entries = 0; static char mlv_play_next_filename[MAX_PATH]; static char mlv_play_current_filename[MAX_PATH]; static playlist_entry_t mlv_playlist_next(playlist_entry_t current); static playlist_entry_t mlv_playlist_prev(playlist_entry_t current); static void mlv_playlist_delete(playlist_entry_t current); static void mlv_playlist_build(uint32_t priv); static uint32_t mlv_play_should_stop(); /* microsecond durations for one frame */ static uint32_t mlv_play_frame_div_pos = 0; static uint32_t mlv_play_frame_dividers[3]; static void mlv_play_flush_queue(struct msg_queue *queue) { uint32_t messages = 0; msg_queue_count(queue, &messages); while(messages > 0) { uint32_t tmp_buf = 0; msg_queue_receive(queue, &tmp_buf, 0); msg_queue_count(queue, &messages); } } static void mlv_play_next() { playlist_entry_t current; playlist_entry_t next; strncpy(current.fullPath, mlv_play_current_filename, sizeof(current.fullPath)); next = mlv_playlist_next(current); if(strlen(next.fullPath)) { strncpy(mlv_play_next_filename, next.fullPath, sizeof(mlv_play_next_filename)); mlv_play_stopfile = 1; mlv_play_paused = 0; } } static void mlv_play_prev() { playlist_entry_t current; playlist_entry_t next; strncpy(current.fullPath, mlv_play_current_filename, sizeof(current.fullPath)); next = mlv_playlist_prev(current); if(strlen(next.fullPath)) { strncpy(mlv_play_next_filename, next.fullPath, sizeof(mlv_play_next_filename)); mlv_play_stopfile = 1; mlv_play_paused = 0; } } static void mlv_play_progressbar(int pct, char *msg) { static int last_pct = -1; int border = 4; int height = 40; int width = 720 - 100; int x = (720 - width) / 2; int y = (480 - height) / 2; if(pct == 0) { bmp_fill(COLOR_BLACK, x, y - font_med.height - border, width, font_med.height); bmp_fill(COLOR_WHITE, x, y, width, height); bmp_fill(COLOR_BLACK, x + border - 1, y + border - 1, width - 2 * (border - 1), height - 2 * (border - 1)); last_pct = -1; } if(last_pct != pct) { bmp_fill(COLOR_BLUE, x + border, y + border, ((width - 2 * border) * pct) / 100, height - 2 * border); bmp_printf(FONT_MED, x, y - font_med.height - border, msg); last_pct = pct; } } /* call with positive duration and some string => print it for a while, then clear it */ /* call with 0 duration and some string => print it without clearing */ /* call with 0 duration and null string => just clear it */ static void mlv_play_show_dlg(uint32_t duration, char *string) { uint32_t width = 500; uint32_t height = 128 + 20; uint32_t pos_y = (480 - height) / 2; uint32_t pos_x = (720 - width) / 2; struct bmp_file_t *icon = NULL; if (string) { icon = bmp_load_ram((uint8_t *)LDVAR(video_bmp), LDLEN(video_bmp), 0); bmp_fill(COLOR_BG, pos_x, pos_y, width, height); /* redraw 4 times in case it gets overdrawn */ for(int loop = 0; loop < 4; loop++) { if(icon) { bmp_draw_scaled_ex(icon, pos_x + 10, pos_y + 10, 128, 128, 0); } else { bmp_printf(FONT_CANON, pos_x + 10, pos_y + 10 - font_med.height / 2, "[X]"); } bmp_printf(FONT_CANON, pos_x + 128 + 20, pos_y + (128 / 2) - 16, string); msleep(duration / 10); } free(icon); } if (duration || !string) { bmp_fill(COLOR_EMPTY, pos_x, pos_y, width, height); } } /* run deletion in the same task as playback, to sync with file closing etc */ static int mlv_play_delete_requested = 0; static void mlv_play_delete_request() { mlv_play_delete_requested = 1; mlv_play_show_dlg(0, "Stopping..."); } /* returns 1 on success, 0 on failure */ static int mlv_play_delete_work(char *filename) { uint32_t size = 0; uint32_t state = 0; uint32_t seq_number = 0; char current_ext[4]; char current_file[128]; /* original extension, to be used as read-only */ const char* main_ext = filename + strlen(filename) - 3; trace_write(mlv_play_trace_ctx, "[Delete] Delete request for: '%s'", filename); strncpy(current_file, filename, sizeof(current_file)); /* file does not exist. the specified base file must exist, so abort */ if(FIO_GetFileSize(filename, &size)) { trace_write(mlv_play_trace_ctx, " Does not exist!"); return 0; } int is_mlv = streq(main_ext, "MLV") || streq(main_ext, "mlv"); while (1) { switch(state) { case 0: /* try to delete main file */ strcpy(current_ext, main_ext); break; case 1: /* try to delete index file */ if (is_mlv) { strcpy(current_ext, "IDX"); } else { /* index files are only used on MLVs, skip for other files */ state++; continue; } break; case 2: /* try to delete chunk files */ snprintf(current_ext, 4, "X%02d", seq_number); current_ext[0] = main_ext[0]; break; case 3: /* next chunk file */ if(seq_number >= 99) { /* that would be the hundreth chunk, so lets say that was it */ state++; } else { /* increase chunk counter and delete next file */ seq_number++; state--; } continue; case 4: /* we are done */ return 1; } for(int drive = 0; drive < 2; drive++) { /* set extension */ strcpy(¤t_file[strlen(current_file) - 3], current_ext); /* set drive letter */ current_file[0] = drive ? 'A' : 'B'; if (!is_mlv) { if (current_file[0] != filename[0]) { /* non-MLV files can't be spanned, do not delete the file with the same name from the other card */ trace_write(mlv_play_trace_ctx, "[Delete] Skip drive: '%s'", current_file); continue; } } trace_write(mlv_play_trace_ctx, "[Delete] Next file: '%s'", current_file); /* check if file exists */ if(FIO_GetFileSize(current_file, &size)) { if (state == 0 && current_file[0] == filename[0]) { trace_write(mlv_play_trace_ctx, "[Delete] main file does not exist '%s'", current_file); return 0; } continue; } trace_write(mlv_play_trace_ctx, " existing, deleting"); /* if existing, try to delete and set fail flag if that doesnt work */ if(FIO_RemoveFile(current_file)) { trace_write(mlv_play_trace_ctx, " FAILED"); return 0; } else { trace_write(mlv_play_trace_ctx, " deleted"); } } /* if there was no failure in deleting the file, next state */ state++; } return 1; } static int mlv_play_delete() { playlist_entry_t current; playlist_entry_t next; playlist_entry_t prev; /* save the currently played file */ strncpy(current.fullPath, mlv_play_current_filename, sizeof(current.fullPath)); /* check next/prev */ next = mlv_playlist_next(current); prev = mlv_playlist_prev(current); /* remove the currently played from the playlist */ mlv_playlist_delete(current); /* delete the files */ int ok = mlv_play_delete_work(current.fullPath); trace_write(mlv_play_trace_ctx, "[Delete] returned %s", ok ? "success" : "FAILURE"); /* check which of the files is available and play it */ if(strlen(next.fullPath)) { strncpy(mlv_play_next_filename, next.fullPath, sizeof(mlv_play_next_filename)); } else if(strlen(prev.fullPath)) { strncpy(mlv_play_next_filename, prev.fullPath, sizeof(mlv_play_next_filename)); } else { /* if none is available, just stop playback */ mlv_play_render_abort = 1; } return ok; } static void mlv_play_delete_if_requested() { if (mlv_play_delete_requested) { mlv_play_show_dlg(0, "Deleting..."); int ok = mlv_play_delete(); mlv_play_show_dlg(3000, ok ? "Deleted." : "Delete failed."); mlv_play_delete_requested = 0; } } static void mlv_play_osd_quality(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_quality = MOD(mlv_play_quality + 1, 2); } if(msg) { snprintf(msg, msg_len, mlv_play_quality?"fast":"color"); } } static void mlv_play_osd_exact(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_exact_fps = MOD(mlv_play_exact_fps + 1, 2); } if(msg) { /* any idea for a meaningful text here? feature description in long: if enabled: play at exact fps with dropping if necessary grayscale might keep up with video rate, but color requires dropping for sure if disabled: play frame by frame as fast as possible */ snprintf(msg, msg_len, mlv_play_exact_fps?"exact":"all"); } } static void mlv_play_osd_prev(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_prev(); } if(msg) { snprintf(msg, msg_len, "<<"); } } static void mlv_play_osd_next(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_next(); } if(msg) { snprintf(msg, msg_len, ">>"); } } static void mlv_play_osd_pause(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_paused = MOD(mlv_play_paused + 1, 2); } if(msg) { snprintf(msg, msg_len, mlv_play_paused?"|>":"||"); } } static void mlv_play_osd_quit(char *msg, uint32_t msg_len, uint32_t selected) { if(selected) { mlv_play_render_abort = 1; } if(msg) { snprintf(msg, msg_len, "Exit"); } } static uint32_t mlv_play_osd_delete_selected = 0; static void mlv_play_osd_delete(char *msg, uint32_t msg_len, uint32_t selected) { uint32_t max_time = 5000; if(selected) { if(!mlv_play_osd_delete_selected || selected == 2) { mlv_play_osd_force_redraw = 1; mlv_play_osd_delete_selected = get_ms_clock(); } else { mlv_play_osd_force_redraw = 0; mlv_play_osd_delete_selected = 0; mlv_play_delete_request(); } } if(msg) { uint32_t time_passed = get_ms_clock() - mlv_play_osd_delete_selected; uint32_t seconds = (max_time - time_passed) / 1000; if(mlv_play_osd_delete_selected && seconds > 0) { snprintf(msg, msg_len, "[delete? %ds]", seconds); } else { mlv_play_osd_delete_selected = 0; mlv_play_osd_force_redraw = 0; snprintf(msg, msg_len, "Del"); } } } static void(*mlv_play_osd_items[])(char *, uint32_t, uint32_t) = { &mlv_play_osd_prev, &mlv_play_osd_pause, &mlv_play_osd_next, &mlv_play_osd_quality, &mlv_play_osd_exact, &mlv_play_osd_delete, &mlv_play_osd_quit }; static uint32_t mlv_play_osd_handle(uint32_t msg) { switch(msg) { case MODULE_KEY_PRESS_SET: case MODULE_KEY_JOY_CENTER: { /* execute menu item */ mlv_play_osd_items[mlv_play_osd_item](NULL, 0, 1); break; } case MODULE_KEY_WHEEL_UP: //case MODULE_KEY_WHEEL_LEFT: case MODULE_KEY_PRESS_LEFT: { if(mlv_play_osd_item > 0) { mlv_play_osd_item--; } break; } case MODULE_KEY_WHEEL_DOWN: //case MODULE_KEY_WHEEL_RIGHT: case MODULE_KEY_PRESS_RIGHT: { if(mlv_play_osd_item < COUNT(mlv_play_osd_items) - 1) { mlv_play_osd_item++; } break; } } return 0; } static uint32_t mlv_play_osd_draw() { uint32_t redraw = 0; uint32_t border = 4; uint32_t y_offset = 28; /* undraw last drawn OSD item */ static char osd_line[64] = ""; uint32_t w = bmp_string_width(FONT_LARGE, osd_line); uint32_t h = fontspec_height(FONT_LARGE); bmp_fill(COLOR_EMPTY, mlv_play_osd_x - w/2 - border, mlv_play_osd_y - border, w + 2 * border, h + 2 * border); /* handle animation */ switch(mlv_play_osd_state) { case MLV_PLAY_MENU_SHOWN: { redraw = 0; break; } case MLV_PLAY_MENU_HIDDEN: case MLV_PLAY_MENU_IDLE: { mlv_play_osd_y = os.y_max + 1; redraw = 0; break; } case MLV_PLAY_MENU_FADEIN: { int y_top = os.y_max - font_large.height - y_offset; mlv_play_osd_y = MAX(mlv_play_osd_y - border, y_top); if(mlv_play_osd_y <= y_top) { mlv_play_osd_state = MLV_PLAY_MENU_SHOWN; } redraw = 1; break; } case MLV_PLAY_MENU_FADEOUT: { int y_bottom = os.y_max + 1; mlv_play_osd_y = MIN(mlv_play_osd_y + border, y_bottom); if(mlv_play_osd_y >= y_bottom) { mlv_play_osd_state = MLV_PLAY_MENU_HIDDEN; } redraw = 1; break; } } /* draw a line with all OSD buttons */ char selected_item[64]; uint32_t selected_x = 0; strcpy(osd_line, ""); for(uint32_t pos = 0; pos < COUNT(mlv_play_osd_items); pos++) { char msg[64]; mlv_play_osd_items[pos](msg, sizeof(msg), 0); /* if this is the selected one, keep the position for redrawing highlighted */ if(pos == mlv_play_osd_item) { strcpy(selected_item, msg); selected_x = bmp_string_width(FONT_LARGE, osd_line); } strcat(osd_line, " "); strcat(osd_line, msg); strcat(osd_line, " "); } w = bmp_string_width(FONT_LARGE, osd_line); bmp_fill(COLOR_BG, mlv_play_osd_x - w/2 - border, mlv_play_osd_y - border, w + 2 * border, h + 2 * border); bmp_printf(FONT(FONT_LARGE,COLOR_WHITE,COLOR_BG), mlv_play_osd_x - w/2, mlv_play_osd_y, osd_line); /* draw selected item over with blue background */ bmp_printf(FONT(FONT_LARGE,COLOR_WHITE,COLOR_BLUE), mlv_play_osd_x - w/2 + selected_x, mlv_play_osd_y, " %s ", selected_item); return redraw; } static void mlv_play_osd_act(void *handler) { int entry = 0; for(entry = 0; entry < COUNT(mlv_play_osd_items); entry++) { if(mlv_play_osd_items[entry] == handler) { mlv_play_osd_item = entry; mlv_play_osd_state = MLV_PLAY_MENU_FADEIN; return; } } } static void mlv_play_osd_task(void *priv) { uint32_t next_render_time = get_ms_clock() + mlv_play_render_timestep; mlv_play_osd_state = MLV_PLAY_MENU_IDLE; mlv_play_osd_item = 1; mlv_play_paused = 0; uint32_t last_keypress_time = get_ms_clock(); TASK_LOOP { uint32_t key; uint32_t timeout = next_render_time - get_ms_clock(); timeout = MIN(timeout, mlv_play_idle_timestep); if(!msg_queue_receive(mlv_play_queue_osd, &key, timeout)) { /* there was a keypress */ last_keypress_time = get_ms_clock(); /* no matter which state - these are handled */ switch(key) { case MODULE_KEY_WHEEL_LEFT: mlv_play_prev(); if(mlv_play_osd_state != MLV_PLAY_MENU_SHOWN) { mlv_play_osd_state = MLV_PLAY_MENU_FADEIN; } break; case MODULE_KEY_WHEEL_RIGHT: mlv_play_next(); if(mlv_play_osd_state != MLV_PLAY_MENU_SHOWN) { mlv_play_osd_state = MLV_PLAY_MENU_FADEIN; } break; case MODULE_KEY_TRASH: mlv_play_osd_act(&mlv_play_osd_delete); mlv_play_osd_delete(NULL, 0, 2); break; case MODULE_KEY_PLAY: mlv_play_osd_act(&mlv_play_osd_pause); mlv_play_osd_pause(NULL, 0, 1); break; case MODULE_KEY_PRESS_ZOOMIN: mlv_play_osd_state = MLV_PLAY_MENU_HIDDEN; mlv_play_zoom = (mlv_play_zoom + 1) % 4; raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); break; case MODULE_KEY_PRESS_UP: mlv_play_zoom_y_pct = MAX(0, mlv_play_zoom_y_pct - 10); raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); break; case MODULE_KEY_PRESS_DOWN: mlv_play_zoom_y_pct = MIN(100, mlv_play_zoom_y_pct + 10); raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); break; case MODULE_KEY_PRESS_LEFT: mlv_play_zoom_x_pct = MAX(0, mlv_play_zoom_x_pct - 10); raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); break; case MODULE_KEY_PRESS_RIGHT: mlv_play_zoom_x_pct = MIN(100, mlv_play_zoom_x_pct + 10); raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); break; } switch(mlv_play_osd_state) { case MLV_PLAY_MENU_SHOWN: case MLV_PLAY_MENU_FADEIN: { if(key == MODULE_KEY_Q || key == MODULE_KEY_PICSTYLE) { if (!mlv_play_osd_force_redraw) { mlv_play_osd_state = MLV_PLAY_MENU_FADEOUT; } } else { mlv_play_osd_handle(key); } break; } /* when fading out, still handle and fade back in */ case MLV_PLAY_MENU_FADEOUT: { if(!mlv_play_zoom) { mlv_play_osd_state = MLV_PLAY_MENU_FADEIN; mlv_play_osd_handle(key); } break; } case MLV_PLAY_MENU_IDLE: case MLV_PLAY_MENU_HIDDEN: { if(!mlv_play_zoom) { if (key == MODULE_KEY_PRESS_SET || key == MODULE_KEY_Q || key == MODULE_KEY_PICSTYLE) { mlv_play_osd_state = MLV_PLAY_MENU_FADEIN; } if (key == MODULE_KEY_INFO) { clrscr(); mlv_play_info = MOD(mlv_play_info + 1, 2) ? 2 : 0; } } break; } } } uint32_t idle_time = get_ms_clock() - last_keypress_time; if(mlv_play_render_abort) { break; } if(mlv_play_osd_draw()) { next_render_time = get_ms_clock() + mlv_play_render_timestep; } else { next_render_time = get_ms_clock() + mlv_play_idle_timestep; /* when redrawing is forced, keep OSD shown */ if(mlv_play_osd_force_redraw) { mlv_play_osd_state = MLV_PLAY_MENU_SHOWN; } else if(idle_time > mlv_play_osd_idle && mlv_play_osd_state == MLV_PLAY_MENU_SHOWN) { mlv_play_osd_state = MLV_PLAY_MENU_FADEOUT; } } continue; } } static void mlv_play_xref_resize(frame_xref_t **table, uint32_t entries, uint32_t *allocated) { /* make sure there is no crappy pointer before using */ if(*allocated == 0) { *table = NULL; } /* only resize if the buffer is too small */ if(entries * sizeof(frame_xref_t) > *allocated) { *allocated += (entries + 1) * sizeof(frame_xref_t); *table = realloc(*table, *allocated); } } static void mlv_play_xref_sort(frame_xref_t *table, uint32_t entries) { if (!entries) return; uint32_t n = entries; do { uint32_t newn = 1; for (uint32_t i = 0; i < n-1; ++i) { if (table[i].frameTime > table[i+1].frameTime) { frame_xref_t tmp = table[i+1]; table[i+1] = table[i]; table[i] = tmp; newn = i + 1; } } n = newn; } while (n > 1); } static mlv_xref_hdr_t *mlv_play_load_index(char *base_filename) { mlv_xref_hdr_t *block_hdr = NULL; char filename[128]; FILE *in_file = NULL; strncpy(filename, base_filename, sizeof(filename)); strcpy(&filename[strlen(filename) - 3], "IDX"); in_file = FIO_OpenFile(filename, O_RDONLY | O_SYNC); if (!in_file) { return NULL; } TASK_LOOP { mlv_hdr_t buf; int64_t position = 0; position = FIO_SeekSkipFile(in_file, 0, SEEK_CUR); if(FIO_ReadFile(in_file, &buf, sizeof(mlv_hdr_t)) != sizeof(mlv_hdr_t)) { break; } /* jump back to the beginning of the block just read */ FIO_SeekSkipFile(in_file, position, SEEK_SET); /* we should check the MLVI header for matching UID value to make sure its the right index... */ if(!memcmp(buf.blockType, "XREF", 4)) { block_hdr = fio_malloc(buf.blockSize); if(FIO_ReadFile(in_file, block_hdr, buf.blockSize) != (int32_t)buf.blockSize) { free(block_hdr); block_hdr = NULL; } } else { FIO_SeekSkipFile(in_file, position + buf.blockSize, SEEK_SET); } /* we are at the same position as before, so abort */ if(position == FIO_SeekSkipFile(in_file, 0, SEEK_CUR)) { break; } } FIO_CloseFile(in_file); return block_hdr; } static void mlv_play_save_index(char *base_filename, mlv_file_hdr_t *ref_file_hdr, int fileCount, frame_xref_t *index, int entries) { char filename[128]; FILE *out_file = NULL; strncpy(filename, base_filename, sizeof(filename)); strcpy(&filename[strlen(filename) - 3], "IDX"); out_file = FIO_CreateFile(filename); if (!out_file) { return; } /* first write MLVI header */ mlv_file_hdr_t file_hdr = *ref_file_hdr; /* update fields */ file_hdr.blockSize = sizeof(mlv_file_hdr_t); file_hdr.videoFrameCount = 0; file_hdr.audioFrameCount = 0; file_hdr.fileNum = fileCount + 1; FIO_WriteFile(out_file, &file_hdr, sizeof(mlv_file_hdr_t)); /* now write XREF block */ mlv_xref_hdr_t hdr; memset(&hdr, 0x00, sizeof(mlv_xref_hdr_t)); memcpy(hdr.blockType, "XREF", 4); hdr.blockSize = sizeof(mlv_xref_hdr_t) + entries * sizeof(mlv_xref_t); hdr.entryCount = entries; if(FIO_WriteFile(out_file, &hdr, sizeof(mlv_xref_hdr_t)) != sizeof(mlv_xref_hdr_t)) { FIO_CloseFile(out_file); return; } uint32_t last_pct = 0; mlv_play_progressbar(0, ""); uint32_t buffer_size = 256; mlv_xref_t *buffer = malloc(sizeof(mlv_xref_t) * buffer_size); /* and then the single entries */ for(int entry = 0; entry < entries; entry++) { uint32_t buffer_pos = entry % buffer_size; mlv_xref_t *field = &buffer[buffer_pos]; uint32_t pct = (entry*100)/entries; if(last_pct != pct) { char msg[100]; snprintf(msg, sizeof(msg), "Saving index (%d entries)...", entries); mlv_play_progressbar(pct, msg); last_pct = pct; } memset(field, 0x00, sizeof(mlv_xref_t)); field->frameOffset = index[entry].frameOffset; field->fileNumber = index[entry].fileNumber; field->frameType = index[entry].frameType; if((buffer_pos + 1) == buffer_size || ((entry + 1) == entries)) { uint32_t write_size = (buffer_pos + 1) * sizeof(mlv_xref_t); if(FIO_WriteFile(out_file, buffer, write_size) != write_size) { FIO_CloseFile(out_file); return; } } } free(buffer); FIO_CloseFile(out_file); } static void mlv_play_build_index(char *filename, FILE **chunk_files, uint32_t chunk_count) { frame_xref_t *frame_xref_table = NULL; uint32_t frame_xref_entries = 0; uint32_t frame_xref_allocated = 0; mlv_file_hdr_t main_header; for(uint32_t chunk = 0; chunk < chunk_count; chunk++) { uint32_t last_pct = 0; int64_t size = 0; int64_t position = 0; size = FIO_SeekSkipFile(chunk_files[chunk], 0, SEEK_END); FIO_SeekSkipFile(chunk_files[chunk], 0, SEEK_SET); mlv_play_progressbar(0, ""); while(1) { if(ml_shutdown_requested) { break; } mlv_hdr_t buf; uint64_t timestamp = 0; uint32_t pct = ((position / 10) / (size / 1000)); if(last_pct != pct) { char msg[100]; snprintf(msg, sizeof(msg), "Building index... (%d/%d)", chunk + 1, chunk_count); mlv_play_progressbar(pct, msg); last_pct = pct; } int read = FIO_ReadFile(chunk_files[chunk], &buf, sizeof(mlv_hdr_t)); if(read != sizeof(mlv_hdr_t)) { if(read <= 0) { break; } else { bmp_printf(FONT_MED, 30, 190, "File #%d ends prematurely, %d bytes read", chunk, read); beep(); msleep(2000); return; } } /* unexpected block header size? */ if(buf.blockSize < sizeof(mlv_hdr_t) || buf.blockSize > 50 * 1024 * 1024) { bmp_printf(FONT_MED, 30, 190, "Invalid header size: %d bytes at 0x%08X", buf.blockSize, position); beep(); msleep(2000); return; } /* file header */ if(!memcmp(buf.blockType, "MLVI", 4)) { mlv_file_hdr_t file_hdr; uint32_t hdr_size = MIN(sizeof(mlv_file_hdr_t), buf.blockSize); FIO_SeekSkipFile(chunk_files[chunk], position, SEEK_SET); /* read the whole header block, but limit size to either our local type size or the written block size */ if(FIO_ReadFile(chunk_files[chunk], &file_hdr, hdr_size) != (int32_t)hdr_size) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during MLVI"); beep(); msleep(2000); return; } /* is this the first file? */ if(file_hdr.fileNum == 0) { memcpy(&main_header, &file_hdr, sizeof(mlv_file_hdr_t)); } else { /* no, its another chunk */ if(main_header.fileGuid != file_hdr.fileGuid) { bmp_printf(FONT_MED, 30, 190, "Error: GUID within the file chunks mismatch!"); beep(); msleep(2000); return; } } /* emulate timestamp zero (will overwrite version string) */ timestamp = 0; } else { /* all other blocks have a timestamp */ timestamp = buf.timestamp; } /* dont index NULL blocks */ if(memcmp(buf.blockType, "NULL", 4)) { mlv_play_xref_resize(&frame_xref_table, frame_xref_entries + 1, &frame_xref_allocated); /* add xref data */ frame_xref_table[frame_xref_entries].frameTime = timestamp; frame_xref_table[frame_xref_entries].frameOffset = position; frame_xref_table[frame_xref_entries].fileNumber = chunk; frame_xref_table[frame_xref_entries].frameType = !memcmp(buf.blockType, "VIDF", 4) ? MLV_FRAME_VIDF : !memcmp(buf.blockType, "AUDF", 4) ? MLV_FRAME_AUDF : MLV_FRAME_UNSPECIFIED; frame_xref_entries++; } position += buf.blockSize; FIO_SeekSkipFile(chunk_files[chunk], position, SEEK_SET); } } mlv_play_xref_sort(frame_xref_table, frame_xref_entries); mlv_play_save_index(filename, &main_header, chunk_count, frame_xref_table, frame_xref_entries); } static mlv_xref_hdr_t *mlv_play_get_index(char *filename, FILE **chunk_files, uint32_t chunk_count) { mlv_xref_hdr_t *table = NULL; table = mlv_play_load_index(filename); if(table) { return table; } bmp_printf(FONT_LARGE, 30, 100, "Preparing:", filename); bmp_printf(FONT_MED, 40, 100 + font_large.height + 1, filename); mlv_play_build_index(filename, chunk_files, chunk_count); return mlv_play_load_index(filename); } static unsigned int mlv_play_is_raw(FILE *f) { lv_rec_file_footer_t footer; /* get current position in file, seek to footer, read and go back where we were */ int64_t old_pos = FIO_SeekSkipFile(f, 0, SEEK_CUR); FIO_SeekSkipFile(f, -(int64_t)sizeof(lv_rec_file_footer_t), SEEK_END); int read = FIO_ReadFile(f, &footer, sizeof(lv_rec_file_footer_t)); FIO_SeekSkipFile(f, old_pos, SEEK_SET); /* check if the footer was read */ if(read != sizeof(lv_rec_file_footer_t)) { return 0; } /* check if the footer is in the right format */ if(strncmp((char *)footer.magic, "RAWM", 4)) { return 0; } return 1; } static unsigned int mlv_play_is_mlv(FILE *f) { mlv_file_hdr_t header; /* get current position in file, seek to footer, read and go back where we were */ int64_t old_pos = FIO_SeekSkipFile(f, 0, SEEK_CUR); FIO_SeekSkipFile(f, 0, SEEK_SET); int read = FIO_ReadFile(f, &header, sizeof(mlv_file_hdr_t)); FIO_SeekSkipFile(f, old_pos, SEEK_SET); /* check if the footer was read */ if(read != sizeof(mlv_file_hdr_t)) { return 0; } /* check if the footer is in the right format */ if(strncmp((char *)header.fileMagic, "MLVI", 4)) { return 0; } return 1; } static unsigned int mlv_play_read_footer(FILE *f) { lv_rec_file_footer_t footer; /* get current position in file, seek to footer, read and go back where we were */ int64_t old_pos = FIO_SeekSkipFile(f, 0, SEEK_CUR); FIO_SeekSkipFile(f, - (int64_t)sizeof(lv_rec_file_footer_t), SEEK_END); int read = FIO_ReadFile(f, &footer, sizeof(lv_rec_file_footer_t)); FIO_SeekSkipFile(f, old_pos, SEEK_SET); /* check if the footer was read */ if(read != sizeof(lv_rec_file_footer_t)) { bmp_printf(FONT_MED, 30, 190, "File position mismatch. Read %d", read); beep(); msleep(1000); } /* check if the footer is in the right format */ if(strncmp((char *)footer.magic, "RAWM", 4)) { bmp_printf(FONT_MED, 30, 190, "Footer format mismatch"); beep(); msleep(1000); return 0; } /* update global variables with data from footer */ res_x = footer.xRes; res_y = footer.yRes; frame_count = footer.frameCount + 1; frame_size = footer.frameSize; raw_info.white_level = footer.raw_info.white_level; raw_info.black_level = footer.raw_info.black_level; fps1000 = footer.sourceFpsx1000; return 1; } static FILE **mlv_play_load_chunks(char *base_filename, uint32_t *entries) { uint32_t seq_number = 0; char filename[128]; *entries = 0; strncpy(filename, base_filename, sizeof(filename)); FILE **files = malloc(sizeof(FILE*)); files[0] = FIO_OpenFile(filename, O_RDONLY | O_SYNC); if (!files[0]) { return NULL; } (*entries)++; while(seq_number < 99) { files = realloc(files, (*entries + 1) * sizeof(FILE*)); /* check for the next file M00, M01 etc */ char seq_name[3]; snprintf(seq_name, 3, "%02d", seq_number); seq_number++; strcpy(&filename[strlen(filename) - 2], seq_name); /* try to open from A: first*/ filename[0] = 'A'; files[*entries] = FIO_OpenFile(filename, O_RDONLY | O_SYNC); /* if failed, try B */ if (!files[*entries]) { filename[0] = 'B'; files[*entries] = FIO_OpenFile(filename, O_RDONLY | O_SYNC); } /* when succeeded, check for next chunk, else abort */ if (files[*entries]) { (*entries)++; } else { break; } } return files; } static void mlv_play_close_chunks(FILE **chunk_files, uint32_t chunk_count) { if(!chunk_files || !chunk_count || chunk_count > 100) { bmp_printf(FONT_MED, 30, 400, "mlv_play_close_chunks(): faulty parameters"); beep(); return; } for(uint32_t pos = 0; pos < chunk_count; pos++) { FIO_CloseFile(chunk_files[pos]); } } /* just don't run it on overexposed footage :) */ static void check_dup_frame(frame_buf_t *buffer) { /* extremely unlikely to get identical values * in subsequent frames, because of the noise * exception: overexposed areas * checking a small number of pixels should be * enough for non-overexposed footage, and very fast */ struct sample { int pixels[5][5]; } __attribute__((packed)); /* store a small sample from current frame and also last 3 frames */ static struct sample prev_samples[4] = { { { { -1 } } }, { { { -2 } } }, { { { -3 } } }, { { { -4 } } }, }; prev_samples[3] = prev_samples[2]; prev_samples[2] = prev_samples[1]; prev_samples[1] = prev_samples[0]; /* sample the current frame (all channels) */ for (int i = -2; i <= 2; i++) { for (int j = -2; j <= 2; j++) { prev_samples[0].pixels[i+2][j+2] = raw_get_pixel_ex( buffer->frameBufferAligned, buffer->xRes / 2 + j * 49, buffer->yRes / 2 + i * 49 ); } } /* compare current frame with past samples */ for (int i = 1; i <= 3; i++) { if (memcmp(&prev_samples[0], &prev_samples[i], sizeof(prev_samples[0])) == 0) { printf("Duplicate frame (%d)\n", i); } } } static void mlv_play_render_frame(frame_buf_t *buffer) { raw_info.buffer = buffer->frameBufferAligned; raw_info.bits_per_pixel = buffer->bitDepth; raw_info.black_level = buffer->blackLevel; raw_info.white_level = buffer->whiteLevel; raw_set_geometry(buffer->xRes, buffer->yRes, 0, 0, 0, 0); /* fixme: read aspect ratio from metadata */ raw_force_aspect_ratio(1, 1); if(raw_twk_available()) { raw_twk_render_ex(buffer->frameBufferAligned, buffer->xRes, buffer->yRes, buffer->bitDepth, mlv_play_quality, buffer->blackLevel); } else { raw_preview_fast_ex((void*)-1,(void*)-1,-1,-1,mlv_play_quality); if (!mlv_play_paused) { check_dup_frame(buffer); } } } static void mlv_play_render_task(uint32_t priv) { uint32_t redraw_loop = 0; frame_buf_t *buffer_paused = NULL; TASK_LOOP { frame_buf_t *buffer; /* signal to stop rendering */ if(mlv_play_render_abort) { break; } /* user exited from playback */ if(gui_state != GUISTATE_PLAYMENU) { break; } if(mlv_play_paused && !mlv_play_should_stop() && buffer_paused) { mlv_play_render_frame(buffer_paused); msleep(100); continue; } /* is there something to render? */ if(msg_queue_receive(mlv_play_queue_render, &buffer, 50)) { continue; } /* if in pause mode, fetch a buffer for pause display and keep it */ if(mlv_play_paused) { buffer_paused = buffer; continue; } else if(buffer_paused) { /* free the pause display buffer */ msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer_paused); buffer_paused = NULL; } if(!buffer->frameBuffer) { bmp_printf(FONT_MED, 30, 400, "buffer empty"); beep(); msleep(1000); msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer); break; } mlv_play_render_frame(buffer); /* if info display is requested, paint it. todo: thats OSD stuff, so it should be removed from here */ if(mlv_play_info) { /* a simple way of forcing a redraw */ if(mlv_play_info == 2) { redraw_loop = 0; mlv_play_info = 1; } /* cheap redraw every time, sometimes do a more expensive clearing too */ if(redraw_loop % 10) { bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG), 0, 0, buffer->messages.topLeft); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG) | FONT_ALIGN_RIGHT, os.x_max, 0, buffer->messages.topRight); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG), 0, font_med.height, buffer->messages.botLeft); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG) | FONT_ALIGN_RIGHT, os.x_max, font_med.height, buffer->messages.botRight); //bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG) | FONT_ALIGN_RIGHT, os.x_max, 2 * font_med.height, "pl: %d", mlv_playlist_entries); } else { BMP_LOCK ( bmp_idle_copy(0,1); bmp_draw_to_idle(1); bmp_fill(COLOR_BG, 0, 0, 720, 2 * font_med.height); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG), 0, 0, buffer->messages.topLeft); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG) | FONT_ALIGN_RIGHT, os.x_max, 0, buffer->messages.topRight); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG), 0, font_med.height, buffer->messages.botLeft); bmp_printf(FONT(FONT_MED,COLOR_WHITE,COLOR_BG) | FONT_ALIGN_RIGHT, os.x_max, font_med.height, buffer->messages.botRight); bmp_draw_to_idle(0); bmp_idle_copy(1,0); ) } redraw_loop++; } else { redraw_loop = 0; } /* finished displaying, requeue frame buffer for refilling */ msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer); } if(buffer_paused) { msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer_paused); } mlv_play_rendering = 0; } static uint32_t mlv_play_should_stop() { /* powering off the camera is for sure a good reason to stop any playback */ if(ml_shutdown_requested) { return 1; } /* not playing anymore because user left the current GUI state somehow */ if(gui_state != GUISTATE_PLAYMENU) { return 1; } /* suddenly the render task exited, it makes no more sense to play */ if(!mlv_play_rendering) { return 1; } /* the current file playback is requested to be stopped. reason might be a file change */ if(mlv_play_stopfile) { return 1; } /* should the current file be deleted? stop playback first */ if (mlv_play_delete_requested) { return 1; } return 0; } static void mlv_play_clear_screen() { /* clear anything */ vram_clear_lv(); clrscr(); /* update OSD */ msg_queue_post(mlv_play_queue_osd, (uint32_t) 0); /* force redraw of info lines */ mlv_play_info = mlv_play_info ? 2 : 0; } static void mlv_play_fps_tick(int expiry_value, void *priv) { uint32_t offset = mlv_play_frame_dividers[mlv_play_frame_div_pos]; mlv_play_frame_div_pos = (mlv_play_frame_div_pos + 1) % 3; if(mlv_play_timer_stop) { mlv_play_timer_stop = 0; return; } //if (!mlv_play_paused) { msg_queue_post(mlv_play_queue_fps, 0); } SetHPTimerNextTick(expiry_value, offset, &mlv_play_fps_tick, &mlv_play_fps_tick, NULL); } static void mlv_play_stop_fps_timer() { mlv_play_timer_stop = 1; while(mlv_play_timer_stop) { msleep(20); } } static void mlv_play_start_fps_timer(uint32_t fps_nom, uint32_t fps_denom) { if (fps_nom == 0 || fps_nom < 2 * fps_denom) { /* fps too low or bad metadata? play at 24 fps */ fps_denom = MAX(fps_denom, 1); fps_nom = 24 * fps_denom; } uint32_t three_frames = 3 * 1000000 * fps_denom / fps_nom; mlv_play_frame_dividers[0] = mlv_play_frame_dividers[1] = mlv_play_frame_dividers[2] = three_frames / 3; switch(three_frames % 3) { case 2: mlv_play_frame_dividers[1]++; case 1: mlv_play_frame_dividers[0]++; default: break; } /* ensure the queue is empty */ mlv_play_flush_queue(mlv_play_queue_fps); /* reset counters */ mlv_play_frame_div_pos = 0; mlv_play_timer_stop = 0; mlv_play_frames_skipped = 0; /* and finally start timer in 1 us */ SetHPTimerAfterNow(1, &mlv_play_fps_tick, &mlv_play_fps_tick, NULL); } static void mlv_play_mlv(char *filename, FILE **chunk_files, uint32_t chunk_count) { uint32_t fps_timer_started = 0; uint32_t frame_size = 0; uint32_t frame_count = 0; mlv_xref_hdr_t *block_xref = NULL; mlv_lens_hdr_t lens_block; mlv_rawi_hdr_t rawi_block; mlv_rtci_hdr_t wavi_block; mlv_rtci_hdr_t rtci_block; mlv_file_hdr_t main_header; void *mlv_play_decomp_buf = NULL; struct memSuite *mlv_play_decomp_suite = NULL; /* make sure there is no crap in stack variables */ memset(&lens_block, 0x00, sizeof(mlv_lens_hdr_t)); memset(&rawi_block, 0x00, sizeof(mlv_rawi_hdr_t)); memset(&wavi_block, 0x00, sizeof(mlv_rawi_hdr_t)); memset(&rtci_block, 0x00, sizeof(mlv_rtci_hdr_t)); memset(&main_header, 0x00, sizeof(mlv_file_hdr_t)); /* read footer information and update global variables, will seek automatically */ if(chunk_count < 1 || !mlv_play_is_mlv(chunk_files[0])) { bmp_printf(FONT_LARGE, 30, 100, "Invalid MLV:", filename); bmp_printf(FONT_MED, 40, 100 + font_large.height + 1, filename); return; } /* load or create index file */ block_xref = mlv_play_get_index(filename, chunk_files, chunk_count); if (!block_xref) { bmp_printf(FONT_LARGE, 30, 100, "Index error:", filename); bmp_printf(FONT_MED, 40, 100 + font_large.height + 1, filename); return; } mlv_xref_t *xrefs = (mlv_xref_t *)&(((uint8_t*)block_xref)[sizeof(mlv_xref_hdr_t)]); /* index building would print on screen */ mlv_play_clear_screen(); uint32_t block_xref_pos = 0; while(1) { /* after playback is finished, return to beginning and put into pause */ if (block_xref_pos == block_xref->entryCount) { /* reset counter */ block_xref_pos = 0; /* miscellaneous cleanup */ mlv_play_flush_queue(mlv_play_queue_fps); mlv_play_info = (mlv_play_info == 1 ? 2 : mlv_play_info); mlv_play_paused = 1; } /* no not pause reader anymore, the renderer might still need a frame */ //while(mlv_play_paused && !mlv_play_should_stop()) //{ // msleep(100); //} /* there are various reasons why this read/play task should get stopped */ if(mlv_play_should_stop()) { break; } /* if in exact playback and this is a skippable VIDF frame */ if(mlv_play_exact_fps) { if (xrefs[block_xref_pos].frameType == MLV_FRAME_VIDF) { uint32_t frames_to_skip = 0; msg_queue_count(mlv_play_queue_fps, &frames_to_skip); /* skip this frame if we are behind */ if(frames_to_skip > 0) { uint32_t temp = 0; msg_queue_receive(mlv_play_queue_fps, &temp, 50); mlv_play_frames_skipped++; block_xref_pos++; continue; } } } else { /* if not, just keep the queue clean */ mlv_play_flush_queue(mlv_play_queue_fps); } /* get the file and position of the next block */ uint32_t in_file_num = xrefs[block_xref_pos].fileNumber; int64_t position = xrefs[block_xref_pos].frameOffset; /* select file and seek to the right position */ FILE *in_file = chunk_files[in_file_num]; /* use the common header structure to get file size */ mlv_hdr_t buf; FIO_SeekSkipFile(in_file, position, SEEK_SET); if(FIO_ReadFile(in_file, &buf, sizeof(mlv_hdr_t)) != sizeof(mlv_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during block header"); beep(); msleep(1000); break; } FIO_SeekSkipFile(in_file, position, SEEK_SET); /* special case: if first block read, reset frame count as all MLVI blocks frame count will get accumulated */ if(block_xref_pos == 0) { frame_count = 0; } /* file header */ if(!memcmp(buf.blockType, "MLVI", 4)) { mlv_file_hdr_t file_hdr; uint32_t hdr_size = MIN(sizeof(mlv_file_hdr_t), buf.blockSize); /* read the whole header block, but limit size to either our local type size or the written block size */ if(FIO_ReadFile(in_file, &file_hdr, hdr_size) != (int32_t)hdr_size) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during MLVI"); beep(); msleep(1000); break; } frame_count += file_hdr.videoFrameCount; /* is this the first file? */ if(file_hdr.fileNum == 0) { memcpy(&main_header, &file_hdr, sizeof(mlv_file_hdr_t)); } else { /* no, its another chunk */ if(main_header.fileGuid != file_hdr.fileGuid) { bmp_printf(FONT_MED, 30, 190, "Error: GUID within the file chunks mismatch!"); beep(); msleep(1000); break; } } } else if(!memcmp(buf.blockType, "LENS", 4)) { if(FIO_ReadFile(in_file, &lens_block, sizeof(mlv_lens_hdr_t)) != sizeof(mlv_lens_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during LENS"); beep(); msleep(1000); break; } } else if(!memcmp(buf.blockType, "RTCI", 4)) { if(FIO_ReadFile(in_file, &rtci_block, sizeof(mlv_rtci_hdr_t)) != sizeof(mlv_rtci_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during RTCI"); beep(); msleep(1000); break; } } else if(!memcmp(buf.blockType, "RAWI", 4)) { if(FIO_ReadFile(in_file, &rawi_block, sizeof(mlv_rawi_hdr_t)) != sizeof(mlv_rawi_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during RAWI"); beep(); msleep(1000); break; } frame_size = rawi_block.xRes * rawi_block.yRes * rawi_block.raw_info.bits_per_pixel / 8; bits_per_pixel = rawi_block.raw_info.bits_per_pixel; } else if(!memcmp(buf.blockType, "WAVI", 4)) { if(FIO_ReadFile(in_file, &wavi_block, sizeof(mlv_wavi_hdr_t)) != sizeof(mlv_wavi_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during WAVI"); beep(); msleep(1000); break; } } else if(!memcmp(buf.blockType, "AUDF", 4)) { /* ToDo: new sound system calls here as soon its merged into unified */ } else if(!memcmp(buf.blockType, "VIDF", 4)) { mlv_vidf_hdr_t vidf_block; if(FIO_ReadFile(in_file, &vidf_block, sizeof(mlv_vidf_hdr_t)) != sizeof(mlv_vidf_hdr_t)) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during VIDF"); beep(); msleep(1000); break; } frame_buf_t *buffer = NULL; /* now get a buffer from the queue */ while (msg_queue_receive(mlv_play_queue_empty, &buffer, 100) && !mlv_play_should_stop()); if (mlv_play_should_stop()) { /* if we also got a buffer, play nicely and give it back */ if(buffer) { msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer); } break; } /* check if the queued buffer has the correct size */ if(buffer->frameSize != frame_size) { /* the first few queued don't have anything allocated, so don't free */ if(buffer->frameBuffer) { fio_free(buffer->frameBuffer); } buffer->frameSize = frame_size; buffer->frameBuffer = fio_malloc(buffer->frameSize * 16 / 14 + 0x1000); buffer->frameBufferAligned = (void *) (((uint32_t)buffer->frameBuffer + 0x1000) & ~0xFFF); } if(!buffer->frameBuffer) { bmp_printf(FONT_MED, 30, 400, "allocation failed"); beep(); msleep(1000); break; } int32_t read_size = MIN(frame_size, vidf_block.blockSize - (sizeof(mlv_vidf_hdr_t) + vidf_block.frameSpace)); void *read_buffer = buffer->frameBufferAligned; /* when we have LJ92 compressed video, read into a different buffer, decompressor will store it in the other one */ if(main_header.videoClass & MLV_VIDEO_CLASS_FLAG_LJ92) { /* none allocated yet? do so */ if(!mlv_play_decomp_buf) { int mem_size = (buffer->frameSize + 0x1000) & ~0xFFF; /* keep in mind the address must be aligned, alloc a few k more */ mlv_play_decomp_buf = fio_malloc(mem_size + 0x1000); if(!mlv_play_decomp_buf) { bmp_printf(FONT_MED, 20, 100, "failed to alloc mlv_play_decomp_buf"); msleep(10000); return; } mlv_play_decomp_suite = CreateMemorySuite(mlv_play_decomp_buf, mem_size, 0); if(!mlv_play_decomp_suite) { bmp_printf(FONT_MED, 20, 100, "(failed inSuite)"); msleep(10000); return; } } /* reading into this one */ read_buffer = mlv_play_decomp_buf; } /* safety check to make sure the format matches, but allow the saved block to be larger (some dummy data at the end of frame is allowed) */ if((uint32_t)read_size > buffer->frameSize) { bmp_printf(FONT_MED, 30, 400, "ERROR 0x%X 0x%X 0x%X 0x%X 0x%X", read_size, frame_size, buffer->frameSize, vidf_block.frameSpace, vidf_block.blockSize); beep(); msleep(10000); break; } /* skip frame space */ FIO_SeekSkipFile(in_file, position + sizeof(mlv_vidf_hdr_t) + vidf_block.frameSpace, SEEK_SET); /* finally read the raw data */ if(FIO_ReadFile(in_file, read_buffer, read_size) != (int32_t)read_size) { bmp_printf(FONT_MED, 30, 190, "File ends prematurely during VIDF raw data"); beep(); msleep(1000); break; } /* update dimensions */ buffer->xRes = rawi_block.xRes; buffer->yRes = rawi_block.yRes; buffer->bitDepth = rawi_block.raw_info.bits_per_pixel; buffer->blackLevel = rawi_block.raw_info.black_level; buffer->whiteLevel = rawi_block.raw_info.white_level; if(main_header.videoClass & MLV_VIDEO_CLASS_FLAG_LJ92) { int output_bpp = raw_twk_available() ? 16 : 14; int r = lossless_decompress_raw( mlv_play_decomp_suite, buffer->frameBufferAligned, rawi_block.xRes, rawi_block.yRes, output_bpp ); if (r == -1) { bmp_printf(FONT_MED, 20, 300, "(no decompression on this model)"); } /* when we have lossless data, we decompressed it already to 16 bpp for raw_twk */ if (output_bpp == 16) { /* also raise black and white levels */ buffer->blackLevel = rawi_block.raw_info.black_level << (16 - rawi_block.raw_info.bits_per_pixel); buffer->whiteLevel = rawi_block.raw_info.white_level << (16 - rawi_block.raw_info.bits_per_pixel); buffer->bitDepth = 16; } } if((main_header.videoClass & 0x0F) != MLV_VIDEO_CLASS_RAW) { bmp_printf(FONT_MED, 20, 300, "(format not supported)"); } else { /* fill strings to display */ snprintf(buffer->messages.topLeft, SCREEN_MSG_LEN, ""); snprintf(buffer->messages.topRight, SCREEN_MSG_LEN, ""); if(lens_block.timestamp) { char *focusMode; if((lens_block.autofocusMode & 0x0F) != AF_MODE_MANUAL_FOCUS) { focusMode = "AF"; } else { focusMode = "MF"; } snprintf(buffer->messages.topRight, SCREEN_MSG_LEN, "%s, %dmm, %s, %s", lens_block.lensName, lens_block.focalLength, lens_block.stabilizerMode?"IS":"no IS", focusMode); } if(rtci_block.timestamp) { snprintf(buffer->messages.topLeft, SCREEN_MSG_LEN, "%02d.%02d.%04d %02d:%02d:%02d", rtci_block.tm_mday, rtci_block.tm_mon + 1, 1900 + rtci_block.tm_year, rtci_block.tm_hour, rtci_block.tm_min, rtci_block.tm_sec); } snprintf(buffer->messages.botLeft, SCREEN_MSG_LEN, "%s: %dx%d %dbpp%s", filename, rawi_block.xRes, rawi_block.yRes, rawi_block.raw_info.bits_per_pixel, (main_header.videoClass & MLV_VIDEO_CLASS_FLAG_LJ92)?" LJ92":""); snprintf(buffer->messages.botRight, SCREEN_MSG_LEN, "%d/%d", vidf_block.frameNumber + 1, frame_count); if (mlv_play_exact_fps) { if (!fps_timer_started) { mlv_play_start_fps_timer(main_header.sourceFpsNom, main_header.sourceFpsDenom); fps_timer_started = 1; } if (fps_timer_started) { /* wait till it is time to render */ uint32_t temp = 0; while(msg_queue_receive(mlv_play_queue_fps, &temp, 50)) { if(mlv_play_should_stop()) { break; } } } } /* queue frame buffer for rendering, retry if queue is full (happens in pause or for slow rendering) */ while(1) { if(msg_queue_post(mlv_play_queue_render, (uint32_t) buffer)) { /* in case we could not pass the buffer and stop is requested, gibe back buffer here also */ if(mlv_play_should_stop()) { msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer); } else { msleep(10); } } else { break; } } } } block_xref_pos++; } if(fps_timer_started) { mlv_play_stop_fps_timer(); } free(block_xref); /* free decompression stuff if needed */ if(mlv_play_decomp_buf) { free(mlv_play_decomp_buf); mlv_play_decomp_buf = NULL; } if(mlv_play_decomp_suite) { DeleteMemorySuite(mlv_play_decomp_suite); mlv_play_decomp_suite = NULL; } } static void mlv_play_raw(char *filename, FILE **chunk_files, uint32_t chunk_count) { uint32_t fps_timer_started = 0; uint32_t chunk_num = 0; /* read footer information and update global variables, will seek automatically */ if(chunk_count < 1 || !mlv_play_is_raw(chunk_files[chunk_count-1])) { bmp_printf(FONT_LARGE, 30, 100, "Invalid RAW:", filename); bmp_printf(FONT_MED, 40, 100 + font_large.height + 1, filename); return; } mlv_play_read_footer(chunk_files[chunk_count-1]); /* update OSD */ msg_queue_post(mlv_play_queue_osd, (uint32_t) 0); int i = 0; while(1) { /* after playback is finished, return to beginning and put into pause */ if (i == frame_count - 1) { /* reset counters */ i = 0; chunk_num = 0; /* reset file pointers */ for(uint32_t j = 0; j < chunk_count; j++) { FIO_SeekSkipFile(chunk_files[j], 0, SEEK_SET); } /* miscellaneous cleanup */ mlv_play_flush_queue(mlv_play_queue_fps); mlv_play_info = (mlv_play_info == 1 ? 2 : mlv_play_info); mlv_play_paused = 1; } /* check if we are too slow */ if(mlv_play_exact_fps) { uint32_t frames_to_skip = 0; msg_queue_count(mlv_play_queue_fps, &frames_to_skip); /* skip frame if we should play at exact fps and we already should be one frame farther */ if(frames_to_skip > 0) { uint32_t temp = 0; msg_queue_receive(mlv_play_queue_fps, &temp, 50); int64_t here = FIO_SeekSkipFile(chunk_files[chunk_num], 0, SEEK_CUR); int64_t end = FIO_SeekSkipFile(chunk_files[chunk_num], 0, SEEK_END); int64_t next = here + frame_size; if (next >= end) { chunk_num++; if (chunk_num < chunk_count) { FIO_SeekSkipFile(chunk_files[chunk_num], next - end, SEEK_SET); } else { break; } } else { FIO_SeekSkipFile(chunk_files[chunk_num], next, SEEK_SET); } mlv_play_frames_skipped++; i++; continue; } } else { /* if not, just keep the queue clean */ mlv_play_flush_queue(mlv_play_queue_fps); } /* there are various reasons why this read/play task should get stopped */ if(mlv_play_should_stop()) { break; } frame_buf_t *buffer = NULL; while(mlv_play_paused && !mlv_play_should_stop()) { msleep(100); } /* now get a buffer from the queue */ while (msg_queue_receive(mlv_play_queue_empty, &buffer, 100) && !mlv_play_should_stop()); if (mlv_play_should_stop()) { break; } /* check if the queued buffer has the correct size */ if((int32_t)buffer->frameSize != frame_size) { /* the first few queued dont have anything allocated, so dont free */ if(buffer->frameBuffer) { free(buffer->frameBuffer); } buffer->frameSize = frame_size; buffer->frameBuffer = fio_malloc(buffer->frameSize); } if(!buffer->frameBuffer) { bmp_printf(FONT_MED, 30, 400, "allocation failed"); beep(); msleep(1000); break; } int32_t r = FIO_ReadFile(chunk_files[chunk_num], buffer->frameBuffer, frame_size); /* reading failed */ if(r < 0) { bmp_printf(FONT_MED, 30, 400, "reading failed"); beep(); msleep(1000); break; } /* end of chunk reached */ if (r != frame_size) { /* no more chunks to play */ if((chunk_num + 1) >= chunk_count) { break; } /* read remaining block from next chunk */ chunk_num++; int remain = frame_size - r; r = FIO_ReadFile(chunk_files[chunk_num], (void*)((uint32_t)buffer->frameBuffer + r), remain); /* it doesnt have enough data. thats enough errors... */ if(r != remain) { bmp_printf(FONT_MED, 30, 400, "reading failed again"); beep(); msleep(1000); break; } } /* fill strings to display */ snprintf(buffer->messages.topLeft, SCREEN_MSG_LEN, ""); snprintf(buffer->messages.topRight, SCREEN_MSG_LEN, ""); snprintf(buffer->messages.botLeft, SCREEN_MSG_LEN, "%s: %dx%d", filename, res_x, res_y); snprintf(buffer->messages.botRight, SCREEN_MSG_LEN, "%d/%d", i+1, frame_count-1); /* update dimensions */ buffer->xRes = res_x; buffer->yRes = res_y; buffer->bitDepth = 14; buffer->blackLevel = raw_info.black_level; buffer->whiteLevel = raw_info.white_level; if (mlv_play_exact_fps) { if (!fps_timer_started) { mlv_play_start_fps_timer(fps1000, 1000); fps_timer_started = 1; } if (fps_timer_started) { /* wait till it is time to render */ uint32_t temp = 0; while(msg_queue_receive(mlv_play_queue_fps, &temp, 50)) { if(mlv_play_should_stop()) { break; } } } } /* requeue frame buffer for rendering */ msg_queue_post(mlv_play_queue_render, (uint32_t) buffer); i++; } if(fps_timer_started) { mlv_play_stop_fps_timer(); } } static void mlv_play(char *filename, FILE **chunk_files, uint32_t chunk_count) { mlv_play_stopfile = 0; if(mlv_play_is_mlv(chunk_files[0])) { mlv_play_mlv(filename, chunk_files, chunk_count); } else { mlv_play_raw(filename, chunk_files, chunk_count); } } static void mlv_playlist_build_path(char *directory) { struct fio_file file; struct fio_dirent * dirent = NULL; dirent = FIO_FindFirstEx(directory, &file); if(IS_ERROR(dirent)) { return; } char *full_path = malloc(MAX_PATH); do { if(file.name[0] == '.') { continue; } snprintf(full_path, MAX_PATH, "%s%s", directory, file.name); /* do not recurse here, but enqueue next directory */ if(file.mode & ATTR_DIRECTORY) { strcat(full_path, "/"); msg_queue_post(mlv_playlist_scan_queue, (uint32_t) strdup(full_path)); } else { char *suffix = &file.name[strlen(file.name) - 3]; if(!strcmp("RAW", suffix) || !strcmp("MLV", suffix)) { playlist_entry_t *entry = malloc(sizeof(playlist_entry_t)); strncpy(entry->fullPath, full_path, sizeof(entry->fullPath)); entry->fileSize = file.size; entry->timestamp = file.timestamp; /* update playlist */ msg_queue_post(mlv_playlist_queue, (uint32_t) entry); } } } while(FIO_FindNextEx(dirent, &file) == 0); FIO_FindClose(dirent); free(full_path); } static void mlv_playlist_free() { /* clear the playlist */ mlv_playlist_entries = 0; if(mlv_playlist) { free(mlv_playlist); } mlv_playlist = NULL; /* clear items in queues */ void *entry = NULL; while(!msg_queue_receive(mlv_playlist_queue, &entry, 50)) { free(entry); } while(!msg_queue_receive(mlv_playlist_scan_queue, &entry, 50)) { free(entry); } } static void mlv_playlist_build(uint32_t priv) { playlist_entry_t *entry = NULL; /* clear the playlist */ mlv_playlist_free(); /* set up initial directories to scan. try to not recurse, but use scan and result queues */ msg_queue_post(mlv_playlist_scan_queue, (uint32_t) strdup("A:/")); msg_queue_post(mlv_playlist_scan_queue, (uint32_t) strdup("B:/")); char *directory = NULL; while(!msg_queue_receive(mlv_playlist_scan_queue, &directory, 50)) { mlv_playlist_build_path(directory); free(directory); } /* pre-allocate the number of enqueued playlist items */ uint32_t msg_count = 0; msg_queue_count(mlv_playlist_queue, &msg_count); playlist_entry_t *playlist = malloc(msg_count * sizeof(playlist_entry_t)); /* add all items */ while(!msg_queue_receive(mlv_playlist_queue, &entry, 50)) { playlist[mlv_playlist_entries++] = *entry; free(entry); } mlv_playlist = playlist; } static int32_t mlv_playlist_find(playlist_entry_t current) { for(uint32_t pos = 0; pos < mlv_playlist_entries; pos++) { if(!strcmp(current.fullPath, mlv_playlist[pos].fullPath)) { return pos; } } return -1; } static void mlv_playlist_delete(playlist_entry_t current) { int32_t pos = mlv_playlist_find(current); if(pos >= 0) { uint32_t remaining = mlv_playlist_entries - pos - 1; memcpy(&mlv_playlist[pos], &mlv_playlist[pos+1], remaining * sizeof(playlist_entry_t)); mlv_playlist_entries--; } } static playlist_entry_t mlv_playlist_next(playlist_entry_t current) { playlist_entry_t ret; strcpy(ret.fullPath, ""); int32_t pos = mlv_playlist_find(current); if(pos >= 0 && (uint32_t)(pos + 1) < mlv_playlist_entries) { ret = mlv_playlist[pos + 1]; } return ret; } static playlist_entry_t mlv_playlist_prev(playlist_entry_t current) { playlist_entry_t ret; strcpy(ret.fullPath, ""); int32_t pos = mlv_playlist_find(current); if(pos > 0) { ret = mlv_playlist[pos - 1]; } return ret; } static void mlv_play_leave_playback() { mlv_play_render_abort = 1; while(mlv_play_rendering) { info_led_blink(1, 100, 100); } /* clean up buffers - free memories and all buffers */ while(1) { frame_buf_t *buffer = NULL; if(msg_queue_receive(mlv_play_queue_render, &buffer, 50) && msg_queue_receive(mlv_play_queue_empty, &buffer, 50)) { break; } /* free allocated buffers */ if(buffer->frameBuffer) { fio_free(buffer->frameBuffer); } free(buffer); } vram_clear_lv(); exit_play_qr_mode(); } static void mlv_play_enter_playback() { /* prepare display */ NotifyBoxHide(); enter_play_mode(); /* render task is slave and controlled via these variables */ mlv_play_render_abort = 0; mlv_play_rendering = 1; task_create("mlv_play_render", 0x1d, 0x4000, mlv_play_render_task, NULL); task_create("mlv_play_osd_task", 0x15, 0x4000, mlv_play_osd_task, 0); mlv_play_zoom = 0; mlv_play_zoom_x_pct = 0; mlv_play_zoom_y_pct = 0; raw_twk_set_zoom(mlv_play_zoom, mlv_play_zoom_x_pct, mlv_play_zoom_y_pct); /* queue a few buffers that are not allocated yet */ for(int num = 0; num < 3; num++) { frame_buf_t *buffer = malloc(sizeof(frame_buf_t)); if (buffer) { buffer->frameSize = 0; buffer->frameBuffer = NULL; msg_queue_post(mlv_play_queue_empty, (uint32_t) buffer); } } /* clear anything on screen */ mlv_play_clear_screen(); } static struct semaphore * mlv_play_sem = 0; static void mlv_play_task(void *priv) { /* we will later restore that value */ int old_black_level = raw_info.black_level; int old_white_level = raw_info.white_level; int old_bits_per_pixel = raw_info.bits_per_pixel; if (take_semaphore(mlv_play_sem, 100)) { NotifyBox(2000, "mlv_play already running"); beep(); return; } FILE **chunk_files = NULL; uint32_t chunk_count = 0; char *filename = (char *)priv; /* playback at last recorded file */ if(!filename) { if(mlv_movie_filename && strlen(mlv_movie_filename)) { filename = mlv_movie_filename; } else if(raw_movie_filename && strlen(raw_movie_filename)) { filename = raw_movie_filename; } } /* get into Canon's PLAY mode */ mlv_play_enter_playback(); /* create playlist */ mlv_play_show_dlg(0, "Building playlist..."); mlv_playlist_build(0); mlv_play_show_dlg(0, 0); if (filename) { uint32_t size = 0; /* is this file still available? if not, show dialog and return */ if(FIO_GetFileSize(filename, &size)) { char msg[50]; snprintf(msg, sizeof(msg), "%s\nnot found", strrchr(filename, '/')+1); mlv_play_show_dlg(4000, msg); /* try to play the first file instead */ filename = 0; } else { strcpy(mlv_play_current_filename, filename); } } /* if called with NULL, or the requested file was not found, play last file found when building playlist */ if(!filename) { if(mlv_playlist_entries <= 0) { mlv_play_show_dlg(2000, "No videos found"); goto cleanup; } strncpy(mlv_play_current_filename, mlv_playlist[mlv_playlist_entries-1].fullPath, sizeof(mlv_play_current_filename)); } do { if(!strlen(mlv_play_current_filename)) { goto cleanup; } strcpy(mlv_play_next_filename, ""); /* open all chunks of that movie file */ chunk_files = mlv_play_load_chunks(mlv_play_current_filename, &chunk_count); if(!chunk_files || !chunk_count) { bmp_printf(FONT_MED, 30, 190, "failed to load chunks"); beep(); msleep(1000); goto cleanup; } /* clear anything on screen */ mlv_play_clear_screen(); /* ok now start real playback routines */ mlv_play(mlv_play_current_filename, chunk_files, chunk_count); mlv_play_close_chunks(chunk_files, chunk_count); /* all files closed, anything to delete? */ mlv_play_delete_if_requested(); /* playback finished. wait until... hmm.. something happens */ while(!strlen(mlv_play_next_filename) && !mlv_play_should_stop()) { msleep(100); } /* anything more to delete? */ mlv_play_delete_if_requested(); strncpy(mlv_play_current_filename, mlv_play_next_filename, sizeof(mlv_play_next_filename)); /* stop file request was handled, if there was any */ mlv_play_stopfile = 0; } while(!mlv_play_should_stop()); cleanup: mlv_playlist_free(); mlv_play_leave_playback(); mlv_play_delete_requested = 0; mlv_play_osd_delete_selected = 0; give_semaphore(mlv_play_sem); /* undo black and white level change */ raw_info.black_level = old_black_level; raw_info.white_level = old_white_level; raw_info.bits_per_pixel = old_bits_per_pixel; } void mlv_play_file(char *filename) { gui_stop_menu(); task_create("mlv_play_task", 0x1e, 0x4000, mlv_play_task, (void*)filename); } FILETYPE_HANDLER(mlv_play_filehandler) { /* there is no header and clean interface yet */ switch(cmd) { case FILEMAN_CMD_INFO: { FILE* f = FIO_OpenFile( filename, O_RDONLY | O_SYNC ); if (!f) { return 0; } if(mlv_play_is_mlv(f)) { strcpy(data, "A MLV v2.0 Video"); } else if(mlv_play_is_raw(f)) { strcpy(data, "A RAW Video"); } else { strcpy(data, "Invalid RAW video format"); } return 1; } case FILEMAN_CMD_VIEW_OUTSIDE_MENU: { mlv_play_file(filename); return 1; } } return 0; /* command not handled */ } static unsigned int mlv_play_keypress_cbr(unsigned int key) { if (mlv_play_rendering) { switch(key) { case MODULE_KEY_UNPRESS_SET: { return 0; } case MODULE_KEY_PRESS_SET: case MODULE_KEY_WHEEL_UP: case MODULE_KEY_WHEEL_DOWN: case MODULE_KEY_WHEEL_LEFT: case MODULE_KEY_WHEEL_RIGHT: case MODULE_KEY_JOY_CENTER: case MODULE_KEY_PRESS_UP: case MODULE_KEY_PRESS_UP_RIGHT: case MODULE_KEY_PRESS_UP_LEFT: case MODULE_KEY_PRESS_RIGHT: case MODULE_KEY_PRESS_LEFT: case MODULE_KEY_PRESS_DOWN_RIGHT: case MODULE_KEY_PRESS_DOWN_LEFT: case MODULE_KEY_PRESS_DOWN: case MODULE_KEY_UNPRESS_UDLR: case MODULE_KEY_INFO: case MODULE_KEY_Q: case MODULE_KEY_PICSTYLE: case MODULE_KEY_PRESS_DP: case MODULE_KEY_UNPRESS_DP: case MODULE_KEY_RATE: case MODULE_KEY_TRASH: case MODULE_KEY_PLAY: case MODULE_KEY_MENU: case MODULE_KEY_PRESS_ZOOMIN: { msg_queue_post(mlv_play_queue_osd, (uint32_t) key); return 0; } /* ignore zero keycodes. pass through or not? */ case 0: { return 0; } /* any other key aborts playback */ default: { /* int loops = 0; while(loops < 50) { bmp_printf(FONT_MED, 30, 400, "key 0x%02X not handled, exiting", key); loops++; } */ mlv_play_render_abort = 1; return 0; } } } else if(raw_video_enabled || mlv_video_enabled) { if (!is_movie_mode()) return 1; if (!liveview_display_idle()) return 1; if (RECORDING) return 1; switch(key) { case MODULE_KEY_PLAY: { task_create("mlv_play_task", 0x1e, 0x4000, mlv_play_task, NULL); return 0; } } } return 1; } static unsigned int mlv_play_init() { /* setup log file */ mlv_play_trace_ctx = trace_start("mlv_play", "mlv_play.log"); trace_set_flushrate(mlv_play_trace_ctx, 500); trace_format(mlv_play_trace_ctx, TRACE_FMT_TIME_REL | TRACE_FMT_COMMENT, ' '); /* setup queues for frame buffers */ mlv_play_queue_empty = (struct msg_queue *) msg_queue_create("mlv_play_queue_empty", 10); mlv_play_queue_render = (struct msg_queue *) msg_queue_create("mlv_play_queue_render", 10); mlv_play_queue_osd = (struct msg_queue *) msg_queue_create("mlv_play_queue_osd", 10); mlv_play_queue_fps = (struct msg_queue *) msg_queue_create("mlv_play_queue_fps", 100); mlv_playlist_queue = (struct msg_queue *) msg_queue_create("mlv_playlist_queue", 500); mlv_playlist_scan_queue = (struct msg_queue *) msg_queue_create("mlv_playlist_scan_queue", 500); fileman_register_type("RAW", "RAW Video", mlv_play_filehandler); fileman_register_type("MLV", "MLV Video", mlv_play_filehandler); mlv_play_sem = create_named_semaphore("mlv_play_running", 1); return 0; } static unsigned int mlv_play_deinit() { return 0; } MODULE_INFO_START() MODULE_INIT(mlv_play_init) MODULE_DEINIT(mlv_play_deinit) MODULE_INFO_END() MODULE_CBRS_START() MODULE_CBR(CBR_KEYPRESS, mlv_play_keypress_cbr, 0) MODULE_CBRS_END() MODULE_PROPHANDLERS_START() MODULE_PROPHANDLERS_END() MODULE_CONFIGS_START() MODULE_CONFIG(mlv_play_quality) MODULE_CONFIG(mlv_play_exact_fps) MODULE_CONFIGS_END()