https://bitbucket.org/daniel_fort/magic-lantern
Tip revision: 3438140b79ea7a83046ccca523a86ef19c99fdc8 authored by Daniel Fort on 04 July 2018, 20:20:36 UTC
Merged unified into update-to-650D.105
Merged unified into update-to-650D.105
Tip revision: 3438140
mlv_lite.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)
*/
/*
* 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 DEBUG_REDRAW_INTERVAL 1000 /* normally 1000; low values like 50 will reduce write speed a lot! */
#undef DEBUG_BUFFERING_GRAPH /* some funky graphs */
#include <module.h>
#include <dryos.h>
#include <property.h>
#include <bmp.h>
#include <menu.h>
#include <config.h>
#include <math.h>
#include <cropmarks.h>
#include <screenshot.h>
#include "../lv_rec/lv_rec.h"
#include "edmac.h"
#include "edmac-memcpy.h"
#include "../file_man/file_man.h"
#include "cache_hacks.h"
#include "lvinfo.h"
#include "beep.h"
#include "raw.h"
#include "zebra.h"
#include "focus.h"
#include "fps.h"
#include "../mlv_rec/mlv.h"
#include "../trace/trace.h"
#include "powersave.h"
/* from mlv_play module */
extern WEAK_FUNC(ret_0) void mlv_play_file(char *filename);
/* camera-specific tricks */
static int cam_eos_m = 0;
static int cam_5d2 = 0;
static int cam_50d = 0;
static int cam_500d = 0;
static int cam_550d = 0;
static int cam_6d = 0;
static int cam_600d = 0;
static int cam_650d = 0;
static int cam_7d = 0;
static int cam_700d = 0;
static int cam_60d = 0;
static int cam_5d3 = 0;
static int cam_5d3_113 = 0;
static int cam_5d3_123 = 0;
/**
* 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 int 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 int aspect_ratio_presets_num[] = { 5, 4, 3, 8, 25, 239, 235, 22, 2, 185, 16, 5, 3, 4, 12, 1175, 1, 1 };
static int 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("raw.video.enabled", raw_video_enabled, 0);
static CONFIG_INT("raw.res_x", resolution_index_x, 4);
static CONFIG_INT("raw.res_x_fine", res_x_fine, 0);
static CONFIG_INT("raw.aspect.ratio", aspect_ratio_index, 10);
static CONFIG_INT("raw.write.speed", measured_write_speed, 0);
static CONFIG_INT("raw.pre-record", pre_record, 0);
static int pre_record_triggered = 0; /* becomes 1 once you press REC twice */
static int pre_record_num_frames = 0; /* how many frames we should pre-record */
static CONFIG_INT("raw.dolly", dolly_mode, 0);
#define FRAMING_CENTER (dolly_mode == 0)
#define FRAMING_PANNING (dolly_mode == 1)
static CONFIG_INT("raw.preview", preview_mode, 0);
#define PREVIEW_AUTO (preview_mode == 0)
#define PREVIEW_CANON (preview_mode == 1)
#define PREVIEW_ML (preview_mode == 2)
#define PREVIEW_HACKED (preview_mode == 3)
static CONFIG_INT("raw.warm.up", warm_up, 0);
static CONFIG_INT("raw.use.srm.memory", use_srm_memory, 1);
static CONFIG_INT("raw.small.hacks", small_hacks, 1);
/* Recording Status Indicator Options */
#define INDICATOR_OFF 0
#define INDICATOR_IN_LVINFO 1
#define INDICATOR_ON_SCREEN 2
#define INDICATOR_RAW_BUFFER 3
static int show_graph = 0;
/* auto-choose the indicator style based on global draw settings */
/* GD off: only "on screen" works, obviously */
/* GD on: place it on the info bars to be minimally invasive */
#define indicator_display (show_graph ? INDICATOR_RAW_BUFFER : get_global_draw() ? INDICATOR_IN_LVINFO : INDICATOR_ON_SCREEN)
/* state variables */
static int res_x = 0;
static int res_y = 0;
static int max_res_x = 0;
static int max_res_y = 0;
static float squeeze_factor = 0;
static int frame_size = 0;
static int frame_size_real = 0;
static int skip_x = 0;
static int skip_y = 0;
static int frame_offset_x = 0;
static int frame_offset_y = 0;
static int frame_offset_delta_x = 0;
static int frame_offset_delta_y = 0;
#define RAW_IDLE 0
#define RAW_PREPARING 1
#define RAW_RECORDING 2
#define RAW_FINISHING 3
#define RAW_PRE_RECORDING 4
static int raw_recording_state = RAW_IDLE;
static int raw_previewing = 0;
#define RAW_IS_IDLE (raw_recording_state == RAW_IDLE)
#define RAW_IS_PREPARING (raw_recording_state == RAW_PREPARING)
#define RAW_IS_RECORDING (raw_recording_state == RAW_RECORDING || \
raw_recording_state == RAW_PRE_RECORDING)
#define RAW_IS_FINISHING (raw_recording_state == RAW_FINISHING)
#define VIDF_HDR_SIZE 64
/* one video frame */
struct frame_slot
{
void* ptr; /* image data, size=frame_size */
int frame_number; /* from 0 to n */
enum {SLOT_FREE, SLOT_FULL, SLOT_WRITING} status;
};
static struct memSuite * shoot_mem_suite = 0; /* memory suite for our buffers */
static struct memSuite * srm_mem_suite = 0;
static void * fullsize_buffers[2]; /* original image, before cropping, double-buffered */
static int fullsize_buffer_pos = 0; /* which of the full size buffers (double buffering) is currently in use */
static int chunk_list[32]; /* list of free memory chunk sizes, used for frame estimations */
static struct frame_slot slots[511]; /* frame slots */
static int slot_count = 0; /* how many frame slots we have */
static int capture_slot = -1; /* in what slot are we capturing now (index) */
static volatile int force_new_buffer = 0; /* if some other task decides it's better to search for a new buffer */
static int writing_queue[COUNT(slots)+1]; /* queue of completed frames (slot indices) waiting to be saved */
static int writing_queue_tail = 0; /* place captured frames here */
static int writing_queue_head = 0; /* extract frames to be written from here */
static int frame_count = 0; /* how many frames we have processed */
static int chunk_frame_count = 0; /* how many frames in the current file chunk */
static int buffer_full = 0; /* true when the memory becomes full */
char* raw_movie_filename = 0; /* file name for current (or last) movie */
static char* chunk_filename = 0; /* file name for current movie chunk */
static int64_t written_total = 0; /* how many bytes we have written in this movie */
static int64_t written_chunk = 0; /* same for current chunk */
static int writing_time = 0; /* time spent by raw_video_rec_task in FIO_WriteFile calls */
static int idle_time = 0; /* time spent by raw_video_rec_task doing something else */
static uint32_t edmac_active = 0;
static mlv_file_hdr_t file_hdr;
static mlv_rawi_hdr_t rawi_hdr;
static mlv_rawc_hdr_t rawc_hdr;
static mlv_idnt_hdr_t idnt_hdr;
static mlv_expo_hdr_t expo_hdr;
static mlv_lens_hdr_t lens_hdr;
static mlv_rtci_hdr_t rtci_hdr;
static mlv_wbal_hdr_t wbal_hdr;
static uint64_t mlv_start_timestamp = 0;
uint32_t raw_rec_trace_ctx = TRACE_ERROR;
/* interface to other modules: these are called when recording starts or stops */
extern WEAK_FUNC(ret_0) unsigned int raw_rec_cbr_starting();
extern WEAK_FUNC(ret_0) unsigned int raw_rec_cbr_stopping();
static int raw_rec_should_preview(void);
static void refresh_cropmarks()
{
if (lv_dispsize > 1 || raw_rec_should_preview() || !raw_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 int calc_res_y(int res_x, int max_res_y, int num, int den, float squeeze)
{
int res_y;
if (squeeze != 1.0f)
{
/* image should be enlarged vertically in post by a factor equal to "squeeze" */
res_y = (int)(roundf(res_x * den / num / squeeze) + 1);
}
else
{
/* assume square pixels */
res_y = (res_x * den / num + 1);
}
res_y = MIN(res_y, max_res_y);
return res_y & ~1;
}
static void update_cropping_offsets()
{
int sx = raw_info.active_area.x1 + (max_res_x - res_x) / 2;
int 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;
int 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();
/* mv640crop needs this to center the recorded image */
if (is_movie_mode() && video_mode_resolution == 2 && video_mode_crop)
{
skip_x = skip_x + 51;
skip_y = skip_y - 6;
}
}
static void update_resolution_params()
{
/* max res X */
/* make sure we don't get dead pixels from rounding */
int left_margin = (raw_info.active_area.x1 + 7) / 8 * 8;
int right_margin = (raw_info.active_area.x2) / 8 * 8;
int 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 (video_mode_resolution == 1 && lv_dispsize == 1 && is_movie_mode()) /* 720p, image squeezed */
{
/* 720p mode uses 5x3 binning (5DMK3)
* or 5x3 horizontal binning + vertical skipping (other cameras) */
squeeze_factor = 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 */
int num = aspect_ratio_presets_num[aspect_ratio_index];
int den = aspect_ratio_presets_den[aspect_ratio_index];
res_y = calc_res_y(res_x, max_res_y, num, den, squeeze_factor);
/* frame size */
/* should be multiple of 512, so there's no write speed penalty (see http://chdk.setepontos.com/index.php?topic=9970 ; confirmed by benchmarks) */
/* let's try 64 for EDMAC alignment */
/* 64 at the front for the VIDF header */
/* 4 bytes after for checking EDMAC operation */
int frame_size_padded = (VIDF_HDR_SIZE + (res_x * res_y * 14/8) + 4 + 511) & ~511;
/* frame size without padding */
/* must be multiple of 4 */
frame_size_real = res_x * res_y * 14/8;
ASSERT(frame_size_real % 4 == 0);
frame_size = frame_size_padded;
update_cropping_offsets();
}
static char* guess_aspect_ratio(int res_x, int res_y)
{
static char msg[20];
int best_num = 0;
int 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 int common_ratios_x[] = {1, 2, 3, 4, 5, 3, 4, 16, 5, 5};
static int common_ratios_y[] = {1, 1, 1, 1, 1, 2, 3, 9, 4, 3};
for (int i = 0; i < COUNT(common_ratios_x); i++)
{
int num = common_ratios_x[i];
int 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)
{
int h = calc_res_y(res_x, max_res_y, 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)
{
int r = (int)roundf(ratio * 100);
/* is it 2.35:1 or 2.353:1? */
int h = calc_res_y(res_x, max_res_y, 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
{
int r = (int)roundf((1/ratio) * 100);
int h = calc_res_y(res_x, max_res_y, 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 int predict_frames(int write_speed)
{
int fps = fps_get_current_x1000();
int capture_speed = frame_size / 1000 * fps;
int buffer_fill_speed = capture_speed - write_speed;
if (buffer_fill_speed <= 0)
return INT_MAX;
int total_slots = 0;
for (int i = 0; i < COUNT(chunk_list); i++)
total_slots += chunk_list[i] / frame_size;
float buffer_fill_time = total_slots * frame_size / (float) buffer_fill_speed;
int 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 "";
if (!chunk_list[0]) return "";
int write_speed_lo = measured_write_speed * 1024 / 100 * 1024 - 512 * 1024;
int write_speed_hi = measured_write_speed * 1024 / 100 * 1024 + 512 * 1024;
int f_lo = predict_frames(write_speed_lo);
int f_hi = predict_frames(write_speed_hi);
static char msg[50];
if (f_lo < 5000)
{
int 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)
{
int fps = fps_get_current_x1000();
int speed = (res_x * res_y * 14/8 / 1024) * fps / 10 / 1024;
int ok = speed < measured_write_speed;
speed /= 10;
if (frame_size % 512)
{
MENU_SET_WARNING(MENU_WARN_ADVICE, "Frame size not multiple of 512 bytes!");
}
else
{
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(int 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))
{
if (raw_update_params())
{
update_resolution_params();
}
}
}
}
static int calc_crop_factor()
{
int sensor_res_x = raw_capture_info.sensor_res_x;
int camera_crop = raw_capture_info.sensor_crop;
int sampling_x = raw_capture_info.binning_x + raw_capture_info.skipping_x;
if (res_x == 0) return 0;
return camera_crop * (sensor_res_x / sampling_x) / res_x;
}
static MENU_UPDATE_FUNC(raw_main_update)
{
// reset_movie_cropmarks if raw_rec is disabled
refresh_cropmarks();
if (!raw_video_enabled) return;
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);
int 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);
if (!streq(ratio, info->value))
{
/* aspect ratio different from requested value? */
MENU_SET_RINFO("%s", ratio);
}
}
else
{
int num = aspect_ratio_presets_num[aspect_ratio_index];
int den = aspect_ratio_presets_den[aspect_ratio_index];
int sq100 = (int)roundf(squeeze_factor*100);
int res_y_corrected = calc_res_y(res_x, max_res_y*squeeze_factor, 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 (!raw_video_enabled || !lv)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Enable RAW video first.");
MENU_SET_VALUE("N/A");
return;
}
res_x = resolution_presets_x[resolution_index_x] + res_x_fine;
refresh_raw_settings(1);
int selected_x = res_x;
MENU_SET_VALUE("%dx%d", res_x, res_y);
int 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);
if (!get_menu_edit_mode())
{
int len = strlen(info->help);
if (len < 20)
{
snprintf(info->help + len, MENU_MAX_HELP_LEN - len,
" Fine-tune with LEFT/RIGHT or top scrollwheel."
);
}
}
}
static MENU_SELECT_FUNC(resolution_change_fine_value)
{
if (!raw_video_enabled || !lv)
{
return;
}
if (get_menu_edit_mode()) {
/* pickbox: select a preset */
resolution_index_x = COERCE(resolution_index_x + delta, 0, COUNT(resolution_presets_x) - 1);
res_x_fine = 0;
return;
}
/* fine-tune resolution in small increments */
int cur_res = resolution_presets_x[resolution_index_x] + res_x_fine;
cur_res = COERCE(cur_res + delta * 32, resolution_presets_x[0], max_res_x);
/* select the closest preset */
int max_delta = INT_MAX;
for (int i = 0; i < COUNT(resolution_presets_x); i++)
{
int preset_res = resolution_presets_x[i];
int delta = MAX(cur_res * 1024 / preset_res, preset_res * 1024 / cur_res);
if (delta < max_delta)
{
resolution_index_x = i;
max_delta = delta;
}
}
res_x_fine = cur_res - resolution_presets_x[resolution_index_x];
}
static MENU_UPDATE_FUNC(aspect_ratio_update)
{
if (!raw_video_enabled || !lv)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Enable RAW video first.");
MENU_SET_VALUE("N/A");
return;
}
refresh_raw_settings(0);
int num = aspect_ratio_presets_num[aspect_ratio_index];
int den = aspect_ratio_presets_den[aspect_ratio_index];
int selected_y = calc_res_y(res_x, max_res_y, 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 int add_mem_suite(struct memSuite * mem_suite, int chunk_index)
{
if(mem_suite)
{
/* use all chunks larger than frame_size for recording */
struct memChunk * chunk = GetFirstChunkFromSuite(mem_suite);
while(chunk)
{
int size = GetSizeOfMemoryChunk(chunk);
intptr_t ptr = (intptr_t) GetMemoryAddressOfMemoryChunk(chunk);
/* write it down for future frame predictions */
if (chunk_index < COUNT(chunk_list) && size > 64)
{
chunk_list[chunk_index] = size - 64;
printf("chunk #%d: size=%x (%x)\n",
chunk_index+1, chunk_list[chunk_index],
format_memory_size(chunk_list[chunk_index])
);
chunk_index++;
}
/* align pointer at 64 bytes */
intptr_t ptr_raw = ptr;
ptr = (ptr + 63) & ~63;
size -= (ptr - ptr_raw);
/* fit as many frames as we can */
int group_size = 0;
while (size >= frame_size && slot_count < COUNT(slots))
{
mlv_vidf_hdr_t* vidf_hdr = (mlv_vidf_hdr_t*) ptr;
memset(vidf_hdr, 0, sizeof(mlv_vidf_hdr_t));
mlv_set_type((mlv_hdr_t*)vidf_hdr, "VIDF");
vidf_hdr->blockSize = frame_size;
vidf_hdr->frameSpace = VIDF_HDR_SIZE - sizeof(mlv_vidf_hdr_t);
vidf_hdr->cropPosX = (skip_x + 7) & ~7;
vidf_hdr->cropPosY = skip_y & ~1;
vidf_hdr->panPosX = skip_x;
vidf_hdr->panPosY = skip_y;
slots[slot_count].ptr = (void*) ptr;
slots[slot_count].status = SLOT_FREE;
ptr += frame_size;
size -= frame_size;
group_size += frame_size;
slot_count++;
printf("slot #%d: %x\n", slot_count, ptr);
/* split the group at 32M-512K */
/* (after this number, write speed decreases) */
/* (CFDMA can write up to FFFF sectors at once) */
/* (FFFE just in case) */
if (group_size + frame_size > 0xFFFE * 512)
{
/* insert a small gap to split the group here */
ptr += 64;
size -= 64;
group_size = 0;
}
}
/* next chunk */
chunk = GetNextMemoryChunk(mem_suite, chunk);
}
}
return chunk_index;
}
static int setup_buffers()
{
/* allocate memory for double buffering */
/* (we need a single large contiguous chunk) */
int 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 */
memset(chunk_list, 0, sizeof(chunk_list));
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;
}
int chunk_index = 0;
chunk_index = add_mem_suite(shoot_mem_suite, chunk_index);
chunk_index = add_mem_suite(srm_mem_suite, chunk_index);
/* we need at least 3 slots */
if (slot_count < 3)
{
return 0;
}
if (pre_record)
{
/* how much should we pre-record? */
const int presets[4] = {1, 2, 5, 10};
int requested_seconds = presets[(pre_record-1) & 3];
int requested_frames = requested_seconds * fps_get_current_x1000() / 1000;
/* leave at least 16MB for buffering */
int max_frames = slot_count - 16*1024*1024 / frame_size;
pre_record_num_frames = MIN(requested_frames, max_frames);
}
return 1;
}
static void free_buffers()
{
if (shoot_mem_suite) shoot_free_suite(shoot_mem_suite);
shoot_mem_suite = 0;
if (srm_mem_suite) srm_free_suite(srm_mem_suite);
srm_mem_suite = 0;
if (fullsize_buffers[0]) fio_free(fullsize_buffers[0]);
fullsize_buffers[0] = 0;
}
static int get_free_slots()
{
int free_slots = 0;
for (int i = 0; i < slot_count; i++)
if (slots[i].status == SLOT_FREE)
free_slots++;
return free_slots;
}
#define BUFFER_DISPLAY_X 30
#define BUFFER_DISPLAY_Y 50
static void show_buffer_status()
{
if (!liveview_display_idle()) return;
int scale = MAX(1, (300 / slot_count + 1) & ~1);
int x = BUFFER_DISPLAY_X;
int y = BUFFER_DISPLAY_Y;
for (int i = 0; i < slot_count; i++)
{
if (i > 0 && slots[i].ptr != slots[i-1].ptr + frame_size)
x += MAX(2, scale);
int color = slots[i].status == SLOT_FREE ? COLOR_BLACK :
slots[i].status == SLOT_WRITING ? COLOR_GREEN1 :
slots[i].status == SLOT_FULL ? COLOR_LIGHT_BLUE :
COLOR_RED ;
for (int k = 0; k < scale; k++)
{
draw_line(x, y+5, x, y+17, color);
x++;
}
if (scale > 3)
x++;
}
#ifdef DEBUG_BUFFERING_GRAPH
{
int free = get_free_slots();
int x = frame_count % 720;
int ymin = 120;
int ymax = 400;
int y = ymin + free * (ymax - ymin) / slot_count;
dot(x-16, y-16, COLOR_BLACK, 3);
static int prev_x = 0;
static int prev_y = 0;
if (prev_x && prev_y && prev_x < x)
{
draw_line(prev_x, prev_y, x, y, COLOR_BLACK);
}
prev_x = x;
prev_y = y;
bmp_draw_rect(COLOR_BLACK, 0, ymin, 720, ymax-ymin);
int xp = predict_frames(measured_write_speed * 1024 / 100 * 1024) % 720;
draw_line(xp, ymax, xp, ymin, COLOR_RED);
}
#endif
}
static void panning_update()
{
if (!FRAMING_PANNING) return;
int sx = raw_info.active_area.x1 + (max_res_x - res_x) / 2;
int 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 int raw_lv_requested = 0;
if (raw_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;
}
}
}
/* Display recording status in top info bar */
static LVINFO_UPDATE_FUNC(recording_status)
{
LVINFO_BUFFER(16);
if ((indicator_display != INDICATOR_IN_LVINFO) || RAW_IS_IDLE) return;
/* Calculate the stats */
int fps = fps_get_current_x1000();
int t = (frame_count * 1000 + fps/2) / fps;
int predicted = predict_frames(measured_write_speed * 1024 / 100 * 1024);
if (!buffer_full)
{
snprintf(buffer, sizeof(buffer), "%02d:%02d", t/60, t%60);
if (raw_recording_state == RAW_PRE_RECORDING)
{
item->color_bg = COLOR_BLUE;
}
else if (predicted >= 10000)
{
item->color_bg = COLOR_GREEN1;
}
else
{
int time_left = (predicted-frame_count) * 1000 / fps;
if (time_left < 10) {
item->color_bg = COLOR_DARK_RED;
} else {
item->color_bg = COLOR_YELLOW;
}
}
}
else
{
snprintf(buffer, sizeof(buffer), "Stopped.");
item->color_bg = COLOR_DARK_RED;
}
}
/* Display the 'Recording...' icon and status */
static void show_recording_status()
{
/* Determine if we should redraw */
static int auxrec = INT_MIN;
if (RAW_IS_RECORDING && liveview_display_idle() && should_run_polling_action(DEBUG_REDRAW_INTERVAL, &auxrec))
{
/* Calculate the stats */
int fps = fps_get_current_x1000();
int t = (frame_count * 1000 + fps/2) / fps;
int predicted = predict_frames(measured_write_speed * 1024 / 100 * 1024);
int speed=0;
int idle_percent=0;
if (writing_time)
{
speed = (int)((float)written_total / (float)writing_time * (1000.0f / 1024.0f / 1024.0f * 100.0f)); // KiB and msec -> MiB/s x100
idle_percent = idle_time * 100 / (writing_time + idle_time);
measured_write_speed = speed;
speed /= 10;
}
if (indicator_display == INDICATOR_IN_LVINFO)
{
/* If displaying in the info bar, force a refresh */
lens_display_set_dirty();
}
else if (indicator_display == INDICATOR_RAW_BUFFER)
{
show_buffer_status();
if (predicted < 10000)
bmp_printf( FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), BUFFER_DISPLAY_X, BUFFER_DISPLAY_Y+22,
"%02d:%02d, %d frames / %d expected ",
t/60, t%60,
frame_count,
predicted
);
else
bmp_printf( FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), BUFFER_DISPLAY_X, BUFFER_DISPLAY_Y+22,
"%02d:%02d, %d frames, continuous OK ",
t/60, t%60,
frame_count
);
if (writing_time)
{
char msg[50];
snprintf(msg, sizeof(msg),
"%s: %d MB, %d.%d MB/s",
chunk_filename + 17, /* skip A:/DCIM/100CANON/ */
(int)(written_total / 1024 / 1024),
speed/10, speed%10
);
if (idle_time)
{
if (idle_percent) { STR_APPEND(msg, ", %d%% idle", idle_percent); }
else { STR_APPEND(msg, ", %dms idle", idle_time); }
}
bmp_printf( FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), BUFFER_DISPLAY_X, BUFFER_DISPLAY_Y+22+font_med.height, "%s", msg);
}
}
else if (indicator_display == INDICATOR_ON_SCREEN)
{
/* Position the Recording Icon */
int rl_x = 500;
int rl_y = 40;
/* If continuous OK, make the movie icon green, else set based on expected time left */
int rl_color;
if (raw_recording_state == RAW_PRE_RECORDING)
{
rl_color = COLOR_BLUE;
}
else if (predicted >= 10000)
{
rl_color = COLOR_GREEN1;
}
else
{
int time_left = (predicted-frame_count) * 1000 / fps;
if (time_left < 10) {
rl_color = COLOR_DARK_RED;
} else {
rl_color = COLOR_YELLOW;
}
}
int rl_icon_width=0;
/* Draw the movie camera icon */
rl_icon_width = bfnt_draw_char (ICON_ML_MOVIE,rl_x,rl_y,rl_color,COLOR_BG_DARK);
/* Display the Status */
bmp_printf (FONT(FONT_MED, COLOR_WHITE, COLOR_BG_DARK), rl_x+rl_icon_width+5, rl_y+5, "%02d:%02d", t/60, t%60);
if (writing_time)
{
char msg[50];
snprintf(msg, sizeof(msg), "%d.%01dMB/s", speed/10, speed%10);
if (idle_time)
{
if (idle_percent) { STR_APPEND(msg, ", %d%% idle ", idle_percent); }
else { STR_APPEND(msg,", %dms idle ", idle_time); }
}
bmp_printf (FONT(FONT_SMALL, COLOR_WHITE, COLOR_BG_DARK), rl_x+rl_icon_width+5, rl_y+5+font_med.height, "%s", msg);
}
}
}
return;
}
static unsigned int raw_rec_polling_cbr(unsigned int unused)
{
raw_lv_request_update();
if (!raw_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 */
show_recording_status();
return 0;
}
/* todo: reference counting, like with raw_lv_request */
static void cache_require(int lock)
{
static int cache_was_unlocked = 0;
if (lock)
{
if (!cache_locked())
{
cache_was_unlocked = 1;
icache_lock();
}
}
else
{
if (cache_was_unlocked)
{
icache_unlock();
cache_was_unlocked = 0;
}
}
}
static void unhack_liveview_vsync(int unused);
static void FAST 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;
int rec = RAW_IS_RECORDING;
static int prev_rec = 0;
int should_hack = 0;
int 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)
{
for (int channel = 0; channel < 32; channel++)
{
/* silence out the EDMACs used for HD and LV buffers */
int pitch = edmac_get_length(channel) & 0xFFFF;
if (pitch == vram_lv.pitch || pitch == vram_hd.pitch)
{
uint32_t reg = edmac_get_base(channel);
//printf("Hack %d %x %dx%d\n", channel, 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)
{
task_create("lv_unhack", 0x1e, 0x1000, unhack_liveview_vsync, (void*)0);
}
}
/* this is a separate task */
static void unhack_liveview_vsync(int unused)
{
while (!RAW_IS_IDLE) msleep(100);
PauseLiveView();
ResumeLiveView();
/* fixme: in exmem.c, but how? */
gui_uilock(UILOCK_NONE);
}
static void hack_liveview(int unhack)
{
if (small_hacks)
{
/* disable canon graphics (gains a little speed) */
static int 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);
/* change dialog refresh timer from 50ms to 8192ms */
uint32_t dialog_refresh_timer_addr = /* in StartDialogRefreshTimer */
cam_50d ? 0xffa84e00 :
cam_5d2 ? 0xffaac640 :
cam_5d3_113 ? 0xff4acda4 :
cam_5d3_123 ? 0xFF4B7648 :
cam_550d ? 0xFF2FE5E4 :
cam_600d ? 0xFF37AA18 :
cam_650d ? 0xFF527E34 :
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_require(1);
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);
cache_require(0);
}
}
}
}
static int FAST choose_next_capture_slot()
{
/* keep on rolling? */
/* O(1) */
if (
capture_slot >= 0 &&
capture_slot + 1 < slot_count &&
slots[capture_slot + 1].ptr == slots[capture_slot].ptr + frame_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 */
int len = 0;
void* prev_ptr = PTR_INVALID;
int best_len = 0;
int best_index = -1;
for (int i = 0; i < slot_count; i++)
{
if (slots[i].status == SLOT_FREE)
{
if (slots[i].ptr == prev_ptr + frame_size)
{
len++;
prev_ptr = slots[i].ptr;
if (len > best_len)
{
best_len = len;
best_index = i - len + 1;
}
}
else
{
len = 1;
prev_ptr = slots[i].ptr;
if (len > best_len)
{
best_len = len;
best_index = i;
}
}
}
else
{
len = 0;
prev_ptr = PTR_INVALID;
}
}
/* fixme: */
/* avoid 32MB writes, they are slower (they require two DMA calls) */
/* go back a few K and the speed is restored */
//~ best_len = MIN(best_len, (32*1024*1024 - 8192) / frame_size);
force_new_buffer = 0;
return best_index;
}
static void pre_record_vsync_step()
{
if (raw_recording_state == RAW_PRE_RECORDING)
{
if (pre_record_triggered)
{
/* queue all captured frames for writing */
/* (they are numbered from 1 to frame_count-1; frame 0 is skipped) */
/* they are not ordered, which complicates things a bit */
int i = 0;
for (int current_frame = 1; current_frame < frame_count; current_frame++)
{
/* consecutive frames tend to be grouped,
* so this loop will not run every time */
while (slots[i].status != SLOT_FULL || slots[i].frame_number != current_frame)
{
i = MOD(i+1, slot_count);
}
writing_queue[writing_queue_tail] = i;
writing_queue_tail = MOD(writing_queue_tail + 1, COUNT(writing_queue));
i = MOD(i+1, slot_count);
}
/* done, from now on we can just record normally */
raw_recording_state = RAW_RECORDING;
}
else if (frame_count >= pre_record_num_frames)
{
/* discard old frames */
/* also adjust frame_count so all frames start from 1,
* just like the rest of the code assumes */
frame_count--;
for (int i = 0; i < slot_count; i++)
{
/* first frame is 1 */
if (slots[i].status == SLOT_FULL)
{
ASSERT(slots[i].frame_number > 0);
if (slots[i].frame_number == 1)
{
slots[i].status = SLOT_FREE;
}
else
{
slots[i].frame_number--;
((mlv_vidf_hdr_t*)slots[i].ptr)->frameNumber
= slots[i].frame_number - 1;
}
}
}
}
}
}
#define FRAME_SENTINEL 0xA5A5A5A5 /* for double-checking EDMAC operations */
static void frame_add_checks(int slot_index)
{
void* ptr = slots[slot_index].ptr + VIDF_HDR_SIZE;
uint32_t* frame_end = ptr + frame_size_real - 4;
uint32_t* after_frame = ptr + frame_size_real;
*(volatile uint32_t*) frame_end = FRAME_SENTINEL; /* this will be overwritten by EDMAC */
*(volatile uint32_t*) after_frame = FRAME_SENTINEL; /* this shalt not be overwritten */
}
static int frame_check_saved(int slot_index)
{
void* ptr = slots[slot_index].ptr + VIDF_HDR_SIZE;
uint32_t* frame_end = ptr + frame_size_real - 4;
uint32_t* after_frame = ptr + frame_size_real;
if (*(volatile uint32_t*) after_frame != FRAME_SENTINEL)
{
/* EDMAC overflow */
return -1;
}
if (*(volatile uint32_t*) frame_end == FRAME_SENTINEL)
{
/* frame not yet complete */
return 0;
}
/* looks alright */
return 1;
}
static void edmac_cbr_r(void *ctx)
{
}
static void edmac_cbr_w(void *ctx)
{
edmac_active = 0;
edmac_copy_rectangle_adv_cleanup();
}
static void FAST process_frame()
{
/* skip the first frame, it will be gibberish */
if (frame_count == 0)
{
frame_count++;
return;
}
if (edmac_active)
{
/* EDMAC too slow */
NotifyBox(2000, "EDMAC timeout.");
buffer_full = 1;
return;
}
if (raw_recording_state == RAW_PRE_RECORDING)
{
pre_record_vsync_step();
}
/* where to save the next frame? */
capture_slot = choose_next_capture_slot(capture_slot);
if (capture_slot >= 0)
{
/* okay */
slots[capture_slot].frame_number = frame_count;
slots[capture_slot].status = SLOT_FULL;
frame_add_checks(capture_slot);
if (raw_recording_state == RAW_PRE_RECORDING)
{
/* pre-recording before trigger? don't queue frames for writing */
/* (do nothing here) */
}
else
{
/* send it for saving, even if it isn't done yet */
/* it's quite unlikely that FIO DMA will be faster than EDMAC */
writing_queue[writing_queue_tail] = capture_slot;
writing_queue_tail = MOD(writing_queue_tail + 1, COUNT(writing_queue));
}
}
else
{
/* card too slow */
buffer_full = 1;
return;
}
/* copy current frame to our buffer and crop it to its final size */
mlv_vidf_hdr_t* vidf_hdr = (mlv_vidf_hdr_t*)slots[capture_slot].ptr;
vidf_hdr->frameNumber = slots[capture_slot].frame_number - 1;
mlv_set_timestamp((mlv_hdr_t*)vidf_hdr, mlv_start_timestamp);
vidf_hdr->cropPosX = (skip_x + 7) & ~7;
vidf_hdr->cropPosY = skip_y & ~1;
vidf_hdr->panPosX = skip_x;
vidf_hdr->panPosY = skip_y;
void* ptr = slots[capture_slot].ptr + VIDF_HDR_SIZE;
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;
//~ printf("saving frame %d: slot %d ptr %x\n", frame_count, capture_slot, ptr);
edmac_active = 1;
edmac_copy_rectangle_cbr_start(
ptr, fullSizeBuffer,
raw_info.pitch,
(skip_x+7)/8*14, skip_y/2*2,
res_x*14/8, 0, 0, res_x*14/8, res_y,
&edmac_cbr_r, &edmac_cbr_w, NULL
);
/* advance to next frame */
frame_count++;
return;
}
static unsigned int FAST raw_rec_vsync_cbr(unsigned int unused)
{
if (!raw_video_enabled) return 0;
if (!is_movie_mode()) return 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; return 0; }
if (buffer_full) return 0;
/* double-buffering */
raw_lv_redirect_edmac(fullsize_buffers[fullsize_buffer_pos % 2]);
process_frame();
return 0;
}
static char* get_next_raw_movie_file_name()
{
static char filename[100];
struct tm now;
LoadCalendarFromRTC(&now);
for (int 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(filename, sizeof(filename), "%s/M%02d-%02d%02d.MLV", get_dcim_dir(), now.tm_mday, now.tm_hour, COERCE(now.tm_min + number, 0, 99));
/* already existing file? */
uint32_t size;
if( FIO_GetFileSize( filename, &size ) != 0 ) break;
if (size == 0) break;
}
return filename;
}
static char* get_next_chunk_file_name(char* base_name, int chunk)
{
static char filename[100];
/* change file extension, according to chunk number: RAW, R00, R01 and so on */
snprintf(filename, sizeof(filename), "%s", base_name);
int len = strlen(filename);
snprintf(filename + len - 2, 3, "%02d", chunk-1);
return filename;
}
static char* get_wav_file_name(char* raw_movie_filename)
{
/* same name as movie, but with wav extension */
static char wavfile[100];
snprintf(wavfile, sizeof(wavfile), raw_movie_filename);
int len = strlen(wavfile);
wavfile[len-4] = '.';
wavfile[len-3] = 'W';
wavfile[len-2] = 'A';
wavfile[len-1] = 'V';
/* prefer SD card for saving WAVs (should be faster on 5D3) */
if (is_dir("B:/")) wavfile[0] = 'B';
return wavfile;
}
static void init_mlv_chunk_headers(struct raw_info * raw_info)
{
mlv_start_timestamp = mlv_set_timestamp(NULL, 0);
memset(&file_hdr, 0, sizeof(mlv_file_hdr_t));
mlv_init_fileheader(&file_hdr);
file_hdr.fileGuid = mlv_generate_guid();
file_hdr.fileNum = 0;
file_hdr.fileCount = 0; //autodetect
file_hdr.fileFlags = 4;
file_hdr.videoClass = 1;
file_hdr.audioClass = 0;
file_hdr.videoFrameCount = 0; //autodetect
file_hdr.audioFrameCount = 0;
file_hdr.sourceFpsNom = fps_get_current_x1000();
file_hdr.sourceFpsDenom = 1000;
memset(&rawi_hdr, 0, sizeof(mlv_rawi_hdr_t));
mlv_set_type((mlv_hdr_t *)&rawi_hdr, "RAWI");
mlv_set_timestamp((mlv_hdr_t *)&rawi_hdr, mlv_start_timestamp);
rawi_hdr.blockSize = sizeof(mlv_rawi_hdr_t);
rawi_hdr.xRes = res_x;
rawi_hdr.yRes = res_y;
rawi_hdr.raw_info = *raw_info;
memset(&rawc_hdr, 0, sizeof(mlv_rawc_hdr_t));
mlv_set_type((mlv_hdr_t *)&rawc_hdr, "RAWC");
mlv_set_timestamp((mlv_hdr_t *)&rawc_hdr, mlv_start_timestamp);
rawc_hdr.blockSize = sizeof(mlv_rawc_hdr_t);
/* copy all fields from raw_capture_info */
rawc_hdr.sensor_res_x = raw_capture_info.sensor_res_x;
rawc_hdr.sensor_res_y = raw_capture_info.sensor_res_y;
rawc_hdr.sensor_crop = raw_capture_info.sensor_crop;
rawc_hdr.reserved = raw_capture_info.reserved;
rawc_hdr.binning_x = raw_capture_info.binning_x;
rawc_hdr.skipping_x = raw_capture_info.skipping_x;
rawc_hdr.binning_y = raw_capture_info.binning_y;
rawc_hdr.skipping_y = raw_capture_info.skipping_y;
rawc_hdr.offset_x = raw_capture_info.offset_x;
rawc_hdr.offset_y = raw_capture_info.offset_y;
mlv_fill_idnt(&idnt_hdr, mlv_start_timestamp);
mlv_fill_expo(&expo_hdr, mlv_start_timestamp);
mlv_fill_lens(&lens_hdr, mlv_start_timestamp);
mlv_fill_rtci(&rtci_hdr, mlv_start_timestamp);
mlv_fill_wbal(&wbal_hdr, mlv_start_timestamp);
}
static int write_mlv_chunk_headers(FILE* f)
{
if (FIO_WriteFile(f, &file_hdr, file_hdr.blockSize) != (int)file_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &rawi_hdr, rawi_hdr.blockSize) != (int)rawi_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &rawc_hdr, rawc_hdr.blockSize) != (int)rawc_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &idnt_hdr, idnt_hdr.blockSize) != (int)idnt_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &expo_hdr, expo_hdr.blockSize) != (int)expo_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &lens_hdr, lens_hdr.blockSize) != (int)lens_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &rtci_hdr, rtci_hdr.blockSize) != (int)rtci_hdr.blockSize) return 0;
if (FIO_WriteFile(f, &wbal_hdr, wbal_hdr.blockSize) != (int)wbal_hdr.blockSize) return 0;
if (mlv_write_vers_blocks(f, mlv_start_timestamp)) return 0;
int hdr_size = FIO_SeekSkipFile(f, 0, SEEK_CUR);
/* insert a null block so the header size is multiple of 512 bytes */
mlv_hdr_t nul_hdr;
mlv_set_type(&nul_hdr, "NULL");
int padded_size = (hdr_size + sizeof(nul_hdr) + 511) & ~511;
nul_hdr.blockSize = padded_size - hdr_size;
if (FIO_WriteFile(f, &nul_hdr, nul_hdr.blockSize) != (int)nul_hdr.blockSize) return 0;
return padded_size;
}
static int file_size_limit = 0; /* have we run into the 4GB limit? */
static int last_write_timestamp = 0; /* last FIO_WriteFile call */
static int mlv_chunk = 0; /* MLV chunk index from header */
/* update the frame count and close the chunk */
static void finish_chunk(FILE* f)
{
file_hdr.videoFrameCount = chunk_frame_count;
FIO_SeekSkipFile(f, 0, SEEK_SET);
FIO_WriteFile(f, &file_hdr, file_hdr.blockSize);
FIO_CloseFile(f);
chunk_frame_count = 0;
}
/* This saves a group of frames, also taking care of file splitting if required */
static int write_frames(FILE** pf, void* ptr, int size_used, int num_frames)
{
/* note: num_frames can be computed as size_used / frame_size, but compressed frames are around the corner) */
ASSERT(num_frames == size_used / frame_size);
FILE* f = *pf;
/* if we know there's a 4GB file size limit and we're about to exceed it, go ahead and make a new chunk */
if (file_size_limit && written_chunk + size_used > 0xFFFFFFFF)
{
finish_chunk(f);
chunk_filename = get_next_chunk_file_name(raw_movie_filename, ++mlv_chunk);
printf("About to reach 4GB limit.\n");
printf("Creating new chunk: %s\n", chunk_filename);
FILE* g = FIO_CreateFile(chunk_filename);
if (!g) return 0;
file_hdr.fileNum = mlv_chunk;
written_chunk = write_mlv_chunk_headers(g);
written_total += written_chunk;
if (written_chunk)
{
printf("Success!\n");
*pf = f = g;
}
else
{
printf("New chunk didn't work. Card full?\n");
FIO_CloseFile(g);
FIO_RemoveFile(chunk_filename);
mlv_chunk--;
return 0;
}
}
int t0 = get_ms_clock_value();
if (!last_write_timestamp) last_write_timestamp = t0;
idle_time += t0 - last_write_timestamp;
int r = FIO_WriteFile(f, ptr, size_used);
last_write_timestamp = get_ms_clock_value();
if (r != size_used) /* 4GB limit or card full? */
{
printf("Write error.\n");
/* failed, but not at 4GB limit, card must be full */
if (written_chunk + size_used < 0xFFFFFFFF)
{
printf("Failed before 4GB limit. Card full?\n");
/* don't try and write the remaining frames, the card is full */
writing_queue_head = writing_queue_tail;
return 0;
}
file_size_limit = 1;
/* 5D2 does not write anything if the call failed, but 5D3 writes exactly 4294967295 */
/* We need to write a null block to cover to the end of the file if anything was written */
/* otherwise the file could end in the middle of a block */
int64_t pos = FIO_SeekSkipFile(f, 0, SEEK_CUR);
if (pos > written_chunk + 1)
{
printf("Covering incomplete block.\n");
FIO_SeekSkipFile(f, written_chunk, SEEK_SET);
mlv_hdr_t nul_hdr;
mlv_set_type(&nul_hdr, "NULL");
nul_hdr.blockSize = MAX(sizeof(nul_hdr), pos - written_chunk);
FIO_WriteFile(f, &nul_hdr, sizeof(nul_hdr));
}
finish_chunk(f);
/* try to create a new chunk */
chunk_filename = get_next_chunk_file_name(raw_movie_filename, ++mlv_chunk);
printf("Creating new chunk: %s\n", chunk_filename);
FILE* g = FIO_CreateFile(chunk_filename);
if (!g) return 0;
file_hdr.fileNum = mlv_chunk;
written_chunk = write_mlv_chunk_headers(g);
written_total += written_chunk;
int r2 = written_chunk ? FIO_WriteFile(g, ptr, size_used) : 0;
if (r2 == size_used) /* new chunk worked, continue with it */
{
printf("Success!\n");
*pf = f = g;
written_total += size_used;
written_chunk += size_used;
chunk_frame_count += num_frames;
}
else /* new chunk didn't work, card full */
{
printf("New chunk didn't work. Card full?\n");
FIO_CloseFile(g);
FIO_RemoveFile(chunk_filename);
mlv_chunk--;
return 0;
}
}
else
{
/* all fine */
written_total += size_used;
written_chunk += size_used;
chunk_frame_count += num_frames;
}
writing_time += last_write_timestamp - t0;
return 1;
}
static void raw_video_rec_task()
{
//~ console_show();
/* init stuff */
raw_recording_state = RAW_PREPARING;
slot_count = 0;
capture_slot = -1;
fullsize_buffer_pos = 0;
frame_count = 0;
chunk_frame_count = 0;
buffer_full = 0;
FILE* f = 0;
written_total = 0; /* in bytes */
int last_block_size = 0; /* for detecting early stops */
last_write_timestamp = 0;
mlv_chunk = 0;
edmac_active = 0;
pre_record_triggered = 0;
powersave_prohibit();
/* wait for two frames to be sure everything is refreshed */
wait_lv_frames(2);
/* detect raw parameters (geometry, black level etc) */
raw_set_dirty();
if (!raw_update_params())
{
NotifyBox(5000, "Raw detect error");
goto cleanup;
}
update_resolution_params();
/* create output file */
raw_movie_filename = get_next_raw_movie_file_name();
chunk_filename = raw_movie_filename;
f = FIO_CreateFile(raw_movie_filename);
if (!f)
{
NotifyBox(5000, "File create error");
goto cleanup;
}
init_mlv_chunk_headers(&raw_info);
written_total = written_chunk = write_mlv_chunk_headers(f);
if (!written_chunk)
{
NotifyBox(5000, "Card Full");
goto cleanup;
}
/* allocate memory */
if (!setup_buffers())
{
NotifyBox(5000, "Memory error");
goto cleanup;
}
hack_liveview(0);
/* get exclusive access to our edmac channels */
edmac_memcpy_res_lock();
/* this will enable the vsync CBR and the other task(s) */
raw_recording_state = pre_record ? RAW_PRE_RECORDING : RAW_RECORDING;
/* try a sync beep (not very precise, but better than nothing) */
beep();
/* signal that we are starting */
raw_rec_cbr_starting();
writing_time = 0;
idle_time = 0;
/* fake recording status, to integrate with other ml stuff (e.g. hdr video */
set_recording_custom(CUSTOM_RECORDING_RAW);
int fps = fps_get_current_x1000();
int last_processed_frame = 0;
/* main recording loop */
while (RAW_IS_RECORDING && lv)
{
if (buffer_full)
{
goto abort_and_check_early_stop;
}
int w_tail = writing_queue_tail; /* this one can be modified outside the loop, so grab it here, just in case */
int w_head = writing_queue_head; /* this one is modified only here, but use it just for the shorter name */
/* writing queue empty? nothing to do */
if (w_head == w_tail)
{
msleep(20);
continue;
}
int first_slot = writing_queue[w_head];
/* check whether the first frame was filled by EDMAC (it may be sent in advance) */
/* probably not needed */
int check = frame_check_saved(first_slot);
if (check == 0)
{
msleep(20);
continue;
}
/* group items from the queue in a contiguous block - as many as we can */
int last_grouped = w_head;
for (int i = w_head; i != w_tail; i = MOD(i+1, COUNT(writing_queue)))
{
int slot_index = writing_queue[i];
int group_pos = MOD(i - w_head, COUNT(writing_queue));
/* TBH, I don't care if these are part of the same group or not,
* as long as pointers are ordered correctly */
if (slots[slot_index].ptr == slots[first_slot].ptr + frame_size * group_pos)
last_grouped = i;
else
break;
}
/* grouped frames from w_head to last_grouped (including both ends) */
int num_frames = MOD(last_grouped - w_head + 1, COUNT(writing_queue));
int free_slots = get_free_slots();
/* if we are about to overflow, save a smaller number of frames, so they can be freed quicker */
if (measured_write_speed)
{
/* measured_write_speed unit: 0.01 MB/s */
/* FPS unit: 0.001 Hz */
/* overflow time unit: 0.1 seconds */
int overflow_time = free_slots * 1000 * 10 / fps;
/* better underestimate write speed a little */
int frame_limit = overflow_time * 1024 / 10 * (measured_write_speed * 9 / 100) * 1024 / frame_size / 10;
if (frame_limit >= 0 && frame_limit < num_frames)
{
//~ printf("careful, will overflow in %d.%d seconds, better write only %d frames\n", overflow_time/10, overflow_time%10, frame_limit);
num_frames = MAX(1, frame_limit - 1);
}
}
int after_last_grouped = MOD(w_head + num_frames, COUNT(writing_queue));
/* write queue empty? better search for a new larger buffer */
if (after_last_grouped == writing_queue_tail)
{
force_new_buffer = 1;
}
void* ptr = slots[first_slot].ptr;
int size_used = frame_size * num_frames;
/* mark these frames as "writing" */
for (int i = w_head; i != after_last_grouped; i = MOD(i+1, COUNT(writing_queue)))
{
int slot_index = writing_queue[i];
if (slots[slot_index].status != SLOT_FULL)
{
bmp_printf(FONT_LARGE, 30, 70, "Slot check error");
beep();
}
slots[slot_index].status = SLOT_WRITING;
}
if (!write_frames(&f, ptr, size_used, num_frames))
{
goto abort;
}
/* for detecting early stops */
last_block_size = MOD(after_last_grouped - w_head, COUNT(writing_queue));
/* mark these frames as "free" so they can be reused */
for (int i = w_head; i != after_last_grouped; i = MOD(i+1, COUNT(writing_queue)))
{
if (i == writing_queue_tail)
{
bmp_printf( FONT_MED, 30, 110,
"Queue overflow"
);
beep();
}
int slot_index = writing_queue[i];
if (frame_check_saved(slot_index) != 1)
{
bmp_printf( FONT_MED, 30, 110,
"Data corruption at slot %d, frame %d ", slot_index, slots[slot_index].frame_number
);
beep();
}
if (slots[slot_index].frame_number != last_processed_frame + 1)
{
bmp_printf( FONT_MED, 30, 110,
"Frame order error: slot %d, frame %d, expected %d ", slot_index, slots[slot_index].frame_number, last_processed_frame + 1
);
beep();
}
last_processed_frame++;
slots[slot_index].status = SLOT_FREE;
}
/* remove these frames from the queue */
writing_queue_head = after_last_grouped;
/* error handling */
if (0)
{
abort:
last_block_size = 0; /* ignore early stop check */
abort_and_check_early_stop:
if (last_block_size > 2)
{
bmp_printf( FONT_MED, 30, 90,
"Early stop (%d). Didn't make it to estimated record time!.", last_block_size
);
beep_times(last_block_size);
}
else
{
bmp_printf( FONT_MED, 30, 90,
"Movie recording stopped automagically "
);
/* this is error beep, not audio sync beep */
beep_times(2);
}
break;
}
}
/* make sure the user doesn't rush to turn off the camera or something */
gui_uilock(UILOCK_EVERYTHING);
/* signal that we are stopping */
raw_rec_cbr_stopping();
/* done, this will stop the vsync CBR and the copying task */
raw_recording_state = RAW_FINISHING;
/* wait until the other tasks calm down */
msleep(500);
/* exclusive edmac access no longer needed */
edmac_memcpy_res_unlock();
set_recording_custom(CUSTOM_RECORDING_NOT_RECORDING);
/* write remaining frames */
for (; writing_queue_head != writing_queue_tail; writing_queue_head = MOD(writing_queue_head + 1, COUNT(writing_queue)))
{
int slot_index = writing_queue[writing_queue_head];
if (slots[slot_index].status != SLOT_FULL)
{
bmp_printf( FONT_MED, 30, 110,
"Slot %d: frame %d not saved ", slot_index, slots[slot_index].frame_number
);
beep();
}
if (frame_check_saved(slot_index) != 1)
{
bmp_printf( FONT_MED, 30, 110,
"Data corruption at slot %d, frame %d ", slot_index, slots[slot_index].frame_number
);
beep();
}
if (slots[slot_index].frame_number != last_processed_frame + 1)
{
bmp_printf( FONT_MED, 30, 110,
"Frame order error: slot %d, frame %d, expected %d ", slot_index, slots[slot_index].frame_number, last_processed_frame + 1
);
beep();
}
last_processed_frame++;
slots[slot_index].status = SLOT_WRITING;
if (indicator_display == INDICATOR_RAW_BUFFER) show_buffer_status();
if (!write_frames(&f, slots[slot_index].ptr, frame_size, 1))
{
NotifyBox(5000, "Card Full");
beep();
break;
}
slots[slot_index].status = SLOT_FREE;
}
if (!written_total || !f)
{
bmp_printf( FONT_MED, 30, 110,
"Nothing saved, card full maybe."
);
beep_times(3);
msleep(2000);
}
cleanup:
if (f) finish_chunk(f);
if (!written_total)
{
FIO_RemoveFile(raw_movie_filename);
raw_movie_filename = 0;
}
/* everything saved, we can unlock the buttons.
* note: freeing SRM memory will also touch uilocks,
* so it's best to call this before free_buffers */
gui_uilock(UILOCK_NONE);
free_buffers();
#ifdef DEBUG_BUFFERING_GRAPH
take_screenshot(SCREENSHOT_FILENAME_AUTO, SCREENSHOT_BMP);
#endif
hack_liveview(1);
redraw();
/* re-enable powersaving */
powersave_permit();
raw_recording_state = RAW_IDLE;
}
static void raw_start_stop()
{
if (!RAW_IS_IDLE)
{
raw_recording_state = RAW_FINISHING;
beep();
}
else
{
raw_recording_state = RAW_PREPARING;
gui_stop_menu();
task_create("raw_rec_task", 0x19, 0x1000, raw_video_rec_task, (void*)0);
}
}
static MENU_SELECT_FUNC(raw_playback_start)
{
if (RAW_IS_IDLE)
{
if (!raw_movie_filename)
{
bmp_printf(FONT_MED, 20, 50, "Please record a movie first.");
return;
}
mlv_play_file(raw_movie_filename);
}
}
static MENU_UPDATE_FUNC(raw_playback_update)
{
if ((thunk)mlv_play_file == (thunk)ret_0)
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "You need to load the mlv_play module.");
if (raw_movie_filename)
MENU_SET_VALUE(raw_movie_filename + 17);
else
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Record a video clip first.");
}
static struct menu_entry raw_video_menu[] =
{
{
.name = "RAW video",
.priv = &raw_video_enabled,
.max = 1,
.update = raw_main_update,
.submenu_width = 710,
.depends_on = DEP_LIVEVIEW | DEP_MOVIE_MODE,
.help = "Record 14-bit RAW video (MLV format, no sound, basic metadata).",
.help2 = "Press LiveView to start recording.",
.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 = "Preview",
.priv = &preview_mode,
.max = 3,
.choices = CHOICES("Auto", "Real-time", "Framing", "Frozen LV"),
.help = "Raw video preview (long half-shutter press to override):",
.help2 = "Auto: ML chooses what's best for each video mode\n"
"Plain old LiveView (color and real-time). Framing is not always correct.\n"
"Slow (not real-time) and low-resolution, but has correct framing.\n"
"Freeze LiveView for more speed; uses 'Framing' preview if Global Draw ON.\n",
.advanced = 1,
},
{
.name = "Pre-record",
.priv = &pre_record,
.max = 4,
.choices = CHOICES("OFF", "1 second", "2 seconds", "5 seconds", "10 seconds"),
.help = "Pre-records a few seconds of video into memory, discarding old frames.",
.help2 = "Press REC twice: 1 - to start pre-recording, 2 - for normal recording.",
},
{
.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.",
.advanced = 1,
},
{
.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.",
.advanced = 1,
},
{
.name = "Use SRM job memory",
.priv = &use_srm_memory,
.max = 1,
.help = "Allocate memory from SRM job buffers",
.advanced = 1,
},
{
.name = "Small hacks",
.priv = &small_hacks,
.max = 1,
.help = "Slow down Canon GUI, disable auto exposure, white balance...",
.advanced = 1,
},
{
.name = "Show buffer graph",
.priv = &show_graph,
.max = 1,
.help = "Displays a graph of the current buffer usage and expected frames.",
.advanced = 1,
},
{
.name = "Playback",
.select = raw_playback_start,
.update = raw_playback_update,
.icon_type = IT_ACTION,
.help = "Play back the last raw video clip.",
},
MENU_ADVANCED_TOGGLE,
MENU_EOL,
},
}
};
static unsigned int raw_rec_keypress_cbr(unsigned int key)
{
if (!raw_video_enabled)
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 */
int 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();
break;
case RAW_PRE_RECORDING:
pre_record_triggered = 1;
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;
}
}
}
return 1;
}
static int preview_dirty = 0;
static int raw_rec_should_preview(void)
{
if (!raw_video_enabled) return 0;
if (!is_movie_mode()) return 0;
/* keep x10 mode unaltered, for focusing */
if (lv_dispsize == 10) return 0;
/* framing is incorrect in modes with high resolutions
* (e.g. x5 zoom, crop_rec) */
int raw_active_width = raw_info.active_area.x2 - raw_info.active_area.x1;
int raw_active_height = raw_info.active_area.y2 - raw_info.active_area.y1;
int framing_incorrect =
raw_active_width > 2000 ||
raw_active_height > (video_mode_fps <= 30 ? 1300 : 720);
/* some modes have Canon preview totally broken */
int preview_broken = (lv_dispsize == 1 && raw_active_width > 2000);
int prefer_framing_preview =
(res_x < max_res_x * 80/100) ? 1 : /* prefer correct framing instead of large black bars */
(res_x*9 < res_y*16) ? 1 : /* tall aspect ratio -> prevent image hiding under info bars*/
(framing_incorrect) ? 1 : /* use correct framing in modes where Canon preview is incorrect */
0 ; /* otherwise, use plain LiveView */
/* only override on long half-shutter press, when not autofocusing */
/* todo: move these in core, with a proper API */
static int long_halfshutter_press = 0;
static int last_hs_unpress = 0;
static int autofocusing = 0;
if (!get_halfshutter_pressed())
{
autofocusing = 0;
long_halfshutter_press = 0;
last_hs_unpress = get_ms_clock_value();
}
else
{
if (lv_focus_status == 3)
{
autofocusing = 1;
}
if (get_ms_clock_value() - last_hs_unpress > 500)
{
long_halfshutter_press = 1;
}
}
if (autofocusing)
{
/* disable our preview during autofocus */
return 0;
}
if (PREVIEW_AUTO)
{
/* half-shutter overrides default choice */
if (preview_broken) return 1;
return prefer_framing_preview ^ long_halfshutter_press;
}
else if (PREVIEW_CANON)
{
return long_halfshutter_press;
}
else if (PREVIEW_ML)
{
return !long_halfshutter_press;
}
else if (PREVIEW_HACKED)
{
if (preview_broken) return 1;
return (RAW_IS_RECORDING || prefer_framing_preview)
^ long_halfshutter_press;
}
return 0;
}
static unsigned int raw_rec_update_preview(unsigned int ctx)
{
/* just say whether we can preview or not */
if (ctx == 0)
{
int enabled = raw_rec_should_preview();
if (!enabled && preview_dirty)
{
/* cleanup the mess, if any */
raw_set_dirty();
preview_dirty = 0;
}
return enabled;
}
/* only consider speed when the recorder is actually busy */
int queued_frames = MOD(writing_queue_tail - writing_queue_head, COUNT(writing_queue));
int need_for_speed = (RAW_IS_RECORDING) && (
(PREVIEW_HACKED && queued_frames > slot_count / 8) ||
(queued_frames > slot_count / 4)
);
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, 1);
raw_force_aspect_ratio_1to1();
/* when recording, preview both full-size buffers,
* to make sure it's not recording every other frame */
static int fi = 0; fi = !fi;
raw_preview_fast_ex(
RAW_IS_RECORDING ? fullsize_buffers[fi] : (void*)-1,
PREVIEW_HACKED && RAW_IS_RECORDING ? (void*)-1 : buffers->dst_buf,
-1,
-1,
(need_for_speed && !get_halfshutter_pressed())
? RAW_PREVIEW_GRAY_ULTRA_FAST
: RAW_PREVIEW_COLOR_HALFRES
);
raw_previewing = 0;
/* be gentle with the CPU, save it for recording (especially if the buffer is almost full) */
msleep(
(need_for_speed)
? ((queued_frames > slot_count / 2) ? 1000 : 500)
: 50
);
preview_dirty = 1;
return 1;
}
static struct lvinfo_item info_items[] = {
/* Top bar */
{
.name = "Rec. Status",
.which_bar = LV_TOP_BAR_ONLY,
.update = recording_status,
.preferred_position = 50,
.priority = 10,
}
};
static unsigned int raw_rec_init()
{
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_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.5");
cam_7d = is_camera("7D", "2.0.3");
cam_700d = is_camera("700D", "1.1.5");
cam_60d = is_camera("60D", "1.1.1");
cam_500d = is_camera("500D", "1.1.1");
cam_5d3_113 = is_camera("5D3", "1.1.3");
cam_5d3_123 = is_camera("5D3", "1.2.3");
cam_5d3 = (cam_5d3_113 || cam_5d3_123);
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));
lvinfo_add_items (info_items, COUNT(info_items));
/* 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()
{
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_CONFIGS_START()
MODULE_CONFIG(raw_video_enabled)
MODULE_CONFIG(resolution_index_x)
MODULE_CONFIG(res_x_fine)
MODULE_CONFIG(aspect_ratio_index)
MODULE_CONFIG(measured_write_speed)
MODULE_CONFIG(pre_record)
MODULE_CONFIG(dolly_mode)
MODULE_CONFIG(preview_mode)
MODULE_CONFIG(use_srm_memory)
MODULE_CONFIG(small_hacks)
MODULE_CONFIG(warm_up)
MODULE_CONFIGS_END()