mlv_rec.c
/**
* RAW recording. Similar to lv_rec, with some different internals:
*
* - buffering strategy:
* - group the frames in contiguous chunks, up to 32MB, to maximize writing speed
* (speed profile depends on buffer size: http://www.magiclantern.fm/forum/index.php?topic=5471 )
* - always write if there's something to write, even if that means using a small buffer
* (this minimizes idle time for the writing task, keeps memory free in the startup phase,
* and has no impact on the sustained write speeds
* - always choose the largest unused chunk => this maximizes the sustained writing speed
* (small chunks will only be used in extreme situations, to squeeze the last few frames)
* - use any memory chunks that can contain at least one video frame
* (they will only be used when recording is about to stop, so no negative impact in sustained write speed)
*
* - edmac_copy_rectangle: we can crop the image and trim the black borders!
* - edmac operation done outside the LV task (in background, synchronized)
* - on buffer overflow, it stops or skips frames (user-selected)
* - using generic raw routines, no hardcoded stuff (should be easier to port)
* - only for RAW in a single file (do one thing and do it well)
* - goal #1: 1920x1080 on 1000x cards (achieved and exceeded, reports say 1920x1280 continuous!)
* - goal #2: maximize number of frames for any given resolution + buffer + card speed configuration
* (see buffering strategy; I believe it's close to optimal, though I have no idea how to write a mathematical proof for it)
*
* Usage:
* - enable modules in Makefile.user (CONFIG_MODULES = y, CONFIG_TCC = y, CONFIG_PICOC = n, CONFIG_CONSOLE = y)
* - run "make" from modules/raw_rec to compile this module and the DNG converter
* - run "make install" from platform dir to copy the modules on the card
* - from Module menu: Load modules now
* - look in Movie menu
*/
/*
* 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.
*/
//#define CONFIG_CONSOLE
//#define TRACE_DISABLED
#include <module.h>
#include <dryos.h>
#include <property.h>
#include <bmp.h>
#include <fps.h>
#include <zebra.h>
#include <beep.h>
#include <menu.h>
#include <config.h>
#include <math.h>
#include <cropmarks.h>
#include <screenshot.h>
#include <util.h>
#include <edmac.h>
#include <edmac-memcpy.h>
#include <cache_hacks.h>
#include <string.h>
#include <shoot.h>
#include <powersave.h>
#include "../lv_rec/lv_rec.h"
#include "../file_man/file_man.h"
#include "../ime_base/ime_base.h"
#include "../trace/trace.h"
#include "mlv.h"
#include "mlv_rec.h"
/* an alternative tracing method that embeds the logs into the MLV file itself */
/* looks like it might cause pink frames - http://www.magiclantern.fm/forum/index.php?topic=5473.msg165356#msg165356 */
#undef EMBEDDED_LOGGING
#if defined(EMBEDDED_LOGGING) && !defined(TRACE_DISABLED)
#define trace_write mlv_debg_printf
#define trace_available() 1
#define trace_start(name, file_name) (uint32_t)(file_name)
#define trace_stop(trace, wait) (void)0
#define trace_format(context, format, separator) (void)0
#define trace_set_flushrate(context, timeout) (void)0
#endif
/* camera-specific tricks */
/* todo: maybe add generic functions like is_digic_v, is_5d2 or stuff like that? */
static uint32_t cam_eos_m = 0;
static uint32_t cam_5d2 = 0;
static uint32_t cam_50d = 0;
static uint32_t cam_5d3 = 0;
static uint32_t cam_500d = 0;
static uint32_t cam_550d = 0;
static uint32_t cam_6d = 0;
static uint32_t cam_600d = 0;
static uint32_t cam_650d = 0;
static uint32_t cam_7d = 0;
static uint32_t cam_700d = 0;
static uint32_t cam_60d = 0;
static uint32_t raw_rec_edmac_align = 0x01000;
static uint32_t raw_rec_write_align = 0x01000;
static uint32_t mlv_rec_dma_active = 0;
static uint32_t mlv_writer_threads = 2;
static uint32_t mlv_max_filesize = 0xFFFFFFFF;
static uint32_t abort_test = 0;
uint32_t raw_rec_trace_ctx = TRACE_ERROR;
/**
* resolution (in pixels) should be multiple of 16 horizontally (see http://www.magiclantern.fm/forum/index.php?topic=5839.0)
* furthermore, resolution (in bytes) should be multiple of 8 in order to use the fastest EDMAC flags ( http://magiclantern.wikia.com/wiki/Register_Map#EDMAC ),
* which copy 16 bytes at a time, but only check for overflows every 8 bytes (can be verified experimentally)
* => if my math is not broken, this traslates to resolution being multiple of 32 pixels horizontally
* use roughly 10% increments
**/
static uint32_t resolution_presets_x[] = { 640, 960, 1280, 1600, 1920, 2240, 2560, 2880, 3200, 3520 };
#define RESOLUTION_CHOICES_X CHOICES( "640","960","1280","1600","1920","2240","2560","2880","3200","3520")
static uint32_t aspect_ratio_presets_num[] = { 5, 4, 3, 8, 25, 239, 235, 22, 2, 185, 16, 5, 3, 4, 12, 1175, 1, 1 };
static uint32_t aspect_ratio_presets_den[] = { 1, 1, 1, 3, 10, 100, 100, 10, 1, 100, 9, 3, 2, 3, 10, 1000, 1, 2 };
static const char * aspect_ratio_choices[] = {"5:1","4:1","3:1","2.67:1","2.50:1","2.39:1","2.35:1","2.20:1","2:1","1.85:1", "16:9","5:3","3:2","4:3","1.2:1","1.175:1","1:1","1:2"};
/* config variables */
CONFIG_INT("mlv.video.enabled", mlv_video_enabled, 0);
static CONFIG_INT("mlv.buffer_fill_method", buffer_fill_method, 4);
static CONFIG_INT("mlv.fast_card_buffers", fast_card_buffers, 1);
static CONFIG_INT("mlv.tracing", enable_tracing, 0);
static CONFIG_INT("mlv.display_rec_info", display_rec_info, 1);
static CONFIG_INT("mlv.show_graph", show_graph, 0);
static CONFIG_INT("mlv.res.x", resolution_index_x, 4);
static CONFIG_INT("mlv.res.x.fine", res_x_fine, 0);
static CONFIG_INT("mlv.aspect_ratio", aspect_ratio_index, 10);
static CONFIG_INT("mlv.write_speed", measured_write_speed, 0);
static CONFIG_INT("mlv.skip_frames", allow_frame_skip, 0);
static CONFIG_INT("mlv.card_spanning", card_spanning, 0);
static CONFIG_INT("mlv.delay", start_delay_idx, 0);
static CONFIG_INT("mlv.killgd", kill_gd, 1);
static CONFIG_INT("mlv.large_file_support", large_file_support, 0);
static CONFIG_INT("mlv.create_dummy", create_dummy, 1);
static CONFIG_INT("mlv.dolly", dolly_mode, 0);
static CONFIG_INT("mlv.preview", preview_mode, 0);
static CONFIG_INT("mlv.warm_up", warm_up, 0);
static CONFIG_INT("mlv.use_srm_memory", use_srm_memory, 1);
static CONFIG_INT("mlv.small_hacks", small_hacks, 1);
static CONFIG_INT("mlv.create_dirs", create_dirs, 0);
static int start_delay = 0;
/* state variables */
static int32_t res_x = 0;
static int32_t res_y = 0;
static int32_t max_res_x = 0;
static int32_t max_res_y = 0;
static int32_t sensor_res_x = 0;
static float squeeze_factor = 0;
static int32_t frame_size = 0;
static int32_t skip_x = 0;
static int32_t skip_y = 0;
static int32_t frame_offset_x = 0;
static int32_t frame_offset_y = 0;
static int32_t frame_offset_delta_x = 0;
static int32_t frame_offset_delta_y = 0;
static int32_t raw_recording_state = RAW_IDLE;
static int32_t raw_previewing = 0;
static uint32_t mlv_metadata = MLV_METADATA_ALL;
/* if these get set, on the next frame the according blocks get queued */
static int32_t mlv_update_lens = 0;
static int32_t mlv_update_styl = 0;
static int32_t mlv_update_wbal = 0;
static mlv_expo_hdr_t last_expo_hdr;
static mlv_lens_hdr_t last_lens_hdr;
static mlv_wbal_hdr_t last_wbal_hdr;
static mlv_styl_hdr_t last_styl_hdr;
/* for debugging */
static uint64_t mlv_rec_dma_start = 0;
static uint64_t mlv_rec_dma_end = 0;
static uint64_t mlv_rec_dma_duration = 0;
static struct memSuite * shoot_mem_suite = 0; /* memory suites for our buffers */
static struct memSuite * srm_mem_suite = 0;
static void * fullsize_buffers[2]; /* original image, before cropping, double-buffered */
static int32_t fullsize_buffer_pos = 0; /* which of the full size buffers (double buffering) is currently in use */
static struct frame_slot slots[512]; /* frame slots */
static struct frame_slot_group slot_groups[512];
static int32_t slot_count = 0; /* how many frame slots we have */
static int32_t slot_group_count = 0;
static int32_t capture_slot = -1; /* in what slot are we capturing now (index) */
static volatile int32_t force_new_buffer = 0; /* if some other task decides it's better to search for a new buffer */
static int32_t frame_count = 0; /* how many frames we have processed */
static int32_t frame_skips = 0; /* how many frames were dropped/skipped */
char* mlv_movie_filename = NULL; /* file name for current (or last) movie */
static uint32_t mlv_rec_threads;
/* per-thread data */
static char chunk_filename[MAX_WRITER_THREADS][MAX_PATH]; /* file name for current movie chunk */
static uint32_t written[MAX_WRITER_THREADS]; /* how many KB we have written in this movie */
static uint32_t frames_written[MAX_WRITER_THREADS]; /* how many frames we have written in this movie */
static int32_t writing_time[MAX_WRITER_THREADS]; /* time spent by raw_video_rec_task in FIO_WriteFile calls */
static int32_t idle_time[MAX_WRITER_THREADS]; /* time spent by raw_video_rec_task doing something else */
static FILE *mlv_handles[MAX_WRITER_THREADS];
static struct msg_queue *mlv_writer_queues[MAX_WRITER_THREADS];
static uint32_t writer_job_count[MAX_WRITER_THREADS];
static int32_t current_write_speed[MAX_WRITER_THREADS];
/* mlv information */
struct msg_queue *mlv_block_queue = NULL;
struct msg_queue *mlv_mgr_queue = NULL;
struct msg_queue *mlv_mgr_queue_close = NULL;
struct msg_queue *mlv_job_alloc_queue = NULL;
static uint64_t mlv_start_timestamp = 0;
static mlv_file_hdr_t mlv_file_hdr;
/* info block data */
static char raw_tag_str[1024];
static char raw_tag_str_tmp[1024];
static int32_t raw_tag_take = 0;
static int32_t mlv_file_count = 0;
static volatile int32_t frame_countdown = 0; /* for waiting X frames */
#if defined(EMBEDDED_LOGGING)
/* START: helper code for logging into MLV files */
static uint8_t *mlv_debg_buffer = NULL;
static uint32_t mlv_debg_size = 0;
static uint32_t mlv_debg_used = 0;
static void mlv_debg_addbin(void *data, uint32_t length)
{
if(!mlv_debg_buffer)
{
mlv_debg_size = 8192;
mlv_debg_buffer = malloc(mlv_debg_size);
}
if(mlv_debg_used + length > mlv_debg_size)
{
mlv_debg_size += 8192 + length;
mlv_debg_buffer = realloc(mlv_debg_buffer, mlv_debg_size);
}
memcpy(&mlv_debg_buffer[mlv_debg_used], data, length);
mlv_debg_used += length;
mlv_debg_buffer[mlv_debg_used] = '\000';
}
static void mlv_rec_requeue_debg(mlv_debg_hdr_t *hdr)
{
uint8_t *str_pos = (uint8_t *)((uint32_t)hdr + sizeof(mlv_debg_hdr_t));
uint32_t old_int = cli();
mlv_debg_addbin(str_pos, hdr->length);
sei(old_int);
free(hdr);
}
static mlv_debg_hdr_t *mlv_rec_queue_debg()
{
if(!mlv_debg_used)
{
return NULL;
}
uint32_t old_int = cli();
/* locked because mlv_debg_used is read and altered here */
uint32_t size = mlv_debg_used + sizeof(mlv_debg_hdr_t);
/* pad size to 32 bits */
if(size % 4)
{
size += 4;
size &= ~3;
}
mlv_debg_hdr_t *hdr = malloc(size);
uint8_t *str_pos = (uint8_t *)((uint32_t)hdr + sizeof(mlv_debg_hdr_t));
memcpy(str_pos, mlv_debg_buffer, mlv_debg_used);
/* set block size and payload size (they may differ due to padding) */
hdr->blockSize = size;
hdr->length = mlv_debg_used;
hdr->type = 0;
mlv_debg_used = 0;
/* end of locking area */
sei(old_int);
mlv_set_type((mlv_hdr_t *)hdr, "DEBG");
mlv_set_timestamp((mlv_hdr_t *)hdr, mlv_start_timestamp);
return hdr;
}
static void mlv_debg_printf(uint32_t ctx, const char* format, ... )
{
va_list args;
va_start( args, format );
uint32_t size = strlen(format) + 128;
char *fmt_str = malloc(size);
vsnprintf(fmt_str, size, format, args);
/* here we can check if logging is enabled and if not, skip further logging */
if(enable_tracing)
{
uint32_t old_int = cli();
mlv_debg_addbin(fmt_str, strlen(fmt_str));
mlv_debg_addbin("\n", 1);
sei(old_int);
}
free(fmt_str);
va_end( args );
}
/* END: helper code for logging into MLV files */
#endif
/* helpers for reserving disc space */
static uint32_t mlv_rec_alloc_dummy(char *filename, uint32_t size)
{
/* add a megabyte extra */
size += 1024 * 1024;
uint32_t file_size = 0;
if(!FIO_GetFileSize(filename, &file_size))
{
/* file already exists and reserves enough room */
if(file_size >= size)
{
return 1;
}
/* not enough room, delete and rewrite */
FIO_RemoveFile(filename);
}
FILE *dummy_file = FIO_CreateFile(filename);
if(!dummy_file)
{
trace_write(raw_rec_trace_ctx, "mlv_rec_alloc_dummy: Failed to create dummy file '%s'", filename);
return 0;
}
bmp_printf(FONT_MED, 30, 90, "Allocating %d MiB backup...", size / 1024 / 1024);
trace_write(raw_rec_trace_ctx, "mlv_rec_alloc_dummy: Allocating %d MiB backup: '%s'", size / 1024 / 1024, filename);
FIO_WriteFile(dummy_file, (void*)0x40000000, size);
uint32_t new_pos = FIO_SeekSkipFile(dummy_file, 0, SEEK_CUR);
FIO_CloseFile(dummy_file);
if(new_pos < size)
{
trace_write(raw_rec_trace_ctx, "mlv_rec_alloc_dummy: Failed to write to dummy file '%s'", filename);
return 0;
}
return 1;
}
static uint32_t mlv_rec_alloc_dummies(uint32_t total_size)
{
/* now allocate a dummy file that is going to be released when disk runs full */
uint32_t ret = 0;
char filename[MAX_PATH];
trace_write(raw_rec_trace_ctx, "mlv_rec_alloc_dummies: allocating...");
snprintf(filename, sizeof(filename), "%s/%s", get_dcim_dir(), MLV_DUMMY_FILENAME);
/* do one for the "main" card */
ret = mlv_rec_alloc_dummy(filename, total_size);
/* do the same for the other cards, if needed */
if(card_spanning)
{
filename[0] = 'B';
ret |= mlv_rec_alloc_dummy(filename, total_size);
}
trace_write(raw_rec_trace_ctx, "mlv_rec_alloc_dummies: allocating returns %d", ret);
return ret;
}
static void mlv_rec_release_dummies()
{
char filename[32];
snprintf(filename, sizeof(filename), "%s/%s", get_dcim_dir(), MLV_DUMMY_FILENAME);
/* delete the one for the "main" card */
/* do the same for the other cards, if needed */
if(card_spanning)
{
filename[0] = 'B';
FIO_RemoveFile(filename);
}
}
/* calc required padding for given address */
static uint32_t calc_padding(uint32_t address, uint32_t alignment)
{
uint32_t offset = address % alignment;
uint32_t padding = (alignment - offset) % alignment;
return padding;
}
static void refresh_cropmarks()
{
if (lv_dispsize > 1 || raw_rec_should_preview(0) || !mlv_video_enabled)
{
reset_movie_cropmarks();
}
else
{
int x = RAW2BM_X(skip_x);
int y = RAW2BM_Y(skip_y);
int w = RAW2BM_DX(res_x);
int h = RAW2BM_DY(res_y);
set_movie_cropmarks(x, y, w, h);
}
}
static void raw_rec_setup_trace()
{
if(!enable_tracing)
{
return;
}
if(raw_rec_trace_ctx != TRACE_ERROR)
{
return;
}
char filename[] = "raw_rec.txt";
raw_rec_trace_ctx = trace_start("raw_rec", filename);
trace_set_flushrate(raw_rec_trace_ctx, 60000);
trace_format(raw_rec_trace_ctx, TRACE_FMT_TIME_REL | TRACE_FMT_COMMENT, ' ');
}
static void 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 int32_t calc_res_y(int32_t res_x, int32_t num, int32_t den, float squeeze)
{
if (squeeze != 1.0f)
{
/* image should be enlarged vertically in post by a factor equal to "squeeze" */
return (int32_t)(roundf(res_x * den / num / squeeze) + 1) & ~1;
}
else
{
/* assume square pixels */
return (res_x * den / num + 1) & ~1;
}
}
static void update_cropping_offsets()
{
int32_t sx = raw_info.active_area.x1 + (max_res_x - res_x) / 2;
int32_t sy = raw_info.active_area.y1 + (max_res_y - res_y) / 2;
if (FRAMING_PANNING)
{
sx += frame_offset_x;
sy += frame_offset_y;
}
else if (FRAMING_CENTER && lv_dispsize > 1)
{
/* try to center the recording window on the YUV frame */
int delta_x, delta_y;
int32_t ok = focus_box_get_raw_crop_offset(&delta_x, &delta_y);
if (ok)
{
sx = COERCE(sx - delta_x, raw_info.active_area.x1, raw_info.active_area.x2 - res_x);
sy = COERCE(sy - delta_y, raw_info.active_area.y1, raw_info.active_area.y2 - res_y);
}
}
skip_x = sx;
skip_y = sy;
refresh_cropmarks();
}
static void update_resolution_params()
{
/* max res X */
/* make sure we don't get dead pixels from rounding */
int32_t left_margin = (raw_info.active_area.x1 + 7) / 8 * 8;
int32_t right_margin = (raw_info.active_area.x2) / 8 * 8;
int32_t max = (right_margin - left_margin);
/* horizontal resolution *MUST* be mod 32 in order to use the fastest EDMAC flags (16 byte transfer) */
max &= ~31;
max_res_x = max;
/* max res Y */
max_res_y = raw_info.jpeg.height & ~1;
/* squeeze factor */
if ( (cam_eos_m && !video_mode_crop) ? (lv_dispsize == 1) : (video_mode_resolution == 1 && lv_dispsize == 1 && is_movie_mode()) ) /* 720p, image squeezed */
{
/* assume the raw image should be 16:9 when de-squeezed */
//int32_t correct_height = max_res_x * 9 / 16;
//int32_t correct_height = max_res_x * 2 / 3; //TODO : FIX THIS, USE FOR NON-FULLFRAME SENSORS!
//squeeze_factor = (float)correct_height / max_res_y;
/* 720p mode uses 5x3 binning (5DMK3) or horizontal binning + vertical skipping (other cameras) */
squeeze_factor = 1.6666f; // 5.0/3.0
}
else squeeze_factor = 1.0f;
/* res X */
res_x = MIN(resolution_presets_x[resolution_index_x] + res_x_fine, max_res_x);
/* res Y */
int32_t num = aspect_ratio_presets_num[aspect_ratio_index];
int32_t den = aspect_ratio_presets_den[aspect_ratio_index];
res_y = MIN(calc_res_y(res_x, num, den, squeeze_factor), max_res_y);
/* frame size without rounding */
/* must be multiple of 4 */
frame_size = res_x * res_y * 14/8;
ASSERT(frame_size % 4 == 0);
update_cropping_offsets();
}
static int mlv_rec_update_raw()
{
/* we will fail if that is just a LV mode, but no movie mode */
if(!lv || !is_movie_mode())
{
return 0;
}
/* this call will retry internally, and if it fails, we can assume it was indeed something bad */
if (!raw_update_params())
{
return 0;
}
/* update interal parameters res_x, res_y, frame_size, squeeze_factor and crop offsets */
update_resolution_params();
return 1;
}
static char* guess_aspect_ratio(int32_t res_x, int32_t res_y)
{
static char msg[20];
int32_t best_num = 0;
int32_t best_den = 0;
float ratio = (float)res_x / res_y;
float minerr = 100;
/* common ratios that are expressed as integer numbers, e.g. 3:2, 16:9, but not 2.35:1 */
static int32_t common_ratios_x[] = {1, 2, 3, 3, 4, 16, 5, 5};
static int32_t common_ratios_y[] = {1, 1, 1, 2, 3, 9, 4, 3};
for (int32_t i = 0; i < COUNT(common_ratios_x); i++)
{
int32_t num = common_ratios_x[i];
int32_t den = common_ratios_y[i];
float err = ABS((float)num / den - ratio);
if (err < minerr)
{
minerr = err;
best_num = num;
best_den = den;
}
}
if (minerr < 0.05)
{
int32_t h = calc_res_y(res_x, best_num, best_den, squeeze_factor);
/* if the difference is 1 pixel, consider it exact */
char* qualifier = ABS(h - res_y) > 1 ? "almost " : "";
snprintf(msg, sizeof(msg), "%s%d:%d", qualifier, best_num, best_den);
}
else if (ratio > 1)
{
int32_t r = (int32_t)roundf(ratio * 100);
/* is it 2.35:1 or 2.353:1? */
int32_t h = calc_res_y(res_x, r, 100, squeeze_factor);
char* qualifier = ABS(h - res_y) > 1 ? "almost " : "";
if (r%100) snprintf(msg, sizeof(msg), "%s%d.%02d:1", qualifier, r/100, r%100);
}
else
{
int32_t r = (int32_t)roundf((1/ratio) * 100);
int32_t h = calc_res_y(res_x, 100, r, squeeze_factor);
char* qualifier = ABS(h - res_y) > 1 ? "almost " : "";
if (r%100) snprintf(msg, sizeof(msg), "%s1:%d.%02d", qualifier, r/100, r%100);
}
return msg;
}
static int32_t predict_frames(int32_t write_speed)
{
int32_t slot_size = frame_size + 64 + raw_rec_edmac_align + raw_rec_write_align;
int32_t fps = fps_get_current_x1000();
int32_t capture_speed = slot_size / 1000 * fps;
int32_t buffer_fill_speed = capture_speed - write_speed;
if (buffer_fill_speed <= 0)
{
return INT_MAX;
}
int32_t write_size = 0;
for (int32_t group = 0; group < slot_group_count; group++)
{
write_size += slot_groups[group].size;
}
float buffer_fill_time = write_size / (float) buffer_fill_speed;
int32_t frames = buffer_fill_time * fps / 1000;
return frames;
}
/* how many frames can we record with current settings, without dropping? */
static char* guess_how_many_frames()
{
if (!measured_write_speed) return "";
int32_t write_speed_lo = measured_write_speed * 1024 / 100 * 1024 - 512 * 1024;
int32_t write_speed_hi = measured_write_speed * 1024 / 100 * 1024 + 512 * 1024;
int32_t f_lo = predict_frames(write_speed_lo);
int32_t f_hi = predict_frames(write_speed_hi);
static char msg[50];
if (f_lo < 5000)
{
int32_t write_speed = (write_speed_lo + write_speed_hi) / 2;
write_speed = (write_speed * 10 + 512 * 1024) / (1024 * 1024);
if (f_lo != f_hi)
{
snprintf(msg, sizeof(msg), "Expect %d-%d frames at %d.%dMB/s.", f_lo, f_hi, write_speed / 10, write_speed % 10);
}
else
{
snprintf(msg, sizeof(msg), "Expect around %d frames at %d.%dMB/s.", f_lo, write_speed / 10, write_speed % 10);
}
}
else
{
snprintf(msg, sizeof(msg), "Continuous recording OK.");
}
return msg;
}
static MENU_UPDATE_FUNC(write_speed_update)
{
int32_t fps = fps_get_current_x1000();
int32_t speed = (res_x * res_y * 14/8 / 1024) * fps / 10 / 1024;
int32_t ok = speed < measured_write_speed;
speed /= 10;
if (!measured_write_speed)
{
MENU_SET_WARNING(ok ? MENU_WARN_INFO : MENU_WARN_ADVICE,
"Write speed needed: %d.%d MB/s at %d.%03d fps.",
speed/10, speed%10, fps/1000, fps%1000
);
}
else
{
MENU_SET_WARNING(ok ? MENU_WARN_INFO : MENU_WARN_ADVICE,
"%d.%d MB/s at %d.%03dp. %s",
speed/10, speed%10, fps/1000, fps%1000,
guess_how_many_frames()
);
}
}
static void refresh_raw_settings(int32_t force)
{
if (!lv) return;
if (RAW_IS_IDLE && !raw_previewing)
{
/* autodetect the resolution (update 4 times per second) */
static int aux = INT_MIN;
if (force || should_run_polling_action(250, &aux))
{
mlv_rec_update_raw();
}
}
}
static int32_t calc_crop_factor()
{
int32_t camera_crop = 162;
int32_t sampling_x = 3;
if (cam_5d2 || cam_5d3 || cam_6d) camera_crop = 100;
if (video_mode_crop || (lv_dispsize > 1)) sampling_x = 1;
get_afframe_sensor_res(&sensor_res_x, NULL);
if (!sensor_res_x) return 0;
if (!res_x) return 0;
return camera_crop * (sensor_res_x / sampling_x) / res_x;
}
static MENU_UPDATE_FUNC(raw_main_update)
{
if (!mlv_video_enabled)
{
reset_movie_cropmarks();
return;
}
refresh_cropmarks();
refresh_raw_settings(0);
if (!RAW_IS_IDLE)
{
MENU_SET_VALUE(RAW_IS_RECORDING ? "Recording..." : RAW_IS_PREPARING ? "Starting..." : RAW_IS_FINISHING ? "Stopping..." : "err");
MENU_SET_ICON(MNI_RECORD, 0);
}
else
{
MENU_SET_VALUE("ON, %dx%d", res_x, res_y);
int32_t crop_factor = calc_crop_factor();
if (crop_factor) MENU_SET_RINFO("%s%d.%02dx", FMT_FIXEDPOINT2( crop_factor ));
}
write_speed_update(entry, info);
}
static MENU_UPDATE_FUNC(aspect_ratio_update_info)
{
if (squeeze_factor == 1.0f)
{
char* ratio = guess_aspect_ratio(res_x, res_y);
MENU_SET_HELP("%dx%d (%s)", res_x, res_y, ratio);
}
else
{
int32_t num = aspect_ratio_presets_num[aspect_ratio_index];
int32_t den = aspect_ratio_presets_den[aspect_ratio_index];
int32_t sq100 = (int32_t)roundf(squeeze_factor*100);
int32_t res_y_corrected = calc_res_y(res_x, num, den, 1.0f);
MENU_SET_HELP("%dx%d. Stretch by %s%d.%02dx to get %dx%d (%s) in post.", res_x, res_y, FMT_FIXEDPOINT2(sq100), res_x, res_y_corrected, aspect_ratio_choices[aspect_ratio_index]);
}
}
static MENU_UPDATE_FUNC(resolution_update)
{
if (!mlv_video_enabled || !lv)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Enable RAW video first.");
MENU_SET_VALUE("N/A");
return;
}
refresh_raw_settings(1);
int32_t selected_x = resolution_presets_x[resolution_index_x] + res_x_fine;
MENU_SET_VALUE("%dx%d", res_x, res_y);
int32_t crop_factor = calc_crop_factor();
if (crop_factor) MENU_SET_RINFO("%s%d.%02dx", FMT_FIXEDPOINT2( crop_factor ));
if (selected_x > max_res_x)
{
MENU_SET_HELP("%d is not possible in current video mode (max %d).", selected_x, max_res_x);
}
else
{
aspect_ratio_update_info(entry, info);
}
write_speed_update(entry, info);
}
static MENU_UPDATE_FUNC(aspect_ratio_update)
{
if (!mlv_video_enabled || !lv)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Enable RAW video first.");
MENU_SET_VALUE("N/A");
return;
}
refresh_raw_settings(0);
int32_t num = aspect_ratio_presets_num[aspect_ratio_index];
int32_t den = aspect_ratio_presets_den[aspect_ratio_index];
int32_t selected_y = calc_res_y(res_x, num, den, squeeze_factor);
if (selected_y > max_res_y + 2)
{
char* ratio = guess_aspect_ratio(res_x, res_y * squeeze_factor);
MENU_SET_VALUE(ratio);
MENU_SET_HELP("Could not get %s. Max vertical resolution: %d.", aspect_ratio_choices[aspect_ratio_index], res_y);
}
else
{
aspect_ratio_update_info(entry, info);
}
write_speed_update(entry, info);
}
static MENU_UPDATE_FUNC(start_delay_update)
{
switch (start_delay_idx)
{
case 0:
start_delay = 0; break;
case 1:
start_delay = 2; break;
case 2:
start_delay = 4; break;
case 3:
start_delay = 10; break;
default:
start_delay= 0; break;
}
}
static void setup_chunk(uint32_t ptr, uint32_t size)
{
trace_write(raw_rec_trace_ctx, "chunk start 0x%X, size 0x%X, (end 0x%08X)", ptr, size, ptr + size);
if((int32_t)size < frame_size)
{
return;
}
uint32_t max_slot_size = raw_rec_write_align + sizeof(mlv_vidf_hdr_t) + raw_rec_edmac_align + frame_size + raw_rec_write_align;
/* fit as many frames as we can */
while (size >= max_slot_size && (slot_count < COUNT(slots)))
{
/* align slots so that they start at a write align boundary */
uint32_t pre_align = calc_padding(ptr, raw_rec_write_align);
/* set up a new VIDF header there */
mlv_vidf_hdr_t *vidf_hdr = (mlv_vidf_hdr_t *)(ptr + pre_align);
memset(vidf_hdr, 0x00, sizeof(mlv_vidf_hdr_t));
mlv_set_type((mlv_hdr_t *)vidf_hdr, "VIDF");
/* write frame header */
uint32_t dataStart = (uint32_t)vidf_hdr + sizeof(mlv_vidf_hdr_t);
int32_t edmac_size_align = calc_padding(dataStart, raw_rec_edmac_align);
vidf_hdr->frameSpace = edmac_size_align;
vidf_hdr->blockSize = sizeof(mlv_vidf_hdr_t) + vidf_hdr->frameSpace + frame_size;
/* now add a NULL block (if required) for aligning the whole slot size to optimal write size */
int32_t write_size_align = calc_padding(ptr + pre_align + vidf_hdr->blockSize, raw_rec_write_align);
if(write_size_align > 0)
{
if(write_size_align < (int32_t)sizeof(mlv_hdr_t))
{
write_size_align += raw_rec_write_align;
}
mlv_hdr_t *write_align_hdr = (mlv_hdr_t *)((uint32_t)vidf_hdr + vidf_hdr->blockSize);
memset(write_align_hdr, 0xA5, write_size_align);
mlv_set_type(write_align_hdr, "NULL");
write_align_hdr->blockSize = write_size_align;
}
/* store this slot */
slots[slot_count].ptr = (void*)(ptr + pre_align);
slots[slot_count].status = SLOT_FREE;
slots[slot_count].size = vidf_hdr->blockSize + write_size_align;
slots[slot_count].blockSize = vidf_hdr->blockSize;
slots[slot_count].frameSpace = vidf_hdr->frameSpace;
trace_write(raw_rec_trace_ctx, " slot %3d: base 0x%08X, end 0x%08X, aligned 0x%08X, data 0x%08X, size 0x%X (pre 0x%08X, edmac 0x%04X, write 0x%04X)",
slot_count, ptr, (slots[slot_count].ptr + slots[slot_count].size), slots[slot_count].ptr, dataStart + vidf_hdr->frameSpace, slots[slot_count].size, pre_align, edmac_size_align, write_size_align);
if(slots[slot_count].size > (int32_t)max_slot_size)
{
trace_write(raw_rec_trace_ctx, " slot %3d: ERROR - size too large", slot_count);
beep(4);
}
/* update counters and pointers */
ptr += slots[slot_count].size;
ptr += pre_align;
size -= slots[slot_count].size;
size -= pre_align;
slot_count++;
}
trace_flush(raw_rec_trace_ctx);
}
static void setup_prot(uint32_t *ptr, uint32_t *size)
{
uint32_t prot_size = 1024;
uint8_t *data = (uint8_t*)*ptr;
for(uint32_t pos = 0; pos < prot_size; pos++)
{
data[pos] = 0xA5;
data[*size - prot_size + pos] = 0xA5;
}
*ptr += prot_size;
*size -= 2 * prot_size;
}
static void check_prot(uint32_t ptr, uint32_t size, uint32_t original)
{
uint32_t prot_size = 1024;
if(!original)
{
ptr -= prot_size;
size += 2 * prot_size;
}
uint8_t *data = (uint8_t*)ptr;
for(uint32_t pos = 0; pos < prot_size; pos++)
{
if(data[pos] != 0xA5)
{
trace_write(raw_rec_trace_ctx, "check_prot(0x%08X, 0x%08X) ERROR - leading protection modified at offset 0x%08X: 0x%02X", ptr, size, pos, data[pos]);
beep(4);
break;
}
if(data[size - prot_size + pos] != 0xA5)
{
trace_write(raw_rec_trace_ctx, "check_prot(0x%08X, 0x%08X) ERROR - trailing protection modified at offset 0x%08X: 0x%02X", ptr, size, pos, data[size - prot_size + pos]);
beep(4);
break;
}
}
}
static void free_mem_suite(struct memSuite * mem_suite, void(*free_suite_func)(struct memSuite *))
{
if (mem_suite)
{
struct memChunk * chunk = GetFirstChunkFromSuite(mem_suite);
while(chunk)
{
uint32_t size = GetSizeOfMemoryChunk(chunk);
uint32_t ptr = (uint32_t)GetMemoryAddressOfMemoryChunk(chunk);
check_prot(ptr, size, 1);
chunk = GetNextMemoryChunk(mem_suite, chunk);
}
free_suite_func(mem_suite);
}
}
static void free_buffers()
{
free_mem_suite(shoot_mem_suite, &shoot_free_suite);
shoot_mem_suite = 0;
free_mem_suite(srm_mem_suite, &srm_free_suite);
srm_mem_suite = 0;
if (fullsize_buffers[0]) fio_free(fullsize_buffers[0]);
fullsize_buffers[0] = 0;
}
static void setup_mem_suite(struct memSuite * mem_suite, uint32_t buf_size)
{
if(mem_suite)
{
struct memChunk * chunk = GetFirstChunkFromSuite(mem_suite);
while(chunk)
{
uint32_t size = GetSizeOfMemoryChunk(chunk);
uint32_t ptr = (uint32_t)GetMemoryAddressOfMemoryChunk(chunk);
/* add some protection to detect overwrites */
setup_prot(&ptr, &size);
check_prot(ptr, size, 0);
chunk = GetNextMemoryChunk(mem_suite, chunk);
}
}
}
static uint32_t add_mem_suite(struct memSuite * mem_suite, uint32_t buf_size)
{
uint32_t total_size = 0;
if(mem_suite)
{
/* use all chunks larger than frame_size for recording */
struct memChunk * chunk = GetFirstChunkFromSuite(mem_suite);
while(chunk)
{
uint32_t size = GetSizeOfMemoryChunk(chunk);
uint32_t ptr = (uint32_t)GetMemoryAddressOfMemoryChunk(chunk);
trace_write(raw_rec_trace_ctx, "Chunk: 0x%08X, size: 0x%08X", ptr, size);
/* add some protection to detect overwrites */
setup_prot(&ptr, &size);
check_prot(ptr, size, 0);
setup_chunk(ptr, size);
total_size += size;
chunk = GetNextMemoryChunk(mem_suite, chunk);
}
}
return total_size;
}
static int32_t setup_buffers()
{
uint32_t total_size = 0;
slot_count = 0;
slot_group_count = 0;
/* allocate memory for double buffering */
/* (we need a single large contiguous chunk) */
uint32_t buf_size = raw_info.width * raw_info.height * 14/8 * 33/32; /* leave some margin, just in case */
ASSERT(fullsize_buffers[0] == 0);
fullsize_buffers[0] = fio_malloc(buf_size);
/* reuse Canon's buffer */
fullsize_buffers[1] = UNCACHEABLE(raw_info.buffer);
/* anything wrong? */
if(fullsize_buffers[0] == 0 || fullsize_buffers[1] == 0)
{
/* buffers will be freed by caller in the cleanup section */
return 0;
}
/* allocate the entire memory, but only use large chunks */
/* yes, this may be a bit wasteful, but at least it works */
shoot_mem_suite = shoot_malloc_suite(0);
srm_mem_suite = use_srm_memory ? srm_malloc_suite(0) : 0;
if(!shoot_mem_suite && !srm_mem_suite)
{
return 0;
}
setup_mem_suite(shoot_mem_suite, buf_size);
setup_mem_suite(srm_mem_suite, buf_size);
trace_write(raw_rec_trace_ctx, "frame size = 0x%X", frame_size);
total_size += add_mem_suite(shoot_mem_suite, buf_size);
total_size += add_mem_suite(srm_mem_suite, buf_size);
if(DISPLAY_REC_INFO_DEBUG)
{
bmp_printf(FONT_MED, 30, 90, "buffer size: %d frames", slot_count);
}
/* we need at least 3 slots */
if (slot_count < 3)
{
return 0;
}
trace_write(raw_rec_trace_ctx, "Building a group list...");
uint32_t block_start = 0;
uint32_t block_len = 0;
uint32_t block_size = 0;
uint32_t last_slot_end = 0;
/* this loop goes one slot behind the end */
for(int32_t slot = 0; slot <= slot_count; slot++)
{
uint32_t slot_start = 0;
uint32_t slot_end = 0;
if(slot < slot_count)
{
slot_start = (uint32_t) slots[slot].ptr;
slot_end = slot_start + slots[slot].size;
}
/* the first time, on a non contiguous area or the last frame (its == slot_count) reset all counters */
uint32_t non_contig = slot_start != last_slot_end;
uint32_t last_iteration = slot == slot_count;
if((block_len != 0) && (non_contig || last_iteration))
{
slot_groups[slot_group_count].slot = block_start;
slot_groups[slot_group_count].len = block_len;
slot_groups[slot_group_count].size = block_size;
trace_write(raw_rec_trace_ctx, "group: %d block_len: %d block_start: %d", slot_group_count, block_len, block_start);
slot_group_count++;
if(slot == slot_count)
{
break;
}
block_len = 0;
}
if(slot < slot_count)
{
if(block_len == 0)
{
block_len = 1;
block_start = slot;
block_size = slots[slot].size;
}
else
{
/* its a contiguous area, increase counters */
block_len++;
block_size += slots[slot].size;
}
}
last_slot_end = slot_end;
}
/* hackish bubble sort group list */
trace_write(raw_rec_trace_ctx, "Sorting group list...");
int n = slot_group_count;
do
{
int newn = 1;
for(int i = 0; i < n-1; ++i)
{
if(slot_groups[i].len < slot_groups[i+1].len)
{
struct frame_slot_group tmp = slot_groups[i+1];
slot_groups[i+1] = slot_groups[i];
slot_groups[i] = tmp;
newn = i + 1;
}
}
n = newn;
} while (n > 1);
for(int group = 0; group < slot_group_count; group++)
{
trace_write(raw_rec_trace_ctx, "group: %d length: %d slot: %d", group, slot_groups[group].len, slot_groups[group].slot);
}
if(create_dummy)
{
return mlv_rec_alloc_dummies(total_size);
}
return 1;
}
static int32_t get_free_slots()
{
int32_t free_slots = 0;
for (int32_t i = 0; i < slot_count; i++)
{
if (slots[i].status == SLOT_FREE)
{
free_slots++;
}
}
return free_slots;
}
static void show_buffer_status()
{
if (!liveview_display_idle()) return;
int32_t scale = MAX(1, (300 / slot_count + 1) & ~1);
int32_t x = 30;
int32_t y = 50;
for (int32_t group = 0; group < slot_group_count; group++)
{
for (int32_t slot = slot_groups[group].slot; slot < (slot_groups[group].slot + slot_groups[group].len); slot++)
{
int32_t color = COLOR_BLACK;
switch(slots[slot].status)
{
case SLOT_FREE:
color = COLOR_GRAY(10);
break;
case SLOT_WRITING:
if(slots[slot].writer == 0)
{
color = COLOR_GREEN1;
}
else
{
color = COLOR_YELLOW;
}
break;
case SLOT_FULL:
color = COLOR_LIGHT_BLUE;
break;
case SLOT_LOCKED:
color = COLOR_RED;
break;
default:
color = COLOR_BLACK;
break;
}
for(int32_t k = 0; k < scale; k++)
{
draw_line(x, y+5, x, y+17, color);
x++;
}
if(scale > 3)
{
x++;
}
}
x += MAX(2, scale);
}
if (DISPLAY_REC_INFO_DEBUG && frame_skips > 0)
{
bmp_printf(FONT(FONT_MED, COLOR_RED, COLOR_BLACK), x+10, y, "%d skips", frame_skips);
}
if(show_graph && (!frame_skips || allow_frame_skip))
{
int32_t free = get_free_slots();
int32_t ymin = 190;
int32_t ymax = 400;
int32_t x = frame_count % 720;
int32_t y_fill = ymin + free * (ymax - ymin) / slot_count;
int32_t y_rate_0 = ymin + (ymax - ymin) - (current_write_speed[0] * (ymax - ymin) / 11000);
int32_t y_rate_1 = ymin + (ymax - ymin) - (current_write_speed[1] * (ymax - ymin) / 11000);
static int32_t prev_x = 0;
static int32_t prev_fill_y = 0;
static int32_t prev_rate_0_y = 0;
static int32_t prev_rate_1_y = 0;
if (prev_x && (prev_x < x))
{
if(prev_fill_y)
{
draw_line(prev_x, prev_fill_y, x, y_fill, COLOR_GREEN1);
}
if(prev_rate_0_y && y_rate_0 != ymax)
{
draw_line(prev_x, prev_rate_0_y, x, y_rate_0, COLOR_RED);
}
if(card_spanning && prev_rate_1_y && y_rate_1 != ymax)
{
draw_line(prev_x, prev_rate_1_y, x, y_rate_1, COLOR_BLUE);
}
}
/* paint dots at current rate/level */
fill_circle(x, y_fill, 2, COLOR_GREEN1);
if(y_rate_0 != ymax)
{
fill_circle(x, y_rate_0, 2, COLOR_RED);
}
if(card_spanning && y_rate_1 != ymax)
{
fill_circle(x, y_rate_1, 2, COLOR_BLUE);
}
prev_x = x;
prev_fill_y = y_fill;
prev_rate_0_y = y_rate_0;
prev_rate_1_y = y_rate_1;
/* paint prediction dot */
static int32_t prev_xp = 0;
if(prev_xp)
{
fill_circle(prev_xp, ymin, 2, COLOR_EMPTY);
}
int32_t xp = predict_frames(measured_write_speed * 1024 / 100 * 1024) % 720;
fill_circle(xp, ymin, 2, COLOR_RED);
prev_xp = xp;
bmp_draw_rect(COLOR_GRAY(20), 0, ymin, 720, ymax-ymin);
}
}
static void panning_update()
{
if (!FRAMING_PANNING) return;
int32_t sx = raw_info.active_area.x1 + (max_res_x - res_x) / 2;
int32_t sy = raw_info.active_area.y1 + (max_res_y - res_y) / 2;
frame_offset_x = COERCE(
frame_offset_x + frame_offset_delta_x,
raw_info.active_area.x1 - sx,
raw_info.active_area.x1 + max_res_x - res_x - sx
);
frame_offset_y = COERCE(
frame_offset_y + frame_offset_delta_y,
raw_info.active_area.y1 - sy,
raw_info.active_area.y1 + max_res_y - res_y - sy
);
update_cropping_offsets();
}
static void raw_video_enable()
{
/* toggle the lv_save_raw flag from raw.c */
raw_lv_request();
msleep(50);
}
static void raw_video_disable()
{
raw_lv_release();
}
static void raw_lv_request_update()
{
static int32_t raw_lv_requested = 0;
if (mlv_video_enabled && lv && is_movie_mode())
{
if (!raw_lv_requested)
{
raw_video_enable();
raw_lv_requested = 1;
}
}
else
{
if (raw_lv_requested)
{
raw_video_disable();
raw_lv_requested = 0;
}
}
}
static unsigned int raw_rec_polling_cbr(unsigned int unused)
{
raw_lv_request_update();
if (!mlv_video_enabled)
{
return 0;
}
if (!lv || !is_movie_mode())
{
return 0;
}
/* update settings when changing video modes (outside menu) */
if (RAW_IS_IDLE && !gui_menu_shown())
{
refresh_raw_settings(0);
}
/* update status messages */
static int auxrec = INT_MIN;
static int block_queueing = INT_MIN;
static int rtci_queueing = INT_MIN;
if (RAW_IS_RECORDING)
{
/* enqueue lens, rtc, expo etc status */
if(should_run_polling_action(MLV_RTCI_BLOCK_INTERVAL, &rtci_queueing) && (mlv_metadata & MLV_METADATA_CYCLIC))
{
trace_write(raw_rec_trace_ctx, "[polling_cbr] queueing RTCI");
mlv_rtci_hdr_t *rtci_hdr = malloc(sizeof(mlv_rtci_hdr_t));
mlv_fill_rtci(rtci_hdr, mlv_start_timestamp);
msg_queue_post(mlv_block_queue, (uint32_t) rtci_hdr);
}
if(should_run_polling_action(MLV_INFO_BLOCK_INTERVAL, &block_queueing) && (mlv_metadata & MLV_METADATA_CYCLIC))
{
trace_write(raw_rec_trace_ctx, "[polling_cbr] queueing INFO blocks");
mlv_expo_hdr_t *expo_hdr = malloc(sizeof(mlv_expo_hdr_t));
mlv_lens_hdr_t *lens_hdr = malloc(sizeof(mlv_lens_hdr_t));
mlv_wbal_hdr_t *wbal_hdr = malloc(sizeof(mlv_wbal_hdr_t));
mlv_fill_expo(expo_hdr, mlv_start_timestamp);
mlv_fill_lens(lens_hdr, mlv_start_timestamp);
mlv_fill_wbal(wbal_hdr, mlv_start_timestamp);
msg_queue_post(mlv_block_queue, (uint32_t) expo_hdr);
msg_queue_post(mlv_block_queue, (uint32_t) lens_hdr);
msg_queue_post(mlv_block_queue, (uint32_t) wbal_hdr);
}
if(!DISPLAY_REC_INFO_NONE && liveview_display_idle() && should_run_polling_action(DEBUG_REDRAW_INTERVAL, &auxrec))
{
if(DISPLAY_REC_INFO_ICON)
{
int32_t fps = fps_get_current_x1000();
int32_t t = ((frame_count + frame_skips) * 1000 + fps/2) / fps;
int32_t predicted = predict_frames(measured_write_speed * 1024 / 100 * 1024);
/* print the Recording Icon */
int rl_color;
if(predicted < 10000)
{
int time_left = (predicted-frame_count) * 1000 / fps;
if (time_left < 10) {
rl_color = COLOR_DARK_RED;
} else {
rl_color = COLOR_YELLOW;
}
}
else
{
rl_color = COLOR_GREEN1;
}
int rl_icon_width=0;
/* Draw the movie camera icon */
rl_icon_width = bfnt_draw_char(ICON_ML_MOVIE, MLV_ICON_X, MLV_ICON_Y, rl_color, COLOR_BG_DARK);
/* Display the Status */
bmp_printf(FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), MLV_ICON_X+rl_icon_width+5, MLV_ICON_Y+5, "%02d:%02d", t/60, t%60);
if(frame_skips)
{
bmp_printf(FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), MLV_ICON_X+rl_icon_width+5, MLV_ICON_Y+30, "%d skipped", frame_skips);
}
/* Write speed, main thread only */
int32_t speed = current_write_speed[0];
int32_t idle_percent = idle_time[0] * 100 / (writing_time[0] + idle_time[0]);
speed /= 10;
if(writing_time[0] || idle_time[0])
{
char msg[50];
snprintf(msg, sizeof(msg), "%d.%01dMB/s", speed/10, speed%10);
if (idle_time[0])
{
if (idle_percent)
{
STR_APPEND(msg, "\n%d%% idle", idle_percent);
}
}
bmp_printf (FONT(FONT_SMALL, COLOR_WHITE, COLOR_BG_DARK), MLV_ICON_X+rl_icon_width+5, MLV_ICON_Y+5+font_med.height, "%s", msg);
}
}
else if(DISPLAY_REC_INFO_DEBUG)
{
int32_t fps = fps_get_current_x1000();
int32_t t = (frame_count * 1000 + fps/2) / fps;
int32_t predicted = predict_frames(measured_write_speed * 1024 / 100 * 1024);
if (predicted < 10000)
bmp_printf( FONT_MED, 30, cam_50d ? 350 : 400,
"%02d:%02d, %d frames / %d expected ",
t/60, t%60,
frame_count,
predicted
);
else
bmp_printf( FONT_MED, 30, cam_50d ? 350 : 400,
"%02d:%02d, %d frames, continuous OK ",
t/60, t%60,
frame_count
);
if (show_graph)
{
show_buffer_status();
}
/* how fast are we writing? does this speed match our benchmarks? */
uint32_t str_skip = strlen(get_dcim_dir()) + 1;
for(uint32_t writer = 0; writer < mlv_writer_threads; writer++)
{
/* skip A:/DCIM/100CANON/ */
char *filename = &(chunk_filename[writer][str_skip]);
if(writing_time[writer] || idle_time[writer])
{
int32_t speed = current_write_speed[writer];
int32_t idle_percent = idle_time[writer] * 100 / (writing_time[writer] + idle_time[writer]);
speed /= 10;
char msg[100];
snprintf(msg, sizeof(msg), "%s: %d MB, %d.%d MB/s", filename, written[writer] / 1024, speed/10, speed%10 );
if(idle_time[writer])
{
if (idle_percent)
{
STR_APPEND(msg, ", %d%% idle ", idle_percent);
}
else
{
STR_APPEND(msg, ", %dms idle ", idle_time[writer]);
}
}
bmp_printf( FONT_MED, 30, cam_50d ? 370 : 420 + writer * font_med.height, "%s ", msg);
}
else
{
bmp_printf( FONT_MED, 30, cam_50d ? 370 : 420 + writer * font_med.height, "%s: idle ", filename);
}
}
if(card_spanning)
{
char msg[100];
snprintf(msg, sizeof(msg), "Total rate: %d.%d MB/s", measured_write_speed/100, (measured_write_speed/10)%10);
bmp_printf( FONT_MED, 30, 130 + mlv_writer_threads * font_med.height, "%s", msg);
}
}
}
}
return 0;
}
static void hack_liveview_vsync()
{
if (cam_5d2 || cam_50d)
{
/* try to fix pink preview in zoom mode (5D2/50D) */
if (lv_dispsize > 1 && !get_halfshutter_pressed())
{
if (RAW_IS_IDLE)
{
/**
* This register seems to be raw type on digic 4; digic 5 has it at c0f37014
* - default is 5 on 5D2 with lv_save_raw, 0xB without, 4 is lv_af_raw
* - don't record this: you will have lots of bad pixels (no big deal if you can remove them)
* - don't record lv_af_raw: you will have random colored dots that contain focus info; their position is not fixed, so you can't remove them
* - use half-shutter heuristic for clean silent pics
*
* Reason for overriding here:
* - if you use lv_af_raw, you can no longer restore it when you start recording.
* - if you override here, image quality is restored as soon as you stop overriding
* - but pink preview is also restored, you can't have both
*/
*(volatile uint32_t*)0xc0f08114 = 0;
}
else
{
/**
* While recording, we will have pink image
* Make it grayscale and bring the shadows down a bit
* (these registers will only touch the preview, not the recorded image)
*/
*(volatile uint32_t*)0xc0f0f070 = 0x01000100;
//~ *(volatile uint32_t*)0xc0f0e094 = 0;
*(volatile uint32_t*)0xc0f0f1c4 = 0xFFFFFFFF;
}
}
}
if (!PREVIEW_HACKED) return;
int32_t rec = RAW_IS_RECORDING;
static int32_t prev_rec = 0;
int32_t should_hack = 0;
int32_t should_unhack = 0;
if(rec)
{
if (frame_count == 0)
{
should_hack = 1;
}
}
else if (prev_rec)
{
should_unhack = 1;
}
prev_rec = rec;
if(should_hack)
{
if(!PREVIEW_CANON && !PREVIEW_AUTO)
{
int32_t y = 100;
for(int32_t channel = 0; channel < 32; channel++)
{
/* silence out the EDMACs used for HD and LV buffers */
int32_t pitch = edmac_get_length(channel) & 0xFFFF;
if (pitch == vram_lv.pitch || pitch == vram_hd.pitch || pitch== 2000 || pitch== 512 || pitch== 576 || pitch== 3456)
{
uint32_t reg = edmac_get_base(channel);
bmp_printf(FONT_SMALL, 30, y += font_small.height, "Hack %x %dx%d ", reg, shamem_read(reg + 0x10) & 0xFFFF, shamem_read(reg + 0x10) >> 16);
*(volatile uint32_t *)(reg + 0x10) = shamem_read(reg + 0x10) & 0xFFFF;
}
}
}
}
else if(should_unhack)
{
if (cam_eos_m) //EOS-M not unhacking, why?
{
//call("aewb_enableaewb", 1);
PauseLiveView();
ResumeLiveView();
idle_globaldraw_en();
}
else
{
task_create("lv_unhack", 0x1e, 0x1000, unhack_liveview_vsync, (void*)0);
}
}
}
/* this is a separate task */
static void unhack_liveview_vsync(int32_t unused)
{
while (!RAW_IS_IDLE)
{
msleep(100);
}
PauseLiveView();
ResumeLiveView();
if (PREVIEW_NOT || kill_gd)
{
idle_globaldraw_en();
}
}
static void hack_liveview(int32_t unhack)
{
if (PREVIEW_NOT || kill_gd)
{
if (!unhack)
{
idle_globaldraw_dis();
clrscr();
}
else
{
idle_globaldraw_en();
}
}
if (small_hacks)
{
/* disable canon graphics (gains a little speed) */
static int32_t canon_gui_was_enabled;
if (!unhack)
{
canon_gui_was_enabled = !canon_gui_front_buffer_disabled();
canon_gui_disable_front_buffer();
}
else if (canon_gui_was_enabled)
{
canon_gui_enable_front_buffer(0);
canon_gui_was_enabled = 0;
}
/* disable auto exposure and auto white balance */
call("aewb_enableaewb", unhack ? 1 : 0); /* for new cameras */
call("lv_ae", unhack ? 1 : 0); /* for old cameras */
call("lv_wb", unhack ? 1 : 0);
if (cam_50d && !(hdmi_code == 5) && !unhack)
{
/* not sure how to unhack this one, and on 5D2 it crashes */
call("lv_af_fase_addr", 0); //Turn off face detection
}
/* change dialog refresh timer from 50ms to 8192ms */
uint32_t dialog_refresh_timer_addr = /* in StartDialogRefreshTimer */
cam_50d ? 0xffa84e00 :
cam_5d2 ? 0xffaac640 :
cam_5d3 ? 0xff4acda4 :
cam_550d ? 0xFF2FE5E4 :
cam_600d ? 0xFF37AA18 :
cam_650d ? 0xFF527E38 :
cam_6d ? 0xFF52C684 :
cam_eos_m ? 0xFF539C1C :
cam_700d ? 0xFF52BB60 :
cam_7d ? 0xFF345788 :
cam_60d ? 0xff36fa3c :
cam_500d ? 0xFF2ABEF8 :
/* ... */
0;
uint32_t dialog_refresh_timer_orig_instr = 0xe3a00032; /* mov r0, #50 */
uint32_t dialog_refresh_timer_new_instr = 0xe3a00a02; /* change to mov r0, #8192 */
if (*(volatile uint32_t*)dialog_refresh_timer_addr != dialog_refresh_timer_orig_instr)
{
/* something's wrong */
NotifyBox(1000, "Hack error at %x:\nexpected %x, got %x", dialog_refresh_timer_addr, dialog_refresh_timer_orig_instr, *(volatile uint32_t*)dialog_refresh_timer_addr);
beep_custom(1000, 2000, 1);
dialog_refresh_timer_addr = 0;
}
if (dialog_refresh_timer_addr)
{
if (!unhack) /* hack */
{
cache_fake(dialog_refresh_timer_addr, dialog_refresh_timer_new_instr, TYPE_ICACHE);
}
else /* unhack */
{
cache_fake(dialog_refresh_timer_addr, dialog_refresh_timer_orig_instr, TYPE_ICACHE);
}
}
}
}
void mlv_rec_queue_block(mlv_hdr_t *hdr)
{
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
void mlv_rec_set_rel_timestamp(mlv_hdr_t *hdr, uint64_t timestamp)
{
hdr->timestamp = timestamp - mlv_start_timestamp;
}
/* this can be called from anywhere to get a free memory slot. must be submitted using mlv_rec_release_slot() */
int32_t mlv_rec_get_free_slot()
{
uint32_t retries = 0;
int32_t allocated_slot = -1;
retry_find:
allocated_slot = -1;
/* find a slot in the smallest groups first */
for(int32_t group = slot_group_count - 1; group >= 0 ; group--)
{
for (int32_t slot = slot_groups[group].slot; slot < (slot_groups[group].slot + slot_groups[group].len); slot++)
{
/* check for the slot being ready for saving */
if(slots[slot].status == SLOT_FREE)
{
allocated_slot = slot;
break;
}
}
/* already found one? */
if(allocated_slot >= 0)
{
break;
}
}
/* now try to mark this slot as being used */
if(allocated_slot >= 0)
{
uint32_t old_int = cli();
if(slots[allocated_slot].status == SLOT_FREE)
{
slots[allocated_slot].status = SLOT_LOCKED;
}
else
{
allocated_slot = -1;
}
sei(old_int);
}
/* ok now check if allocation was successful and retry */
if(allocated_slot < 0)
{
retries++;
if(retries < 5)
{
goto retry_find;
}
else
{
return -1;
}
}
return allocated_slot;
}
void mlv_rec_get_slot_info(int32_t slot, uint32_t *size, void **address)
{
if(slot < 0 || slot >= slot_count)
{
*address = NULL;
*size = 0;
return;
}
/* as the caller will use the slot for anything, we have to save out VIDF frame header
(it contains padding and stuff that we dont want to recalculate)
we are using a NULL block with some data in it to store the VIDF.
*/
mlv_vidf_hdr_t *vidf = slots[slot].ptr;
/* set old header to a skipped header format */
mlv_set_type((mlv_hdr_t *)vidf, "NULL");
/* backup old size into free space */
((uint32_t*) vidf)[sizeof(mlv_vidf_hdr_t)/4] = vidf->blockSize;
/* then set the header to be totally skipped */
vidf->blockSize = 0x100;
/* ok now return the shrunk buffer address */
*address = (void*)((uint32_t)slots[slot].ptr + 0x100);
*size = slots[slot].size - vidf->blockSize;
}
/* mark a previously with mlv_rec_get_free_slot() allocated slot for being reused or written into the file */
void mlv_rec_release_slot(int32_t slot, uint32_t write)
{
if(slot < 0 || slot >= slot_count)
{
return;
}
if(write)
{
slots[slot].status = SLOT_FULL;
}
else
{
slots[slot].status = SLOT_FREE;
}
}
static int32_t FAST choose_next_capture_slot()
{
uint32_t retries = 0;
int32_t allocated_slot = -1;
retry_find:
allocated_slot = -1;
switch(buffer_fill_method)
{
case 0:
/* new: return next free slot for out-of-order writing */
for(int32_t slot = 0; slot < slot_count; slot++)
{
if(slots[slot].status == SLOT_FREE)
{
allocated_slot = slot;
break;
}
}
break;
case 4:
case 1:
/* new method: first fill largest group */
for (int32_t group = 0; group < slot_group_count; group++)
{
for (int32_t slot = slot_groups[group].slot; slot < (slot_groups[group].slot + slot_groups[group].len); slot++)
{
if (slots[slot].status == SLOT_FREE)
{
allocated_slot = slot;
break;
}
}
/* already found one? */
if(allocated_slot >= 0)
{
break;
}
}
break;
case 3:
/* new method: first fill largest groups */
for (int32_t group = 0; group < fast_card_buffers; group++)
{
for (int32_t slot = slot_groups[group].slot; slot < (slot_groups[group].slot + slot_groups[group].len); slot++)
{
if (slots[slot].status == SLOT_FREE)
{
allocated_slot = slot;
break;
}
}
/* already found one? */
if(allocated_slot >= 0)
{
break;
}
}
/* fall through */
case 2:
default:
/* keep on rolling? */
/* O(1) */
if (capture_slot >= 0 && capture_slot + 1 < slot_count)
{
if(slots[capture_slot + 1].ptr == slots[capture_slot].ptr + slots[capture_slot].size &&
slots[capture_slot + 1].status == SLOT_FREE && !force_new_buffer )
return capture_slot + 1;
}
/* choose a new buffer? */
/* choose the largest contiguous free section */
/* O(n), n = slot_count */
int32_t len = 0;
void* prev_ptr = PTR_INVALID;
int32_t prev_blockSize = 0;
int32_t best_len = 0;
for (int32_t i = 0; i < slot_count; i++)
{
if (slots[i].status == SLOT_FREE)
{
if (slots[i].ptr == prev_ptr + prev_blockSize)
{
len++;
prev_ptr = slots[i].ptr;
prev_blockSize = slots[i].size;
if (len > best_len)
{
best_len = len;
allocated_slot = i - len + 1;
}
}
else
{
len = 1;
prev_ptr = slots[i].ptr;
prev_blockSize = slots[i].size;
if (len > best_len)
{
best_len = len;
allocated_slot = i;
}
}
}
else
{
len = 0;
prev_ptr = PTR_INVALID;
}
}
break;
}
/* now try to mark this slot as being used */
if(allocated_slot >= 0)
{
uint32_t old_int = cli();
if(slots[allocated_slot].status == SLOT_FREE)
{
slots[allocated_slot].status = SLOT_LOCKED;
}
else
{
allocated_slot = -1;
}
sei(old_int);
}
/* ok now check if allocation was successful and retry */
if(allocated_slot < 0)
{
retries++;
if(retries < 5)
{
goto retry_find;
}
else
{
return -1;
}
}
return allocated_slot;
}
/* this function uses the frameSpace area in a VIDF that was meant for padding to insert some other block before */
static int32_t mlv_prepend_block(uint32_t slot, mlv_hdr_t *block)
{
mlv_vidf_hdr_t *hdr = slots[slot].ptr;
uint32_t blockSize = block->blockSize;
/* make sure that the block size of the block to insert is aligned */
blockSize += ((0x10 - (blockSize % 0x10)) % 0x10);
if(!memcmp(hdr->blockType, "VIDF", 4))
{
/* it's a VIDF block that should get shrinked and data prepended.
new layout:
BLOCK (the block being inserted)
VIDF (original VIDF, just shrinked frameSpace)
*/
if(hdr->frameSpace < blockSize)
{
/* there is not enough room */
return 1;
}
/* calculate start address of repositioned VIDF block */
uint32_t new_vidf_offset = blockSize;
/* create pointers to all blocks used */
mlv_hdr_t *inserted_block = (mlv_hdr_t *)hdr;
mlv_vidf_hdr_t *new_vidf = (mlv_vidf_hdr_t *)((uint32_t)hdr + new_vidf_offset);
/* copy VIDF header to new position and fix frameSpace */
memmove(new_vidf, hdr, sizeof(mlv_vidf_hdr_t));
new_vidf->blockSize -= new_vidf_offset;
new_vidf->frameSpace -= new_vidf_offset;
/* copy block to prepend */
memmove(inserted_block, block, block->blockSize);
/* and set the correctly aligned blocksize */
inserted_block->blockSize = blockSize;
return 0;
}
else
{
/* now skip until the VIDF is reached */
uint32_t offset = 0;
while(offset < slots[slot].frameSpace)
{
mlv_hdr_t *current_hdr = (mlv_hdr_t *)((uint32_t)hdr + offset);
ASSERT((current_hdr->blockSize > 0) && (current_hdr->blockSize < 0x20000000));
if(!memcmp(current_hdr->blockType, "VIDF", 4))
{
mlv_vidf_hdr_t *old_vidf = (mlv_vidf_hdr_t *)current_hdr;
if(old_vidf->frameSpace < blockSize)
{
/* there is not enough room */
return 2;
}
/* calculate start address of the again repositioned VIDF block */
mlv_vidf_hdr_t *new_vidf = (mlv_vidf_hdr_t *)((uint32_t)old_vidf + blockSize);
/* copy VIDF header to new position and fix frameSpace */
memmove(new_vidf, old_vidf, sizeof(mlv_vidf_hdr_t));
new_vidf->blockSize -= blockSize;
new_vidf->frameSpace -= blockSize;
/* copy block to prepend */
memmove(current_hdr, block, block->blockSize);
/* and set the correctly aligned blocksize */
current_hdr->blockSize = blockSize;
return 0;
}
else
{
/* skip to next block */
offset += current_hdr->blockSize;
}
}
return 0;
}
return 4;
}
static void mlv_rec_dma_cbr_r(void *ctx)
{
/* now mark the last filled buffer as being ready to transfer */
slots[capture_slot].status = SLOT_FULL;
mlv_rec_dma_active = 0;
mlv_rec_dma_end = get_us_clock_value();
mlv_rec_dma_duration = (uint32_t)(mlv_rec_dma_end - mlv_rec_dma_start);
edmac_copy_rectangle_adv_cleanup();
}
static void mlv_rec_dma_cbr_w(void *ctx)
{
}
static int32_t FAST process_frame()
{
/* skip the first frame, it will be gibberish */
if(frame_count == 0)
{
frame_count++;
return 0;
}
/* where to save the next frame? */
capture_slot = choose_next_capture_slot(capture_slot);
if(capture_slot < 0)
{
/* card too slow */
frame_skips++;
return 0;
}
/* restore VIDF header */
mlv_vidf_hdr_t *hdr = slots[capture_slot].ptr;
mlv_set_type((mlv_hdr_t *)hdr, "VIDF");
mlv_set_timestamp((mlv_hdr_t *)hdr, mlv_start_timestamp);
hdr->blockSize = slots[capture_slot].blockSize;
hdr->frameSpace = slots[capture_slot].frameSpace;
/* frame number in file is off by one. nobody needs to know we skipped the first frame */
hdr->frameNumber = frame_count - 1;
hdr->cropPosX = (skip_x + 7) & ~7;
hdr->cropPosY = skip_y & ~1;
hdr->panPosX = skip_x;
hdr->panPosY = skip_y;
void* ptr = (void*)((int32_t)hdr + sizeof(mlv_vidf_hdr_t) + hdr->frameSpace);
void* fullSizeBuffer = fullsize_buffers[(fullsize_buffer_pos+1) % 2];
/* advance to next buffer for the upcoming capture */
fullsize_buffer_pos = (fullsize_buffer_pos + 1) % 2;
/* dont process this frame if a module wants to skip that */
if(raw_rec_cbr_skip_frame(fullSizeBuffer))
{
return 0;
}
mlv_rec_dma_active = 1;
edmac_copy_rectangle_cbr_start(ptr, raw_info.buffer, raw_info.pitch, (skip_x+7)/8*14, skip_y/2*2, res_x*14/8, 0, 0, res_x*14/8, res_y, &mlv_rec_dma_cbr_r, &mlv_rec_dma_cbr_w, NULL);
mlv_rec_dma_start = get_us_clock_value();
/* copy current frame to our buffer and crop it to its final size */
slots[capture_slot].frame_number = frame_count;
trace_write(raw_rec_trace_ctx, "==> enqueue frame %d in slot %d DMA: %d us", frame_count, capture_slot, mlv_rec_dma_duration);
/* advance to next frame */
frame_count++;
return 1;
}
static unsigned int FAST raw_rec_vsync_cbr(unsigned int unused)
{
static uint32_t edmac_timeouts = 0;
/* just a counter for waiting x frames, decrease whenever non-zero */
if(frame_countdown)
{
frame_countdown--;
}
if(!mlv_video_enabled || !is_movie_mode())
{
return 0;
}
/* if previous DMA isn't finished yet, skip frame */
if(mlv_rec_dma_active)
{
trace_write(raw_rec_trace_ctx, "raw_rec_vsync_cbr: skipping frame due to slow EDMAC");
frame_skips++;
edmac_timeouts++;
/* safety measure: try to abort recording if too many frames were dropped at once */
if(edmac_timeouts > 10)
{
edmac_timeouts = 0;
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
}
return 0;
}
edmac_timeouts = 0;
hack_liveview_vsync();
/* panning window is updated when recording, but also when not recording */
panning_update();
if(!RAW_IS_RECORDING)
{
return 0;
}
if(!raw_lv_settings_still_valid())
{
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
return 0;
}
if(!allow_frame_skip && frame_skips)
{
return 0;
}
process_frame();
return 0;
}
static char *get_next_raw_movie_file_name()
{
static char filename[48];
static char videoname[48];
static char dirname[48];
struct tm now;
LoadCalendarFromRTC(&now);
for (int32_t number = 0; number < 100; number++)
{
/**
* Get unique file names from the current date/time
* last field gets incremented if there's another video with the same name
*/
snprintf(videoname, sizeof(videoname), "M%02d-%02d%02d", now.tm_mday, now.tm_hour, COERCE(now.tm_min + number, 0, 99));
if(create_dirs)
{
snprintf(dirname, sizeof(dirname), "%s/%s", get_dcim_dir(), videoname);
snprintf(filename, sizeof(filename), "%s/%s.MLV", dirname, videoname);
FIO_CreateDirectory(dirname);
}
else
{
snprintf(filename, sizeof(filename), "%s/%s.MLV", get_dcim_dir(), videoname);
}
/* when card spanning is enabled, primary writer is for CF, regardless which card is preferred */
if(card_spanning)
{
filename[0] = 'A';
}
trace_write(raw_rec_trace_ctx, "Base filename: '%s'", filename);
/* already existing file? */
uint32_t size;
if(FIO_GetFileSize( filename, &size ) != 0)
{
break;
}
if(size == 0)
{
break;
}
}
return filename;
}
static int32_t mlv_rec_get_chunk_filename(char* base_name, char* filename, int32_t chunk, uint32_t writer)
{
/* change file extension, according to chunk number: MLV, M00, M01 and so on */
strncpy(filename, base_name, MAX_PATH);
if(chunk > 0)
{
int32_t len = strlen(filename);
snprintf(filename + len - 2, 3, "%02d", chunk-1);
}
/* patch drive letter when using second writer (card spanning) */
if(writer == 1)
{
filename[0] = 'B';
}
return 0;
}
static int32_t mlv_write_hdr(FILE* f, mlv_hdr_t *hdr)
{
raw_rec_cbr_mlv_block(hdr);
uint32_t written = FIO_WriteFile(f, hdr, hdr->blockSize);
return written == hdr->blockSize;
}
static int32_t mlv_write_info(FILE* f)
{
mlv_info_hdr_t *hdr = malloc(1080);
char *str_pos = (char *)((uint32_t)hdr + sizeof(mlv_info_hdr_t));
int32_t ret = 0;
/* init empty string */
strcpy(str_pos, "");
/* if any text is given, set it first */
if(strlen(raw_tag_str) > 0)
{
strcpy(&str_pos[strlen(str_pos)], "text: ");
strcpy(&str_pos[strlen(str_pos)], raw_tag_str);
strcpy(&str_pos[strlen(str_pos)], "; ");
}
/* if take number is enabled, append it */
if(raw_tag_take)
{
char buf[32];
snprintf(buf, 32, "%d", raw_tag_take);
strcpy(&str_pos[strlen(str_pos)], "take: ");
strcpy(&str_pos[strlen(str_pos)], buf);
strcpy(&str_pos[strlen(str_pos)], "; ");
}
/* now build block header */
mlv_set_type((mlv_hdr_t *)hdr, "INFO");
mlv_set_timestamp((mlv_hdr_t *)hdr, mlv_start_timestamp);
hdr->blockSize = sizeof(mlv_info_hdr_t) + strlen(str_pos);
ret = mlv_write_hdr(f, (mlv_hdr_t *)hdr);
free(hdr);
return ret;
}
static void mlv_init_header()
{
/* recording start timestamp */
mlv_start_timestamp = mlv_set_timestamp(NULL, 0);
/* setup header */
mlv_init_fileheader(&mlv_file_hdr);
mlv_file_hdr.fileGuid = mlv_generate_guid();
mlv_file_hdr.fileNum = 0;
mlv_file_hdr.fileCount = 0;
mlv_file_hdr.fileFlags = 1;
/* for now only raw video, no sound */
mlv_file_hdr.videoClass = 1;
mlv_file_hdr.audioClass = 0;
mlv_file_hdr.videoFrameCount = 0;
mlv_file_hdr.audioFrameCount = 0;
/* can be improved to make use of nom/denom */
mlv_file_hdr.sourceFpsNom = fps_get_current_x1000();
mlv_file_hdr.sourceFpsDenom = 1000;
}
static int32_t mlv_write_rawi(FILE* f, struct raw_info raw_info)
{
mlv_rawi_hdr_t rawi;
mlv_set_type((mlv_hdr_t *)&rawi, "RAWI");
mlv_set_timestamp((mlv_hdr_t *)&rawi, mlv_start_timestamp);
rawi.blockSize = sizeof(mlv_rawi_hdr_t);
rawi.xRes = res_x;
rawi.yRes = res_y;
rawi.raw_info = raw_info;
return mlv_write_hdr(f, (mlv_hdr_t *)&rawi);
}
static uint32_t find_largest_buffer(uint32_t start_group, write_job_t *write_job, uint32_t max_size)
{
write_job_t job;
uint32_t get_partial = 0;
retry_find:
/* initialize write job */
memset(&job, 0x00, sizeof(write_job_t));
*write_job = job;
for (int32_t group = start_group; group < slot_group_count; group++)
{
uint32_t block_len = 0;
uint32_t block_start = 0;
uint32_t block_size = 0;
uint32_t group_full = 1;
for (int32_t slot = slot_groups[group].slot; slot < (slot_groups[group].slot + slot_groups[group].len); slot++)
{
/* check for the slot being ready for saving */
if(slots[slot].status == SLOT_FULL)
{
/* the first time or on a non contiguous area reset all counters */
if(block_len == 0)
{
block_start = slot;
}
block_len++;
block_size += slots[slot].size;
/* we have a new candidate */
if(block_len > job.block_len)
{
job.block_start = block_start;
job.block_len = block_len;
job.block_size = block_size;
job.block_ptr = slots[block_start].ptr;
}
}
else
{
group_full = 0;
block_len = 0;
block_size = 0;
block_start = 0;
}
/* already over the maximum write block size? then break now */
if(max_size && job.block_size >= max_size)
{
break;
}
}
/* methods 3 and 4 want the "fast card" buffers to fill before queueing */
if(buffer_fill_method == 3 || buffer_fill_method == 4)
{
/* the queued group is not ready to be queued yet, reset */
if(!group_full && (group < fast_card_buffers) && !get_partial)
{
memset(&job, 0x00, sizeof(write_job_t));
}
}
/* if the current group has more frames, use it */
if(job.block_len > write_job->block_len)
{
*write_job = job;
}
}
/* if nothing was found, even a partially filled buffer is better than nothing */
if(write_job->block_len == 0 && !get_partial)
{
get_partial = 1;
goto retry_find;
}
/* if we were able to locate blocks for writing, return 1 */
return (write_job->block_len > 0);
}
static uint32_t raw_get_next_filenum()
{
uint32_t fileNum = 0;
uint32_t old_int = cli();
fileNum = mlv_file_hdr.fileCount;
mlv_file_hdr.fileCount++;
sei(old_int);
return fileNum;
}
static void raw_prepare_chunk(FILE *f, mlv_file_hdr_t *hdr)
{
if(f == NULL)
{
return;
}
mlv_write_hdr(f, (mlv_hdr_t *)hdr);
/* fill and write camera and lens information */
if(hdr->fileNum == 0)
{
mlv_write_rawi(f, raw_info);
mlv_write_info(f);
mlv_rtci_hdr_t rtci_hdr;
mlv_expo_hdr_t expo_hdr;
mlv_lens_hdr_t lens_hdr;
mlv_idnt_hdr_t idnt_hdr;
mlv_wbal_hdr_t wbal_hdr;
mlv_styl_hdr_t styl_hdr;
mlv_fill_rtci(&rtci_hdr, mlv_start_timestamp);
mlv_fill_expo(&expo_hdr, mlv_start_timestamp);
mlv_fill_lens(&lens_hdr, mlv_start_timestamp);
mlv_fill_idnt(&idnt_hdr, mlv_start_timestamp);
mlv_fill_wbal(&wbal_hdr, mlv_start_timestamp);
mlv_fill_styl(&styl_hdr, mlv_start_timestamp);
/* ensure that these come right after header. quite hackish but necessary */
rtci_hdr.timestamp = 1;
expo_hdr.timestamp = 2;
lens_hdr.timestamp = 3;
idnt_hdr.timestamp = 4;
wbal_hdr.timestamp = 5;
styl_hdr.timestamp = 6;
mlv_write_hdr(f, (mlv_hdr_t *)&rtci_hdr);
mlv_write_hdr(f, (mlv_hdr_t *)&expo_hdr);
mlv_write_hdr(f, (mlv_hdr_t *)&lens_hdr);
mlv_write_hdr(f, (mlv_hdr_t *)&idnt_hdr);
mlv_write_hdr(f, (mlv_hdr_t *)&wbal_hdr);
mlv_write_hdr(f, (mlv_hdr_t *)&styl_hdr);
}
}
static void raw_writer_task(uint32_t writer)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: starting", writer);
struct msg_queue *queue = mlv_writer_queues[writer];
char *error_message = "Huh? Which error?";
/* keep it local to make sure it is getting optimized */
FILE* f = mlv_handles[writer];
FILE* next_file_handle = NULL;
/* used to calculate the execution time of management code */
int64_t last_time_after = 0;
/* number of frames in current chunk */
uint32_t frames_written = 0;
/* in bytes, for current chunk */
uint32_t written_chunk = 0;
/* flag to know if we already requested a new file handle */
uint32_t handle_requested = 0;
uint32_t next_file_num = 0;
char next_filename[MAX_PATH];
/* use global file header and update local fields */
mlv_file_hdr_t file_header;
file_header = mlv_file_hdr;
file_header.videoFrameCount = 0;
file_header.audioFrameCount = 0;
/* update file count */
file_header.fileNum = writer;
written_chunk = FIO_SeekSkipFile(f, 0, SEEK_CUR);
util_atomic_inc(&mlv_rec_threads);
while(raw_recording_state == RAW_PREPARING)
{
msleep(20);
}
/* main recording loop */
TASK_LOOP
{
write_job_t *job = NULL;
/* receive write job from dispatcher */
if(msg_queue_receive(queue, &job, 1000))
{
//static uint32_t timeouts = 0;
//trace_write(raw_rec_trace_ctx, " --> WRITER#%d: message timed out %d times now", writer, ++timeouts);
continue;
}
if(!job)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: job is NULL");
error_message = "Internal error #1";
goto abort;
}
if(job->job_type == JOB_TYPE_WRITE)
{
/* decrease number of queued writes */
util_atomic_dec(&writer_job_count[writer]);
/* this is an "abort" job */
if(job->block_len == 0)
{
msg_queue_post(mlv_job_alloc_queue, (uint32_t) job);
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: expected to terminate", writer);
break;
}
//trace_write(raw_rec_trace_ctx, " --> WRITER#%d: write %d slots from %d (%dKiB, addr 0x%08X, size 0x%08X)", writer, job->block_len, job->block_start, job->block_size/1024, job->block_ptr, job->block_size);
/* ToDo: ask an optional external routine if this buffer should get saved now. if none registered, it will return 1 */
if(1)
{
#if defined(EMBEDDED_LOGGING)
if(writer == 0)
{
/* instantly embed DEBG messages, if any */
mlv_debg_hdr_t *debg_hdr = mlv_rec_queue_debg();
if(debg_hdr)
{
int32_t debg_written = FIO_WriteFile(f, debg_hdr, debg_hdr->blockSize);
if(debg_written != (int32_t)debg_hdr->blockSize)
{
mlv_rec_requeue_debg(debg_hdr);
}
else
{
free(debg_hdr);
}
}
}
#endif
if(!large_file_support)
{
/* check if we will reach the 4GiB boundary with this write */
uint32_t free_space = mlv_max_filesize - written_chunk;
uint32_t limit = 8 * job->block_size;
//trace_write(raw_rec_trace_ctx, " --> WRITER#%d: free: 0x%08x limit: 0x%08x", writer, free_space, limit);
if(free_space < job->block_size)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: reached 4GiB, queuing close of '%s'", writer, chunk_filename[writer]);
/* rewrite header */
file_header.videoFrameCount = frames_written;
/* queue a close command */
close_job_t *close_job = NULL;
msg_queue_receive(mlv_job_alloc_queue, &close_job, 0);
if(!close_job)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: close_job is NULL", writer);
error_message = "Internal error #2";
goto abort;
}
close_job->job_type = JOB_TYPE_CLOSE;
close_job->writer = writer;
close_job->file_handle = f;
close_job->file_header = file_header;
strncpy(close_job->filename, chunk_filename[writer], MAX_PATH);
msg_queue_post(mlv_mgr_queue, (uint32_t) close_job);
/* this should never happen, as the main queue handler should take care of us */
if(!next_file_handle)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: no chunk prepared", writer);
error_message = "Internal error #3";
goto abort;
}
/* update file handle */
f = next_file_handle;
next_file_handle = NULL;
/* also update filename */
strncpy(chunk_filename[writer], next_filename, MAX_PATH);
strncpy(next_filename, "", MAX_PATH);
frames_written = 0;
FIO_SeekSkipFile(f, 0, SEEK_END);
written_chunk = FIO_SeekSkipFile(f, 0, SEEK_CUR);
/* write next header */
file_header.fileNum = next_file_num;
file_header.videoFrameCount = 0;
file_header.audioFrameCount = 0;
handle_requested = 0;
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: reached 4GiB, next chunk is '%s', %d", writer, chunk_filename[writer], f);
}
else if(free_space < limit)
{
if(!handle_requested)
{
/* we will reach the 4GiB boundary soon */
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: close to 4GiB (0x%08x), request another chunk", writer, free_space);
/* queue a preparation job */
handle_job_t *prepare_job = NULL;
msg_queue_receive(mlv_job_alloc_queue, &prepare_job, 0);
if(!prepare_job)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: prepare_job is NULL", writer);
error_message = "Internal error #4";
goto abort;
}
prepare_job->job_type = JOB_TYPE_NEXT_HANDLE;
prepare_job->writer = writer;
prepare_job->file_handle = NULL;
prepare_job->file_header = file_header;
prepare_job->filename[0] = '\000';
msg_queue_post(mlv_mgr_queue, (uint32_t) prepare_job);
handle_requested = 1;
}
else
{
/* recheck? no dont think thats necessary */
}
}
}
/* start write and measure times */
job->last_time_after = last_time_after;
job->time_before = get_us_clock_value();
job->file_offset = FIO_SeekSkipFile(f, 0, SEEK_CUR);
int32_t written = FIO_WriteFile(f, job->block_ptr, job->block_size);
job->time_after = get_us_clock_value();
last_time_after = job->time_after;
/* handle disk full cases */
if(written != (int32_t)job->block_size) /* 4GB limit or card full? */
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: write error: %d", writer, written);
/* it failed right away? card must be full */
if(written_chunk == 0)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: write error: could not write anything, exiting", writer);
}
else if(written == -1)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: write error: write failed", writer);
}
else
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: write error: write failed, wrote only partially (%d/%d bytes)", writer, written, job->block_size);
}
/* okay, writing failed. now try to save what we have by relasing the dummy file */
mlv_rec_release_dummies();
/* if the whole write call failed, nothing would have been saved */
if(written < 0)
{
written = 0;
}
/* now try to write the remaining buffer content */
written = FIO_WriteFile(f, &((char *)job->block_ptr)[written], job->block_size - written);
if(written != (int32_t)(job->block_size - written)) /* 4GB limit or card full? */
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: Even writing after removing dummy file failed. No idea what to do now.", writer);
if(large_file_support)
{
error_message = "Card/camera really exFAT?";
}
else
{
error_message = "Card/Filesystem error?";
}
}
else
{
error_message = "Card seems to be full";
}
goto abort;
}
/* all fine */
written_chunk += job->block_size;
frames_written += job->block_len;
}
/* send job back and wake up manager */
msg_queue_post(mlv_mgr_queue, (uint32_t) job);
//trace_write(raw_rec_trace_ctx, " --> WRITER#%d: returned job 0x%08X", writer, job);
}
else if(job->job_type == JOB_TYPE_NEXT_HANDLE)
{
handle_job_t *prepare_job = (handle_job_t *)job;
next_file_handle = prepare_job->file_handle;
next_file_num = prepare_job->file_header.fileNum;
strncpy(next_filename, prepare_job->filename, MAX_PATH);
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: next chunk handle received, file '%s'", writer, next_filename );
}
else
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: unhandled job 0x%08X", writer, job->job_type);
bmp_printf(FONT(FONT_MED, COLOR_RED, COLOR_BLACK), 10, 300, "WRITER#%d: unhandled job 0x%08X", writer, job->job_type);
error_message = "Internal error ";
goto abort;
}
/* error handling */
if (0)
{
abort:
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
NotifyBox(5000, "Recording stopped:\n '%s'", error_message);
/* this is error beep, not audio sync beep */
beep_times(2);
break;
}
}
if (f)
{
file_header.videoFrameCount = frames_written;
FIO_SeekSkipFile(f, 0, SEEK_SET);
mlv_write_hdr(f, (mlv_hdr_t *)&file_header);
FIO_CloseFile(f);
}
util_atomic_dec(&mlv_rec_threads);
}
static void enqueue_buffer(uint32_t writer, write_job_t *write_job)
{
/* if we are about to overflow, save a smaller number of frames, so they can be freed quicker */
if (measured_write_speed)
{
int32_t fps = fps_get_current_x1000();
/* measured_write_speed unit: 0.01 MB/s */
/* FPS unit: 0.001 Hz */
/* overflow time unit: 0.1 seconds */
int32_t free_slots = get_free_slots();
int32_t overflow_time = free_slots * 1000 * 10 / fps;
/* better underestimate write speed a little */
int32_t frame_limit = overflow_time * 1024 / 10 * (measured_write_speed * 9 / 100) * 1024 / (write_job->block_size / write_job->block_len) / 10;
/* do not decrease write size if skipping is allowed */
if (!allow_frame_skip && frame_limit >= 0 && frame_limit < (int32_t)write_job->block_len)
{
//trace_write(raw_rec_trace_ctx, "<-- careful, will overflow in %d.%d seconds, better write only %d frames", overflow_time/10, overflow_time%10, frame_limit);
write_job->block_len = MAX(1, frame_limit - 1);
write_job->block_size = 0;
/* now fix the buffer size to write */
for(uint32_t slot = write_job->block_start; slot < write_job->block_start + write_job->block_len; slot++)
{
write_job->block_size += slots[slot].size;
}
}
}
/* mark slots to be written */
for(uint32_t slot = write_job->block_start; slot < (write_job->block_start + write_job->block_len); slot++)
{
/* write blocks if some were queued */
static int32_t queued = 0;
static int32_t failed = 0;
uint32_t msg_count = 0;
/* check if there is a block that should get embedded */
msg_queue_count(mlv_block_queue, &msg_count);
/* embed what is possible */
for(uint32_t msg = 0; msg < msg_count; msg++)
{
mlv_hdr_t *block = NULL;
/* there is a block in the queue, try to get that block */
if(msg_queue_receive(mlv_block_queue, &block, 0))
{
bmp_printf(FONT_MED, 0, 400, "MESSAGE RECEIVE ERROR!!");
}
else
{
raw_rec_cbr_mlv_block(block);
/* prepend the given block if possible or requeue it in case of error */
int32_t ret = mlv_prepend_block(slot, block);
if(!ret)
{
queued++;
free(block);
}
else
{
failed++;
msg_queue_post(mlv_block_queue, (uint32_t) block);
bmp_printf(FONT_MED, 0, 430, "FAILED. queued: %d failed: %d (requeued)", queued, failed);
break;
}
}
}
slots[slot].status = SLOT_WRITING;
slots[slot].writer = writer;
}
/* enqueue the configured block */
write_job_t *queue_job = NULL;
msg_queue_receive(mlv_job_alloc_queue, &queue_job, 0);
if(!queue_job)
{
trace_write(raw_rec_trace_ctx, " --> WRITER#%d: queue_job is NULL");
return;
}
*queue_job = *write_job;
queue_job->job_type = JOB_TYPE_WRITE;
queue_job->writer = writer;
msg_queue_post(mlv_writer_queues[writer], (uint32_t) queue_job);
//trace_write(raw_rec_trace_ctx, "<-- POST: group with %d entries at %d (%dKiB) for slow card", write_job->block_len, write_job->block_start, write_job->block_size/1024);
}
/* check if the given file is empty (no MLV header) and delete it if it is */
static uint32_t mlv_rec_precreate_del_empty(char *filename)
{
uint32_t size;
if(FIO_GetFileSize(filename, &size) != 0)
{
return 0;
}
/* if only the size of a file header, remove again */
if(size <= sizeof(mlv_file_hdr_t))
{
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_del_empty: '%s' empty, deleting", filename);
FIO_RemoveFile(filename);
return 1;
}
return 0;
}
static void mlv_rec_precreate_cleanup(char *base_filename, uint32_t count)
{
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_cleanup: check '%s'", base_filename);
for(uint32_t pos = 0; pos < count; pos++)
{
char filename[MAX_PATH];
mlv_rec_get_chunk_filename(base_filename, filename, pos, 0);
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_cleanup: --> '%s'", filename);
mlv_rec_precreate_del_empty(filename);
if(card_spanning)
{
mlv_rec_get_chunk_filename(base_filename, filename, pos, 1);
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_cleanup: --> '%s'", filename);
mlv_rec_precreate_del_empty(filename);
}
}
}
static void mlv_rec_precreate_files(char *base_filename, uint32_t count, mlv_file_hdr_t main_hdr)
{
for(uint32_t pos = 0; pos < count; pos++)
{
char filename[MAX_PATH];
FILE *handle = NULL;
mlv_file_hdr_t hdr = main_hdr;
hdr.fileNum = pos;
mlv_rec_get_chunk_filename(base_filename, filename, pos, 0);
handle = FIO_CreateFile(filename);
raw_prepare_chunk(handle, &hdr);
FIO_CloseFile(handle);
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_files: '%s' created", filename);
/* dont create the .mlv on the second card */
if(card_spanning && pos > 0)
{
mlv_rec_get_chunk_filename(base_filename, filename, pos, 1);
handle = FIO_CreateFile(filename);
raw_prepare_chunk(handle, &hdr);
FIO_CloseFile(handle);
trace_write(raw_rec_trace_ctx, "mlv_rec_precreate_files: '%s' created", filename);
}
}
}
static void mlv_rec_wait_frames(uint32_t frames)
{
frame_countdown = frames;
for(int32_t i = 0; i < 200; i++)
{
msleep(20);
if(frame_countdown == 0)
{
break;
}
}
}
static void mlv_rec_queue_blocks()
{
if(mlv_update_lens && (mlv_metadata & MLV_METADATA_SPORADIC))
{
mlv_update_lens = 0;
mlv_expo_hdr_t old_expo = last_expo_hdr;
mlv_lens_hdr_t old_lens = last_lens_hdr;
mlv_fill_expo(&last_expo_hdr, mlv_start_timestamp);
mlv_fill_lens(&last_lens_hdr, mlv_start_timestamp);
/* update timestamp for comparing content changes */
old_expo.timestamp = last_expo_hdr.timestamp;
old_lens.timestamp = last_lens_hdr.timestamp;
/* write new state if something changed */
if(memcmp(&last_expo_hdr, &old_expo, sizeof(mlv_expo_hdr_t)))
{
mlv_hdr_t *hdr = malloc(sizeof(mlv_expo_hdr_t));
memcpy(hdr, &last_expo_hdr, sizeof(mlv_expo_hdr_t));
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
/* write new state if something changed */
if(memcmp(&last_lens_hdr, &old_lens, sizeof(mlv_lens_hdr_t)))
{
mlv_hdr_t *hdr = malloc(sizeof(mlv_lens_hdr_t));
memcpy(hdr, &last_lens_hdr, sizeof(mlv_lens_hdr_t));
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
}
if(mlv_update_styl && (mlv_metadata & MLV_METADATA_SPORADIC))
{
mlv_update_styl = 0;
mlv_styl_hdr_t old_hdr = last_styl_hdr;
mlv_fill_styl(&last_styl_hdr, mlv_start_timestamp);
/* update timestamp for comparing content changes */
old_hdr.timestamp = last_styl_hdr.timestamp;
/* write new state if something changed */
if(memcmp(&last_styl_hdr, &old_hdr, sizeof(mlv_styl_hdr_t)))
{
mlv_hdr_t *hdr = malloc(sizeof(mlv_styl_hdr_t));
memcpy(hdr, &last_styl_hdr, sizeof(mlv_styl_hdr_t));
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
}
if(mlv_update_wbal && (mlv_metadata & MLV_METADATA_SPORADIC))
{
mlv_update_wbal = 0;
/* capture last state and get new one */
mlv_wbal_hdr_t old_hdr = last_wbal_hdr;
mlv_fill_wbal(&last_wbal_hdr, mlv_start_timestamp);
/* update timestamp for comparing content changes */
old_hdr.timestamp = last_wbal_hdr.timestamp;
/* write new state if something changed */
if(memcmp(&last_wbal_hdr, &old_hdr, sizeof(mlv_wbal_hdr_t)))
{
mlv_hdr_t *hdr = malloc(sizeof(mlv_wbal_hdr_t));
memcpy(hdr, &last_wbal_hdr, sizeof(mlv_wbal_hdr_t));
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
}
}
static void raw_video_rec_task()
{
/* init stuff */
raw_recording_state = RAW_PREPARING;
if(DISPLAY_REC_INFO_DEBUG)
{
bmp_printf(FONT_MED, 30, 50, "Prepare recording...");
}
else if(DISPLAY_REC_INFO_ICON)
{
uint32_t width = bfnt_draw_char(ICON_ML_MOVIE, MLV_ICON_X, MLV_ICON_Y, COLOR_WHITE, COLOR_BG_DARK);
bmp_printf(FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), MLV_ICON_X + width, MLV_ICON_Y + 5, "Prepare");
}
/* make sure tracing is active */
raw_rec_setup_trace();
/* wait for two frames to be sure everything is refreshed */
mlv_rec_wait_frames(5);
/* detect raw parameters (geometry, black level etc) */
raw_set_dirty();
if (!mlv_rec_update_raw())
{
NotifyBox(5000, "Raw detect error");
goto cleanup;
}
trace_write(raw_rec_trace_ctx, "Resolution: %dx%d @ %d.%03d FPS", res_x, res_y, fps_get_current_x1000()/1000, fps_get_current_x1000()%1000);
/* disable Canon's powersaving (30 min in LiveView) */
powersave_prohibit();
/* signal that we are starting, call this before any memory allocation to give CBR the chance to allocate memory */
raw_rec_cbr_starting();
/* allocate memory */
if(!setup_buffers())
{
trace_write(raw_rec_trace_ctx, "Failed to setup. Card/RAM full?");
NotifyBox(5000, "Failed to setup. Card/RAM full?");
beep();
goto cleanup;
}
msleep(start_delay * 1000);
hack_liveview(0);
do
{
/* get exclusive access to our edmac channels */
edmac_memcpy_res_lock();
clrscr();
write_job_t *write_job = NULL;
frame_count = 0;
frame_skips = 0;
mlv_file_count = 0;
capture_slot = -1;
fullsize_buffer_pos = 0;
/* setup MLV stuff */
mlv_init_header();
/* wait for a few frames again to prevent some hickups going into footage */
mlv_rec_wait_frames(5);
/* fake recording status, to integrate with other ml stuff (e.g. hdr video */
set_recording_custom(CUSTOM_RECORDING_RAW);
/* create output file name */
mlv_movie_filename = get_next_raw_movie_file_name();
if(card_spanning)
{
mlv_writer_threads = 2;
}
else
{
mlv_writer_threads = 1;
}
/* create all possible files with an reference header */
mlv_rec_precreate_files(mlv_movie_filename, MAX_PRECREATE_FILES, mlv_file_hdr);
/* open files for writers */
for(uint32_t writer = 0; writer < mlv_writer_threads; writer++)
{
written[writer] = 0;
frames_written[writer] = 0;
writing_time[writer] = 0;
idle_time[writer] = 0;
writer_job_count[writer] = 0;
mlv_handles[writer] = NULL;
mlv_rec_get_chunk_filename(mlv_movie_filename, chunk_filename[writer], writer, writer);
trace_write(raw_rec_trace_ctx, "Filename (Thread #%d): '%s'", writer, chunk_filename[writer]);
mlv_handles[writer] = FIO_OpenFile(chunk_filename[writer], O_RDWR | O_SYNC);
/* failed to open? */
if(!mlv_handles[writer])
{
trace_write(raw_rec_trace_ctx, "FIO_CreateFile(#%d): FAILED", writer);
NotifyBox(5000, "Failed to create file. Card full?");
beep_times(2);
return;
}
mlv_file_hdr.fileCount++;
trace_write(raw_rec_trace_ctx, " (CUR 0x%08X, END 0x%08X)", FIO_SeekSkipFile(mlv_handles[writer], 0, SEEK_CUR), FIO_SeekSkipFile(mlv_handles[writer], 0, SEEK_END));
}
/* create writer threads with decreasing priority */
for(uint32_t writer = 0; writer < mlv_writer_threads; writer++)
{
uint32_t base_prio = 0x12;
task_create("writer_thread", base_prio + writer, 0x1000, raw_writer_task, (void*)writer);
}
/* wait a bit to make sure threads are running */
uint32_t thread_wait = 10;
while(mlv_rec_threads != mlv_writer_threads)
{
thread_wait--;
if(!thread_wait)
{
NotifyBox(5000, "Threads failed to start");
trace_write(raw_rec_trace_ctx, "Threads failed to start");
beep_times(2);
return;
}
msleep(100);
}
uint32_t used_slots = 0;
uint32_t writing_slots = 0;
uint32_t queued_writes = 0;
/* this will enable the vsync CBR and the other task(s) */
raw_recording_state = RAW_RECORDING;
/* some modules may do some specific stuff right when we started recording */
raw_rec_cbr_started();
while((raw_recording_state == RAW_RECORDING) || (used_slots > 0))
{
/* on shutdown or writers that aborted, abort even if there are unwritten slots */
if(ml_shutdown_requested || !mlv_rec_threads)
{
/* exclusive edmac access no longer needed */
edmac_memcpy_res_unlock();
set_recording_custom(CUSTOM_RECORDING_NOT_RECORDING);
goto cleanup;
}
/* here we receive a previously sent job back. process it after refilling the queue */
write_job_t *returned_job = NULL;
int timeout = 500;
/* if there are no frames avaiable for write or writers are idle, wait not that long */
if(used_slots == 0 || queued_writes == 0)
{
timeout = 30;
}
if(msg_queue_receive(mlv_mgr_queue, &returned_job, timeout))
{
returned_job = NULL;
}
/* when capture task had to skip a frame, stop recording */
if (!allow_frame_skip && frame_skips && (raw_recording_state == RAW_RECORDING))
{
NotifyBox(5000, "Frame skipped. Stopping");
trace_write(raw_rec_trace_ctx, "<-- stopped recording, frame was skipped");
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
}
/* how fast are we writing? does this speed match our benchmarks? */
int32_t temp_speed = 0;
for(uint32_t writer = 0; writer < mlv_writer_threads; writer++)
{
if(writing_time[writer] || idle_time[writer])
{
/* punctual use of floating point as there is a narrow band of accuracy vs. overflows in integer arithmetics */
float speed = (float)written[writer] / (float)writing_time[writer] * (1000.0f / 1024.0f * 100.0f); // KiB and msec -> MiB/s x100
temp_speed += (uint32_t) speed;
}
}
measured_write_speed = temp_speed;
/* check CF queue */
if(writer_job_count[0] < 1)
{
write_job_t write_job;
//trace_write(raw_rec_trace_ctx, "<-- No jobs in fast-card queue");
/* in case there is something to write... */
if(find_largest_buffer(0, &write_job, 16 * 1024 * 1024))
{
enqueue_buffer(0, &write_job);
util_atomic_inc(&writer_job_count[0]);
}
else
{
//trace_write(raw_rec_trace_ctx, "<-- (nothing found to enqueue)");
}
}
/* check SD queue */
if((mlv_writer_threads > 1) && (writer_job_count[1] < 1))
{
write_job_t write_job;
//trace_write(raw_rec_trace_ctx, "<-- No jobs in slow-card queue");
/* in case there is something to write... SD must not use the two largest buffers */
if(find_largest_buffer(fast_card_buffers, &write_job, 4 * 1024 * 1024))
{
enqueue_buffer(1, &write_job);
util_atomic_inc(&writer_job_count[1]);
}
else
{
//trace_write(raw_rec_trace_ctx, "<-- (nothing found to enqueue)");
}
}
/* a writer finished and we have to update statistics etc */
if(returned_job)
{
if(returned_job->job_type == JOB_TYPE_WRITE)
{
//trace_write(raw_rec_trace_ctx, "<-- processing returned_job 0x%08X from %d", returned_job, returned_job->writer);
/* set all slots as free again */
for(uint32_t slot = returned_job->block_start; slot < (returned_job->block_start + returned_job->block_len); slot++)
{
slots[slot].status = SLOT_FREE;
//trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: free slot %d", returned_job->writer, slot);
}
/* calc writing and idle time */
int32_t write_time = (uint32_t)(returned_job->time_after - returned_job->time_before);
int32_t mgmt_time = 0;
/* wait until first block is written before counting */
if(returned_job->last_time_after)
{
mgmt_time = (uint32_t)(returned_job->time_before - returned_job->last_time_after);
}
int32_t rate = (int32_t)(((int64_t)returned_job->block_size * 1000000ULL / (int64_t)write_time) / 1024ULL);
/* hack working for one writer only */
current_write_speed[returned_job->writer] = rate*100/1024;
trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: write took: %8d µs (%6d KiB/s), %9d bytes, %3d blocks, slot %3d, mgmt %6d µs, offset 0x%08X",
returned_job->writer, write_time, rate, returned_job->block_size, returned_job->block_len, returned_job->block_start, mgmt_time, returned_job->file_offset);
/* update statistics */
writing_time[returned_job->writer] += write_time / 1000;
idle_time[returned_job->writer] += mgmt_time / 1000;
written[returned_job->writer] += returned_job->block_size / 1024;
frames_written[returned_job->writer] += returned_job->block_len;
msg_queue_post(mlv_job_alloc_queue, (uint32_t) returned_job);
}
else if(returned_job->job_type == JOB_TYPE_NEXT_HANDLE)
{
handle_job_t *handle = (handle_job_t*)returned_job;
/* open the next file */
int32_t filenum = raw_get_next_filenum();
mlv_rec_get_chunk_filename(mlv_movie_filename, handle->filename, filenum, handle->writer);
trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: prepare new file #%d: '%s'", handle->writer, filenum, handle->filename);
handle->file_handle = FIO_OpenFile(handle->filename, O_RDWR | O_SYNC);
/* failed to open? */
if(!handle->file_handle)
{
/* we probably ran out of precreated files, create one now which is a bit more expensive */
handle->file_handle = FIO_CreateFile(handle->filename);
if(!handle->file_handle)
{
NotifyBox(5000, "Failed to create new file. Card full?");
trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: prepare new file: '%s' FAILED", handle->writer, handle->filename);
/* try to free up some space and exit */
mlv_rec_release_dummies();
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
}
raw_prepare_chunk(handle->file_handle, &handle->file_header);
}
trace_write(raw_rec_trace_ctx, " (CUR 0x%08X, END 0x%08X)", FIO_SeekSkipFile(handle->file_handle, 0, SEEK_CUR), FIO_SeekSkipFile(handle->file_handle, 0, SEEK_END));
/* requeue job again, the writer will care for it */
msg_queue_post(mlv_writer_queues[handle->writer], (uint32_t) handle);
}
else if(returned_job->job_type == JOB_TYPE_CLOSE)
{
close_job_t *handle = (close_job_t*)returned_job;
trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: close file '%s'", handle->writer, handle->filename);
FIO_SeekSkipFile(handle->file_handle, 0, SEEK_SET);
mlv_write_hdr(handle->file_handle, (mlv_hdr_t *)&(handle->file_header));
FIO_CloseFile(handle->file_handle);
/* "free" that job buffer again */
msg_queue_post(mlv_job_alloc_queue, (uint32_t) handle);
}
returned_job = NULL;
}
/* update some statistics. do this last, to make sure the writers have enough jobs */
queued_writes = writer_job_count[0] + writer_job_count[1];
used_slots = 0;
writing_slots = 0;
for(int32_t slot = 0; slot < slot_count; slot++)
{
if(slots[slot].status != SLOT_FREE)
{
used_slots++;
}
if(slots[slot].status == SLOT_WRITING)
{
writing_slots++;
}
}
//trace_write(raw_rec_trace_ctx, "Slots used: %d, writing: %d", used_slots, writing_slots);
mlv_rec_queue_blocks();
if((raw_recording_state != RAW_RECORDING) && (show_graph))
{
show_buffer_status();
}
}
/* now close all queued files */
while(1)
{
write_job_t *returned_job = NULL;
if(msg_queue_receive(mlv_mgr_queue_close, &returned_job, 50))
{
break;
}
if(returned_job->job_type == JOB_TYPE_CLOSE)
{
close_job_t *handle = (close_job_t*)returned_job;
trace_write(raw_rec_trace_ctx, "<-- WRITER#%d: close file '%s'", handle->writer, handle->filename);
FIO_SeekSkipFile(handle->file_handle, 0, SEEK_SET);
mlv_write_hdr(handle->file_handle, (mlv_hdr_t *)&(handle->file_header));
FIO_CloseFile(handle->file_handle);
/* "free" that job buffer again */
msg_queue_post(mlv_job_alloc_queue, (uint32_t) handle);
}
}
/* delete all empty files */
mlv_rec_precreate_cleanup(mlv_movie_filename, MAX_PRECREATE_FILES);
/* wait until all jobs done */
int32_t has_data = 0;
do
{
/* on shutdown exit immediately */
if(ml_shutdown_requested || !mlv_rec_threads)
{
/* exclusive edmac access no longer needed */
edmac_memcpy_res_unlock();
set_recording_custom(CUSTOM_RECORDING_NOT_RECORDING);
goto cleanup;
}
if (show_graph)
{
show_buffer_status();
}
/* wait until all writers wrote their data */
has_data = 0;
for(int32_t slot = 0; slot < slot_count; slot++)
{
if(slots[slot].status == SLOT_WRITING)
{
has_data = 1;
}
}
if(has_data)
{
trace_write(raw_rec_trace_ctx, "<-- still have data to write...");
}
msleep(200);
} while(has_data && mlv_rec_threads);
/* done, this will stop the vsync CBR and the copying task */
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
/* queue two aborts to cancel tasks */
msg_queue_receive(mlv_job_alloc_queue, &write_job, 0);
write_job->job_type = JOB_TYPE_WRITE;
write_job->block_len = 0;
msg_queue_post(mlv_writer_queues[0], (uint32_t) write_job);
msg_queue_receive(mlv_job_alloc_queue, &write_job, 0);
write_job->job_type = JOB_TYPE_WRITE;
write_job->block_len = 0;
msg_queue_post(mlv_writer_queues[1], (uint32_t) write_job);
/* flush queues */
msleep(250);
/* exclusive edmac access no longer needed */
edmac_memcpy_res_unlock();
/* make sure all queues are empty */
flush_queue(mlv_writer_queues[0]);
flush_queue(mlv_writer_queues[1]);
flush_queue(mlv_block_queue);
flush_queue(mlv_mgr_queue);
flush_queue(mlv_mgr_queue_close);
/* wait until the other tasks calm down */
msleep(500);
set_recording_custom(CUSTOM_RECORDING_NOT_RECORDING);
trace_flush(raw_rec_trace_ctx);
} while(false);
cleanup:
/* signal that we are stopping */
raw_rec_cbr_stopped();
/*
if(DISPLAY_REC_INFO_DEBUG)
{
NotifyBox(5000, "Frames captured: %d", frame_count - 1);
}
*/
if(show_graph)
{
take_screenshot(SCREENSHOT_FILENAME_AUTO, SCREENSHOT_BMP);
}
trace_flush(raw_rec_trace_ctx);
free_buffers();
/* count up take number */
if(raw_tag_take)
{
raw_tag_take++;
}
hack_liveview(1);
redraw();
/* re-enable powersaving */
powersave_permit();
raw_recording_state = RAW_IDLE;
}
static MENU_SELECT_FUNC(raw_start_stop)
{
if (!RAW_IS_IDLE)
{
abort_test = 1;
raw_recording_state = RAW_FINISHING;
raw_rec_cbr_stopping();
}
else
{
raw_recording_state = RAW_PREPARING;
gui_stop_menu();
task_create("raw_rec_task", 0x19, 0x1000, raw_video_rec_task, (void*)0);
}
}
static IME_DONE_FUNC(raw_tag_str_done)
{
if(status == IME_OK)
{
strcpy(raw_tag_str, raw_tag_str_tmp);
}
return IME_OK;
}
/* LENS changes */
PROP_HANDLER( PROP_LV_LENS_STABILIZE )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_STROBO_AECOMP )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_ISO_AUTO )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_ISO )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_LV_LENS )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_APERTURE )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_APERTURE_AUTO )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_SHUTTER )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_SHUTTER_AUTO )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_BV )
{
mlv_update_lens = 1;
}
PROP_HANDLER( PROP_AE )
{
mlv_update_lens = 1;
}
/* STYL changes */
PROP_HANDLER( PROP_PICTURE_STYLE )
{
mlv_update_styl = 1;
}
/* any WBAL change */
PROP_HANDLER( PROP_WB_MODE_LV )
{
mlv_update_wbal = 1;
}
PROP_HANDLER( PROP_WBS_GM )
{
mlv_update_wbal = 1;
}
PROP_HANDLER( PROP_WBS_BA )
{
mlv_update_wbal = 1;
}
PROP_HANDLER( PROP_WB_KELVIN_LV )
{
mlv_update_wbal = 1;
}
PROP_HANDLER( PROP_CUSTOM_WB )
{
mlv_update_wbal = 1;
}
PROP_HANDLER(PROP_ROLLING_PITCHING_LEVEL)
{
struct rolling_pitching * orientation = (struct rolling_pitching *) buf;
if((RAW_IS_RECORDING && orientation->status == 2) && (mlv_metadata & MLV_METADATA_SPORADIC))
{
mlv_elvl_hdr_t *hdr = malloc(sizeof(mlv_elvl_hdr_t));
/* prepare header */
mlv_set_type((mlv_hdr_t *)hdr, "ELVL");
mlv_set_timestamp((mlv_hdr_t *)hdr, mlv_start_timestamp);
hdr->blockSize = sizeof(mlv_elvl_hdr_t);
/* fill in data */
hdr->roll = orientation->roll_hi * 256 + orientation->roll_lo;
hdr->pitch = orientation->pitch_hi * 256 + orientation->pitch_lo;
/* put into block queue */
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
}
static MENU_SELECT_FUNC(raw_tag_str_start)
{
strcpy(raw_tag_str_tmp, raw_tag_str);
ime_base_start((unsigned char *)"Enter text", (unsigned char *)raw_tag_str_tmp, sizeof(raw_tag_str_tmp)-1, IME_UTF8, IME_CHARSET_ANY, NULL, raw_tag_str_done, 0, 0, 0, 0);
}
static MENU_UPDATE_FUNC(raw_tag_str_update)
{
if(strlen(raw_tag_str))
{
MENU_SET_VALUE(raw_tag_str);
}
else
{
MENU_SET_VALUE("none");
}
}
static MENU_UPDATE_FUNC(raw_tag_take_update)
{
if(raw_tag_take)
{
MENU_SET_VALUE("#%d", raw_tag_take);
}
else
{
MENU_SET_VALUE("OFF");
}
}
static MENU_SELECT_FUNC(resolution_change_fine_value)
{
if (!mlv_video_enabled || !lv)
{
return;
}
if (get_menu_edit_mode()) {
/* preset resolution from pickbox */
resolution_index_x = MOD(resolution_index_x + delta, COUNT(resolution_presets_x));
res_x_fine = 0;
return;
}
/* fine-tune resolution in 32px increments */
uint32_t cur_res = resolution_presets_x[resolution_index_x] + res_x_fine;
if (delta < 0) cur_res = MIN(cur_res, max_res_x);
cur_res += delta * 32;
int last = COUNT(resolution_presets_x)-1;
cur_res = COERCE(cur_res, resolution_presets_x[0], resolution_presets_x[last]);
/* pick the closest preset */
resolution_index_x = 0;
while((resolution_index_x < (COUNT(resolution_presets_x) - 1)) && (resolution_presets_x[resolution_index_x+1] <= cur_res)) {
resolution_index_x += 1;
}
res_x_fine = cur_res - resolution_presets_x[resolution_index_x];
}
static struct menu_entry raw_video_menu[] =
{
{
.name = "RAW video (MLV)",
.priv = &mlv_video_enabled,
.max = 1,
.update = raw_main_update,
.submenu_width = 710,
.depends_on = DEP_LIVEVIEW | DEP_MOVIE_MODE,
.help = "Record 14-bit RAW video. Press LiveView to start.",
.children = (struct menu_entry[]) {
{
.name = "Resolution",
.priv = &resolution_index_x,
.max = COUNT(resolution_presets_x) - 1,
.select = resolution_change_fine_value,
.update = resolution_update,
.choices = RESOLUTION_CHOICES_X,
},
{
.name = "Aspect Ratio",
.priv = &aspect_ratio_index,
.max = COUNT(aspect_ratio_presets_num) - 1,
.update = aspect_ratio_update,
.choices = aspect_ratio_choices,
},
{
.name = "Create Directory",
.priv = &create_dirs,
.max = 1,
.help = "Save video chunks in separate folders.",
},
{
.name = "Global Draw",
.priv = &kill_gd,
.max = 1,
.choices = CHOICES("Allow", "OFF"),
.help = "Disable global draw while recording.",
.help2 = "May help with performance. Some previews depend on GD.",
},
{
.name = "Frame Skipping",
.priv = &allow_frame_skip,
.max = 1,
.choices = CHOICES("OFF", "Allow"),
.help = "Enable if you don't mind skipping frames (for slow cards).",
.help2 = "Be careful of stuttering footage.",
},
{
.name = "Preview Options",
.priv = &preview_mode,
.max = 4,
.choices = CHOICES("Auto", "Canon", "ML Grayscale", "HaCKeD", "Hacked No Prev"),
.help2 = "Auto: ML chooses what's best for each video mode.\n"
"Canon: Plain old LiveView. Framing is not always correct.\n"
"ML Grayscale: Looks ugly, but at least framing is correct.\n"
"HaCKeD: Try to squeeze a little speed by killing LiveView.\n"
"HaCKeD2: No preview. Disables Global draw while recording.\n"
},
{
.name = "Status When Recording",
.priv = &display_rec_info,
.max = 2,
.choices = CHOICES("None", "Icon", "Debug"),
.help = "Display status while recording.",
.help2 = "Display a small recording icon with basic information.\n"
"Display more information useful for debugging.\n"
},
{
.name = "Start Delay",
.priv = &start_delay_idx,
.max = 3,
.choices = CHOICES("OFF", "2 sec.", "4 sec.", "10 sec."),
.update = start_delay_update,
.help = "Start delay. Useful to stabilize in photo mode.",
.help2 = "Pressing shutter button.",
},
{
.name = "Files > 4GiB (exFAT)",
.priv = &large_file_support,
.max = 1,
.help = "Don't split files on 4GiB margins.",
.help2 = "Ensure your card is formatted as exFAT!"
},
{
.name = "Digital Dolly",
.priv = &dolly_mode,
.max = 1,
.help = "Smooth panning of the recording window (software dolly).",
.help2 = "Use arrow keys (joystick) to move the window."
},
{
.name = "Card Warm-up",
.priv = &warm_up,
.max = 7,
.choices = CHOICES("OFF", "16 MB", "32 MB", "64 MB", "128 MB", "256 MB", "512 MB", "1 GB"),
.help = "Write a large file on the card at camera startup.",
.help2 = "Some cards seem to get a bit faster after this.",
},
{
.name = "Use SRM Job Memory",
.priv = &use_srm_memory,
.max = 1,
.help = "Allocate memory from SRM job buffers.",
},
{
.name = "Extra Hacks",
.priv = &small_hacks,
.max = 1,
.help = "Slow down Canon GUI, lock digital expo while recording.",
.help2 = "May help with performance.",
},
{
.name = "Debug Trace",
.priv = &enable_tracing,
.max = 1,
.help = "Write an execution trace. Causes perfomance drop.",
.help2 = "You have to restart camera before setting takes effect.",
},
{
.name = "Show Buffer Graph",
.priv = &show_graph,
.max = 1,
.help = "Displays a graph of the current buffer usage and expected frames.",
},
{
.name = "Buffer Fill Method",
.priv = &buffer_fill_method,
.max = 4,
.help = "Method for filling buffers. Will affect write speed.",
.help2 = "Try different options for the best performance.",
},
{
.name = "CF-only Buffers",
.priv = &fast_card_buffers,
.max = 9,
.help = "How many of the largest buffers are for CF writing.",
},
{
.name = "Card Spanning",
.priv = &card_spanning,
.max = 1,
.help = "Span video file over cards to use SD+CF write speed.",
.help2 = "May increase performance.",
},
{
.name = "Reserve Card Space",
.priv = &create_dummy,
.max = 1,
.help = "Write a file to the card before recording.",
.help2 = "Use this to prevent data loss at card full.",
},
{
.name = "Tag: Text",
.priv = raw_tag_str,
.select = raw_tag_str_start,
.update = raw_tag_str_update,
.help = "Free text field.",
},
{
.name = "Tag: Take",
.priv = &raw_tag_take,
.min = 0,
.max = 99,
.update = raw_tag_take_update,
.help = "Auto-counting take number.",
},
MENU_EOL,
},
}
};
static unsigned int raw_rec_keypress_cbr(unsigned int key)
{
/* if module is disabled or canon is currently recording, return */
if (!mlv_video_enabled || RECORDING_H264)
return 1;
if (!is_movie_mode())
return 1;
/* keys are only hooked in LiveView */
if (!liveview_display_idle() && !RECORDING_RAW)
return 1;
/* if you somehow managed to start recording H.264, let it stop */
if (RECORDING_H264)
return 1;
/* block the zoom key while recording */
if (!RAW_IS_IDLE && key == MODULE_KEY_PRESS_ZOOMIN)
return 0;
/* start/stop recording with the LiveView key */
int32_t rec_key_pressed = (key == MODULE_KEY_LV || key == MODULE_KEY_REC);
/* ... or SET on 5D2/50D */
if (cam_50d || cam_5d2) rec_key_pressed = (key == MODULE_KEY_PRESS_SET);
if (rec_key_pressed)
{
switch(raw_recording_state)
{
case RAW_IDLE:
case RAW_RECORDING:
raw_start_stop(0,0);
break;
}
return 0;
}
/* panning (with arrow keys) */
if (FRAMING_PANNING)
{
switch (key)
{
case MODULE_KEY_PRESS_LEFT:
frame_offset_delta_x -= 8;
return 0;
case MODULE_KEY_PRESS_RIGHT:
frame_offset_delta_x += 8;
return 0;
case MODULE_KEY_PRESS_UP:
frame_offset_delta_y -= 2;
return 0;
case MODULE_KEY_PRESS_DOWN:
frame_offset_delta_y += 2;
return 0;
case MODULE_KEY_PRESS_DOWN_LEFT:
frame_offset_delta_y += 2;
frame_offset_delta_x -= 8;
return 0;
case MODULE_KEY_PRESS_DOWN_RIGHT:
frame_offset_delta_y += 2;
frame_offset_delta_x += 8;
return 0;
case MODULE_KEY_PRESS_UP_LEFT:
frame_offset_delta_y -= 2;
frame_offset_delta_x -= 8;
return 0;
case MODULE_KEY_PRESS_UP_RIGHT:
frame_offset_delta_y -= 2;
frame_offset_delta_x += 8;
return 0;
case MODULE_KEY_JOY_CENTER:
/* first click stop the motion, second click center the window */
if (frame_offset_delta_x || frame_offset_delta_y)
{
frame_offset_delta_y = 0;
frame_offset_delta_x = 0;
}
else
{
frame_offset_y = 0;
frame_offset_x = 0;
}
}
}
/* nothing to handle, log keypress */
if((raw_recording_state == RAW_RECORDING) && (mlv_metadata & MLV_METADATA_SPORADIC))
{
mlv_mark_hdr_t *hdr = malloc(sizeof(mlv_mark_hdr_t));
/* prepare header */
mlv_set_type((mlv_hdr_t *)hdr, "MARK");
mlv_set_timestamp((mlv_hdr_t *)hdr, mlv_start_timestamp);
hdr->blockSize = sizeof(mlv_mark_hdr_t);
hdr->type = key;
msg_queue_post(mlv_block_queue, (uint32_t) hdr);
}
return 1;
}
static int32_t preview_dirty = 0;
static uint32_t raw_rec_should_preview(uint32_t ctx)
{
if (!mlv_video_enabled) return 0;
if (!is_movie_mode()) return 0;
/* keep x10 mode unaltered, for focusing */
if (lv_dispsize == 10 && !cam_7d) return 0;
if (PREVIEW_AUTO)
/* enable preview in x5 mode, since framing doesn't match */
return lv_dispsize == 5;
else if (PREVIEW_CANON)
return 0;
else if (PREVIEW_ML)
return 1;
else if (PREVIEW_HACKED)
return RAW_IS_RECORDING || get_halfshutter_pressed() || lv_dispsize == 5;
return 0;
}
static unsigned int raw_rec_update_preview(unsigned int ctx)
{
/* just say whether we can preview or not */
if (ctx == 0)
{
int32_t enabled = raw_rec_should_preview(ctx);
if (!enabled && preview_dirty)
{
/* cleanup the mess, if any */
raw_set_dirty();
preview_dirty = 0;
}
return enabled;
}
struct display_filter_buffers * buffers = (struct display_filter_buffers *) ctx;
raw_previewing = 1;
raw_set_preview_rect(skip_x, skip_y, res_x, res_y);
raw_force_aspect_ratio_1to1();
raw_preview_fast_ex(
(void*)-1,
(PREVIEW_HACKED && RAW_RECORDING) ? (void*)-1 : buffers->dst_buf,
-1,
-1,
get_halfshutter_pressed() ? RAW_PREVIEW_COLOR_HALFRES : RAW_PREVIEW_GRAY_ULTRA_FAST
);
raw_previewing = 0;
if (!RAW_IS_IDLE)
{
/* be gentle with the CPU, save it for recording (especially if the buffer is almost full) */
//~ msleep(free_buffers <= 2 ? 2000 : used_buffers > 1 ? 1000 : 100);
msleep(1000);
}
preview_dirty = 1;
return 1;
}
static unsigned int raw_rec_init()
{
/* enable tracing */
raw_rec_setup_trace();
/* create message queues */
mlv_block_queue = (struct msg_queue *) msg_queue_create("mlv_block_queue", 100);
mlv_writer_queues[0] = (struct msg_queue *) msg_queue_create("mlv_writer_queue", 10);
mlv_writer_queues[1] = (struct msg_queue *) msg_queue_create("mlv_writer_queue", 10);
mlv_mgr_queue = (struct msg_queue *) msg_queue_create("mlv_mgr_queue", 10);
mlv_mgr_queue_close = (struct msg_queue *) msg_queue_create("mlv_mgr_queue_close", 10);
mlv_job_alloc_queue = (struct msg_queue *) msg_queue_create("mlv_job_alloc_queue", 100);
for(int num = 0; num < 50; num++)
{
msg_queue_post(mlv_job_alloc_queue, (uint32_t) malloc(sizeof(largest_job_t)));
}
/* default free text string is empty */
strcpy(raw_tag_str, "");
cam_eos_m = is_camera("EOSM", "2.0.2");
cam_5d2 = is_camera("5D2", "2.1.2");
cam_50d = is_camera("50D", "1.0.9");
cam_5d3 = is_camera("5D3", "1.1.3");
cam_550d = is_camera("550D", "1.0.9");
cam_6d = is_camera("6D", "1.1.6");
cam_600d = is_camera("600D", "1.0.2");
cam_650d = is_camera("650D", "1.0.4");
cam_7d = is_camera("7D", "2.0.3");
cam_700d = is_camera("700D", "1.1.4");
cam_60d = is_camera("60D", "1.1.1");
cam_500d = is_camera("500D", "1.1.1");
/* not all models support exFAT filesystem */
uint32_t exFAT = 1;
if(cam_5d2 || cam_50d || cam_500d || cam_7d)
{
exFAT = 0;
large_file_support = 0;
}
for (struct menu_entry * e = raw_video_menu[0].children; !MENU_IS_EOL(e); e++)
{
/* customize menus for each camera here (e.g. hide what doesn't work) */
if (cam_eos_m && streq(e->name, "Digital dolly") )
e->shidden = 1;
if (!cam_5d3 && streq(e->name, "CF-only Buffers") )
e->shidden = 1;
if (!cam_5d3 && streq(e->name, "Card Spanning") )
e->shidden = 1;
if (!exFAT && streq(e->name, "Files > 4GiB (exFAT)") )
e->shidden = 1;
}
/* disable card spanning on models other than 5D3 */
if(!cam_5d3)
{
card_spanning = 0;
}
if(cam_5d2 || cam_50d)
{
raw_video_menu[0].help = "Record 14-bit RAW video. Press SET to start.";
}
menu_add("Movie", raw_video_menu, COUNT(raw_video_menu));
/* some cards may like this */
if(warm_up)
{
NotifyBox(100000, "Card warming up...");
char warmup_filename[100];
snprintf(warmup_filename, sizeof(warmup_filename), "%s/warmup.raw", get_dcim_dir());
FILE* f = FIO_CreateFile(warmup_filename);
if (f)
{
FIO_WriteFile(f, (void*)0x40000000, 8*1024*1024 * (1 << warm_up));
FIO_CloseFile(f);
FIO_RemoveFile(warmup_filename);
}
NotifyBoxHide();
}
return 0;
}
static unsigned int raw_rec_deinit()
{
if(raw_rec_trace_ctx != TRACE_ERROR)
{
trace_stop(raw_rec_trace_ctx, 0);
raw_rec_trace_ctx = TRACE_ERROR;
}
return 0;
}
MODULE_INFO_START()
MODULE_INIT(raw_rec_init)
MODULE_DEINIT(raw_rec_deinit)
MODULE_INFO_END()
MODULE_CBRS_START()
MODULE_CBR(CBR_VSYNC, raw_rec_vsync_cbr, 0)
MODULE_CBR(CBR_KEYPRESS, raw_rec_keypress_cbr, 0)
MODULE_CBR(CBR_SHOOT_TASK, raw_rec_polling_cbr, 0)
MODULE_CBR(CBR_DISPLAY_FILTER, raw_rec_update_preview, 0)
MODULE_CBRS_END()
MODULE_PROPHANDLERS_START()
MODULE_PROPHANDLER(PROP_ROLLING_PITCHING_LEVEL)
MODULE_PROPHANDLER(PROP_LV_LENS_STABILIZE)
MODULE_PROPHANDLER(PROP_STROBO_AECOMP)
MODULE_PROPHANDLER(PROP_ISO_AUTO)
MODULE_PROPHANDLER(PROP_ISO)
MODULE_PROPHANDLER(PROP_LV_LENS)
MODULE_PROPHANDLER(PROP_APERTURE)
MODULE_PROPHANDLER(PROP_APERTURE_AUTO)
MODULE_PROPHANDLER(PROP_SHUTTER)
MODULE_PROPHANDLER(PROP_SHUTTER_AUTO)
MODULE_PROPHANDLER(PROP_BV)
MODULE_PROPHANDLER(PROP_AE)
MODULE_PROPHANDLER(PROP_PICTURE_STYLE)
MODULE_PROPHANDLER(PROP_WB_MODE_LV)
MODULE_PROPHANDLER(PROP_WBS_GM)
MODULE_PROPHANDLER(PROP_WBS_BA)
MODULE_PROPHANDLER(PROP_WB_KELVIN_LV)
MODULE_PROPHANDLER(PROP_CUSTOM_WB)
MODULE_PROPHANDLERS_END()
MODULE_CONFIGS_START()
MODULE_CONFIG(mlv_video_enabled)
MODULE_CONFIG(resolution_index_x)
MODULE_CONFIG(res_x_fine)
MODULE_CONFIG(aspect_ratio_index)
MODULE_CONFIG(measured_write_speed)
MODULE_CONFIG(allow_frame_skip)
MODULE_CONFIG(dolly_mode)
MODULE_CONFIG(preview_mode)
MODULE_CONFIG(use_srm_memory)
MODULE_CONFIG(start_delay_idx)
MODULE_CONFIG(kill_gd)
MODULE_CONFIG(display_rec_info)
MODULE_CONFIG(small_hacks)
MODULE_CONFIG(warm_up)
MODULE_CONFIG(card_spanning)
MODULE_CONFIG(buffer_fill_method)
MODULE_CONFIG(fast_card_buffers)
MODULE_CONFIG(enable_tracing)
MODULE_CONFIG(show_graph)
MODULE_CONFIG(large_file_support)
MODULE_CONFIG(create_dummy)
MODULE_CONFIG(create_dirs)
MODULE_CONFIGS_END()