#include #include #include #include #include #include #include #include #include #include #include #include #include #undef CROP_DEBUG #ifdef CROP_DEBUG #define dbg_printf(fmt,...) { printf(fmt, ## __VA_ARGS__); } #else #define dbg_printf(fmt,...) {} #endif static int is_digic4 = 0; static int is_digic5 = 0; static int is_5D3 = 0; static int is_6D = 0; static int is_700D = 0; static int is_650D = 0; static int is_100D = 0; static int is_EOSM = 0; static CONFIG_INT("crop.enabled", crop_enabled, 0); static CONFIG_INT("crop.bin_mode", menu_bin_mode, 0); /* 1:1 */ static CONFIG_INT("crop.xres", menu_xres, 0); /* 0 = no change */ static CONFIG_INT("crop.yres", menu_yres, 0); static CONFIG_INT("crop.fps_mode", fps_mode, 0); /* 0 = value from Canon menu */ static CONFIG_INT("crop.shutter_range", shutter_range, 0); /* 0 = no change */ static int settings_changed = 0; enum binning_mode { BINNING_1x1, BINNING_3x3, BINNING_1x3, BINNING_3x1, }; static int binning_mode = BINNING_1x1; /* camera-specific parameters */ static uint32_t CMOS_WRITE = 0; static uint32_t MEM_CMOS_WRITE = 0; static uint32_t ADTG_WRITE = 0; static uint32_t MEM_ADTG_WRITE = 0; static uint32_t ENGIO_WRITE = 0; static uint32_t MEM_ENGIO_WRITE = 0; static int sensor_xres = 0; static int sensor_yres = 0; /* how many columns are read out at once */ /* i.e. how many pixels do you get horizontally by incrementing FPS timer A */ static int column_factor = 0; /* from SENSOR_TIMING_TABLE (fps-engio.c) or FPS override submenu */ static int fps_main_clock = 0; static int default_timerA[11]; /* 1080p 1080p 1080p 720p 720p zoom crop crop crop crop crop */ static int default_timerB[11]; /* 24p 25p 30p 50p 60p x5 24p 25p 30p 50p 60p */ static int default_fps_1k[11] = { 23976, 25000, 29970, 50000, 59940, 29970, 23976, 25000, 29970, 50000, 59940 }; /* video modes */ /* properties are fired AFTER the new video mode is fully up and running * to apply our presets, we need to know the video more DURING the switch * we'll peek into the PathDriveMode structure for that * * Example: x10 -> x1 on 5D3 * This sequence cannot be identified just by looking at C0F06804; * some ADTG registers that we need to overide are configured before that. * * CtrlSrv: DlgLiveView.c PRESS_TELE_MAG_BUTTON KeyRepeat[0] * Gmt: gmtModeChange * Evf: evfModeChangeRequest(4) * Evf: PATH_SelectPathDriveMode S:0 Z:10000 R:0 DZ:0 SM:1 * (lots of stuff going on) * Evf: evfModeChangeComplete * (some more stuff) * Gmt: VisibleParam 720, 480, 0, 38, 720, 404. * Gmt: gmtUpdateDispSize (10 -> 1) * PropMgr: *** mpu_send(06 05 09 11 01 00) ; finally triggered PROP_LV_DISPSIZE... */ /* PATH_SelectPathDriveMode S:%d Z:%lx R:%lx DZ:%d SM:%d */ /* offsets verified on 5D3, 6D, 70D, EOSM, 100D, 60D, 80D, 200D */ const struct PathDriveMode { uint32_t SM; /* 5D3,700D: 0 during zoom, 1 in all other modes; 6D: 2 is flicker-related?! */ uint32_t fps_mode; /* 5D3,700D: 0=60p, 1=50p, 2=30p/zoom, 3=25p, 4=24p */ uint32_t S; /* 5D3,700D: 0=1080p, 1=720p, 8=zoom, 6=1080crop (700D) */ uint32_t resolution_idx;/* 5D3,700D: 0=1080p/zoom, 1=720p, 2=640x480 (PathDriveMode->resolution_idx) */ uint16_t zoom_lo; /* 5D3,700D: lower word of zoom; unused? */ uint16_t zoom; /* 5D3,700D: 1, 5 or 10 */ uint32_t unk_14; /* 5D3: unused? */ uint32_t DZ; /* 5D3: unused? 700D: 2=1080crop */ uint32_t unk_1c; uint32_t unk_20; uint32_t CF; /* 100D, 80D: ? */ uint32_t SV; /* 100D, EOSM, 80D: ? */ uint32_t unk_2c; uint32_t unk_30; uint32_t unk_34; uint32_t unk_38; uint32_t unk_3c; uint32_t unk_40; uint32_t DT; /* 100D, EOSM: ? */ } * PathDriveMode = 0; enum fps_mode { FPS_60 = 0, FPS_50 = 1, FPS_30 = 2, FPS_25 = 3, FPS_24 = 4, }; static int is_1080p() { /* properties triggered too late */ if (PathDriveMode->zoom != 1) { return 0; } /* unsure whether fast enough or not; to be tested */ if (PathDriveMode->DZ) { return 0; } /* this snippet seems OK with properties */ /* note: on 5D2 and 5D3 (maybe also 6D, not sure), * sensor configuration in photo mode is identical to 1080p. * other cameras may be different */ return !is_movie_mode() || PathDriveMode->resolution_idx == 0; } static int is_720p() { /* properties triggered too late */ if (PathDriveMode->zoom != 1) { return 0; } /* unsure whether fast enough or not; to be tested */ if (PathDriveMode->DZ) { return 0; } if (is_EOSM && !RECORDING_H264) { /* EOS M stays in 720p30 during standby */ return 1; } /* this snippet seems OK with properties */ return is_movie_mode() && PathDriveMode->resolution_idx == 1; } static int is_supported_mode() { if (!lv) return 0; if (0) { printf( "Path: SM=%d S=%d res=%d DZ=%d zoom=%d mode=%d\n", PathDriveMode->SM, PathDriveMode->S, PathDriveMode->resolution_idx, PathDriveMode->DZ, PathDriveMode->zoom, PathDriveMode->fps_mode ); } if (PathDriveMode->zoom == 10) { /* leave the x10 zoom unaltered, for focusing */ return 0; } if (PathDriveMode->zoom == 5) { /* only 1:1 binning is compatible with x5 zoom */ if (binning_mode != BINNING_1x1) { return 0; } } return 1; } static uint32_t target_xres = 0; static uint32_t target_yres = 0; static int32_t xres_delta = 0; static int32_t yres_delta = 0; static uint32_t target_timerA = 0; static uint32_t target_timerB = 0; static int row_binning = 1; static int col_binning = 1; /* helper to allow indexing various properties of Canon's video modes */ static inline int get_video_mode_index() { if (PathDriveMode->zoom > 1) { return 5; } #if 0 /* likely handled by PathDriveMode->fps_mode; to be tested */ if (is_EOSM) { if (PathDriveMode->zoom == 1 && !RECORDING_H264) { /* EOS M stays in 720p30 during standby (same timer values as with 1080p30) */ return 2; } } #endif if (PathDriveMode->DZ) { /* some cameras may have various crop modes, hopefully at most one per FPS */ return (PathDriveMode->fps_mode == FPS_24) ? 6 : (PathDriveMode->fps_mode == FPS_25) ? 7 : (PathDriveMode->fps_mode == FPS_30) ? 8 : (PathDriveMode->fps_mode == FPS_50) ? 9 : /* (PathDriveMode->fps_mode == FPS_60) */ 10 ; } /* regular video modes */ return (PathDriveMode->fps_mode == FPS_24) ? 0 : (PathDriveMode->fps_mode == FPS_25) ? 1 : (PathDriveMode->fps_mode == FPS_30) ? 2 : (PathDriveMode->fps_mode == FPS_50) ? 3 : /* (PathDriveMode->fps_mode == FPS_60) */ 4 ; } /* optical black area sizes */ /* not sure how to adjust them from registers, so... hardcode them here */ static inline void FAST calc_skip_offsets(int * p_skip_left, int * p_skip_right, int * p_skip_top, int * p_skip_bottom) { int skip_left = 0; int skip_top = 0; int skip_right = 0; int skip_bottom = 0; /* LiveView values */ if (is_5D3) { skip_left = 146; skip_right = 2; skip_top = 28; skip_bottom = 0; } if (is_700D) { skip_left = 72; skip_top = 28; skip_right = 0; skip_bottom = 0; } if (p_skip_left) *p_skip_left = skip_left; if (p_skip_right) *p_skip_right = skip_right; if (p_skip_top) *p_skip_top = skip_top; if (p_skip_bottom) *p_skip_bottom = skip_bottom; } static inline int get_default_xres() { /* fixme: 700D only */ if (PathDriveMode->zoom > 1) return 2520; return sensor_xres / 3; } /* Vertical resolution from current unmodified video mode */ /* (active area only, as seen by mlv_lite) */ /* This is close to sensor_yres / 3, but not identical... */ static inline int get_default_yres() { if (is_5D3) { if (PathDriveMode->zoom > 1) return 1320; return (PathDriveMode->fps_mode >= FPS_30) ? 1290 : 672; } if (is_700D) { /* 1032 in Canon's 3x crop, 1080 in x5 */ if (PathDriveMode->zoom > 1) return 1080; return (PathDriveMode->fps_mode >= FPS_30) ? 1160 : 696; } return 0; } /* skip_top from unmodified video mode (raw.c, LiveView skip offsets) */ static inline int get_default_skip_top() { if (is_5D3) { /* 60 in x5, but we don't override this mode */ return (PathDriveMode->fps_mode >= FPS_30) ? 28 : 20; } if (is_700D) { /* same offset in 1080p, 720p and x5 */ return 28; } ASSERT(0); return 0; } /* 5D3 vertical resolution increments over default configuration */ /* note that first scanline may be moved down by 30 px (see reg_override_top_bar) */ static inline int FAST calc_yres_delta() { /* user override */ int skip_top; calc_skip_offsets(0, 0, &skip_top, 0); int default_yres = get_default_yres(); int default_skip_top = get_default_skip_top(); //int top_adj = get_top_bar_adjustment(); int top_adj = 0; return target_yres - default_yres + skip_top - default_skip_top + top_adj; } static inline int FAST calc_xres_delta() { int default_xres = get_default_xres(); return target_xres - default_xres; } /* pack two 6-bit values into a 12-bit one */ #define PACK12(lo,hi) ((((lo) & 0x3F) | ((hi) << 6)) & 0xFFF) /* pack 5 bits + 5 bits + 2 bits into a 12-bit value */ #define PACK552(lo,mid,hi) ((((lo) & 0x1F) | (((mid) & 0x1F) << 5) | (hi) << 10) & 0xFFF) /* pack two 16-bit values into a 32-bit one */ #define PACK32(lo,hi) (((uint32_t)(lo) & 0xFFFF) | ((uint32_t)(hi) << 16)) /* pack two 16-bit values into a 32-bit one */ #define PACK32(lo,hi) (((uint32_t)(lo) & 0xFFFF) | ((uint32_t)(hi) << 16)) /* what CMOS parameters we are going to override */ static uint32_t cmos_new[10] = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; static void cmos_update_params() { if (1) { /* horizontal and vetical centering, in rough increments */ int xres = target_xres; int yres = target_yres; /* the sensor is divided into slices, 120x120 pixels each */ const int slice_size = 120; /* 700D: * xpos=0 => capture from the left side * xpos=44 => black image, no valid data * sensor xres = 5208; last slice is incomplete */ const int x_slices = (sensor_xres + slice_size - 1) / slice_size; const int y_slices = (sensor_yres + slice_size - 1) / slice_size; const int extended_xres = x_slices * slice_size; const int extended_yres = y_slices * slice_size; /* reading out every column, or bin every 3 columns? */ int col_binning = (binning_mode == BINNING_3x3 || binning_mode == BINNING_1x3) ? 3 : 1; /* 700D: * CMOS[5]: horizontal position mask 0xFC0, column binning 0x020 * 1736: about 14.5 would be ideal, formula gives 14.46 before rounding * 1800: 14 looks about right, formula gives 14.2 before rounding * 2520: 12 is Canon default, formula gives 11 (11.2 before rounding) */ int xpos = ((sensor_xres/col_binning - xres) * x_slices + extended_xres/col_binning) / (2 * extended_xres/col_binning); int col_binning_flag = (col_binning == 1) ? 0 : 0x20; cmos_new[5] = PACK12(col_binning_flag, xpos); /* vertical position in rough increments */ /* CMOS[7]: start/stop scanline, 5 bits each, 120px increments, highest 2 bits unknown */ /* reading out every line, or bin/skip every 3 lines? */ int row_binning = (binning_mode == BINNING_3x3 || binning_mode == BINNING_3x1) ? 3 : 1; int ystart = ((sensor_yres/row_binning - yres) * y_slices) / (2 * extended_yres/row_binning); int ystop = ((sensor_yres/row_binning + yres) * y_slices + extended_yres/row_binning) / (2 * extended_yres/row_binning); cmos_new[7] = PACK552(ystart, ystop, 0); /* to test: ystop-1 should leave a thin white bar at the bottom of the screen */ dbg_printf("sensor res: %dx%d\n", sensor_xres, sensor_yres); dbg_printf("capture res: %dx%d\n", xres, yres); dbg_printf("slices: %dx%d bin %dx%d\n", x_slices, y_slices, col_binning, row_binning); dbg_printf("x:%d y:%d-%d\n", xpos, ystart, ystop); dbg_printf("cmos [5]:%x [7]:%x\n", cmos_new[5], cmos_new[7]); } if (is_5D3) { switch (binning_mode) { case BINNING_1x1: cmos_new[6] = 0x170; /* pink highlights without this */ break; case BINNING_3x3: cmos_new[6] = 0x370; /* pink highlights without this */ break; default: cmos_new[6] = -1; /* don't change */ } } } static void FAST cmos_hook(uint32_t* regs, uint32_t* stack, uint32_t pc) { if (!is_supported_mode()) { /* don't patch other video modes */ return; } /* some dynamic overrides */ if (is_700D) { /* CMOS[6]: 0x789 in 1:1 crop modes, 0x78B in 3x3 and 5x3 * without this, 1:1 crop mode looks greenish (left OB ends up too bright) * it also takes some other values that should not be changed, * otherwise you get lots of vertical stripes * fixme: try to find a one-size-fits-all value */ cmos_new[6] = -1; uint16_t* data_buf = (uint16_t*) regs[0]; while (*data_buf != 0xFFFF) { int reg = (*data_buf) >> 12; int val = (*data_buf) & 0xFFF; if (reg == 6) { switch (binning_mode) { case BINNING_1x1: case BINNING_3x1: /* 700D: 78B->789, 5AB->5A9 */ cmos_new[6] = val & ~2; break; case BINNING_3x3: case BINNING_1x3: /* 700D: 789->78B, 5A9->5AB */ cmos_new[6] = val | 2; break; } } data_buf++; } } uint16_t* data_buf = (uint16_t*) regs[0]; /* copy data into a buffer, to make the override temporary */ /* that means: as soon as we stop executing the hooks, values are back to normal */ static uint16_t copy[512]; uint16_t* copy_end = ©[COUNT(copy)]; uint16_t* copy_ptr = copy; while (*data_buf != 0xFFFF) { *copy_ptr = *data_buf; int reg = (*data_buf) >> 12; if (cmos_new[reg] != -1) { *copy_ptr = (reg << 12) | cmos_new[reg]; dbg_printf("CMOS[%x] = %x\n", reg, cmos_new[reg]); } data_buf++; copy_ptr++; if (copy_ptr > copy_end) while(1); } *copy_ptr = 0xFFFF; /* pass our modified register list to cmos_write */ regs[0] = (uint32_t) copy; } static uint32_t nrzi_encode( uint32_t in_val ) { uint32_t out_val = 0; uint32_t old_bit = 0; for (int num = 0; num < 31; num++) { uint32_t bit = in_val & 1<<(30-num) ? 1 : 0; if (bit != old_bit) out_val |= (1 << (30-num)); old_bit = bit; } return out_val; } static uint32_t nrzi_decode( uint32_t in_val ) { uint32_t val = 0; if (in_val & 0x8000) val |= 0x8000; for (int num = 0; num < 31; num++) { uint32_t old_bit = (val & 1<<(30-num+1)) >> 1; val |= old_bit ^ (in_val & 1<<(30-num)); } return val; } static int FAST adtg_lookup(uint32_t* data_buf, int reg_needle) { while(*data_buf != 0xFFFFFFFF) { int reg = (*data_buf) >> 16; if (reg == reg_needle) { return *(uint16_t*)data_buf; } } return -1; } /* adapted from fps_override_shutter_blanking in fps-engio.c */ static int adjust_shutter_blanking(int old) { /* sensor duty cycle: range 0 ... timer B */ int current_blanking = nrzi_decode(old); int video_mode = get_video_mode_index(); /* what value Canon firmware assumes for timer B? */ int fps_timer_b_orig = default_timerB[video_mode]; int current_exposure = fps_timer_b_orig - current_blanking; /* wrong assumptions? */ if (current_exposure < 0) { return old; } int default_fps = default_fps_1k[video_mode]; int current_fps = fps_get_current_x1000(); dbg_printf("FPS %d->%d\n", default_fps, current_fps); float frame_duration_orig = 1000.0 / default_fps; float frame_duration_current = 1000.0 / current_fps; float orig_shutter = frame_duration_orig * current_exposure / fps_timer_b_orig; float new_shutter = (shutter_range == 0) ? ({ /* original shutter speed from the altered video mode */ orig_shutter; }) : ({ /* map the available range of 1/4000...1/30 (24-30p) or 1/4000...1/60 (50-60p) * from minimum allowed (1/15000 with full-res LV) to 1/fps */ int max_fps_shutter = (PathDriveMode->fps_mode >= FPS_30) ? 33333 : 64000; int default_fps_adj = 1e9 / (1e9 / max_fps_shutter - 250); (orig_shutter - 250e-6) * default_fps_adj / current_fps; }); /* what value is actually used for timer B? (possibly after our overrides) */ int fps_timer_b = (shamem_read(0xC0F06014) & 0xFFFF) + 1; dbg_printf("Timer B %d->%d\n", fps_timer_b_orig, fps_timer_b); int new_exposure = new_shutter * fps_timer_b / frame_duration_current; int new_blanking = COERCE(fps_timer_b - new_exposure, 10, fps_timer_b - 2); dbg_printf("Exposure %d->%d (timer B units)\n", current_exposure, new_exposure); #ifdef CROP_DEBUG float chk_shutter = frame_duration_current * new_exposure / fps_timer_b; dbg_printf("Shutter %d->%d us\n", (int)(orig_shutter*1e6), (int)(chk_shutter*1e6)); #endif dbg_printf("Blanking %d->%d\n", current_blanking, new_blanking); return nrzi_encode(new_blanking); } extern void fps_override_shutter_blanking(); static void FAST adtg_hook(uint32_t* regs, uint32_t* stack, uint32_t pc) { if (!is_supported_mode()) { /* don't patch other video modes */ return; } /* This hook is called from the DebugMsg's in adtg_write, * so if we change the register list address, it won't be able to override them. * Workaround: let's call it here. */ fps_override_shutter_blanking(); uint32_t cs = regs[0]; uint32_t *data_buf = (uint32_t *) regs[1]; int dst = cs & 0xF; /* copy data into a buffer, to make the override temporary */ /* that means: as soon as we stop executing the hooks, values are back to normal */ static uint32_t copy[512]; uint32_t* copy_end = ©[COUNT(copy)]; uint32_t* copy_ptr = copy; struct adtg_new { int dst; int reg; int val; }; /* expand this as required */ struct adtg_new adtg_new[13] = {{0}}; const int blanking_reg_zoom = (is_5D3) ? 0x805E : 0x805F; const int blanking_reg_nozoom = (is_5D3) ? 0x8060 : 0x8061; const int blanking_reg = (PathDriveMode->zoom == 1) ? blanking_reg_nozoom : blanking_reg_zoom; /* scan for shutter blanking and make both zoom and non-zoom value equal */ /* (the values are different when using FPS override with ADTG shutter override) */ /* (fixme: might be better to handle this in ML core?) */ int shutter_blanking = 0; for (uint32_t * buf = data_buf; *buf != 0xFFFFFFFF; buf++) { int reg = (*buf) >> 16; if (reg == blanking_reg) { int val = (*buf) & 0xFFFF; shutter_blanking = val; } } /* some modes may need adjustments to maintain exposure */ if (shutter_blanking) { shutter_blanking = adjust_shutter_blanking(shutter_blanking); } /* all modes may want to override shutter speed */ /* ADTG[0x8060/61]: shutter blanking for 3x3 mode */ /* ADTG[0x805E/5F]: shutter blanking for zoom mode */ adtg_new[0] = (struct adtg_new) {6, blanking_reg_nozoom, shutter_blanking}; adtg_new[1] = (struct adtg_new) {6, blanking_reg_zoom, shutter_blanking}; /* hopefully generic; to be tested later */ if (1) { switch (binning_mode) { /* all 1:1 modes (3x, 3K, 4K...) */ case BINNING_1x1: /* ADTG2/4[0x8000] = 5 (set in one call) */ /* ADTG2[0x8806] = 0x6088 on 5D3 (artifacts without it) */ adtg_new[2] = (struct adtg_new) {6, 0x8000, 5}; if (is_5D3) { /* this register is model-specific */ adtg_new[3] = (struct adtg_new) {2, 0x8806, 0x6088}; } break; /* 3x3 binning in 720p (in 1080p it's already 3x3) */ case BINNING_3x3: /* ADTG2/4[0x800C] = 2: vertical binning factor = 3 */ adtg_new[2] = (struct adtg_new) {6, 0x800C, 2}; break; /* 1x3 binning (read every line, bin every 3 columns) */ case BINNING_1x3: /* ADTG2/4[0x800C] = 0: read every line */ adtg_new[2] = (struct adtg_new) {6, 0x800C, 0}; break; /* 3x1 binning (bin every 3 lines, read every column) */ /* doesn't work well, figure out why */ case BINNING_3x1: /* ADTG2/4[0x800C] = 2: vertical binning factor = 3 */ /* ADTG2[0x8806] = 0x6088 on 5D3 (artifacts worse without it) */ adtg_new[2] = (struct adtg_new) {6, 0x800C, 2}; if (is_5D3) { /* this register is model-specific */ adtg_new[3] = (struct adtg_new) {2, 0x8806, 0x6088}; } break; } } /* these should work on all presets, on all DIGIC 5 models and also on recent DIGIC 4 */ if (1) { /* assuming FPS timer B was overridden before this */ int fps_timer_b = (shamem_read(0xC0F06014) & 0xFFFF) + 1; int readout_end = shamem_read(is_digic4 ? 0xC0F06088 : 0xC0F06804) >> 16; /* PowerSaveTiming registers */ /* after readout is finished, we can turn off the sensor until the next frame */ /* we could also set these to 0; it will work, but the sensor will run a bit hotter */ /* to be tested to find out exactly how much */ adtg_new[4] = (struct adtg_new) {6, 0x8172, nrzi_encode(readout_end + 1) }; /* PowerSaveTiming ON (6D/700D) */ adtg_new[5] = (struct adtg_new) {6, 0x8178, nrzi_encode(readout_end + 1) }; /* PowerSaveTiming ON (5D3/6D/700D) */ adtg_new[6] = (struct adtg_new) {6, 0x8196, nrzi_encode(readout_end + 1) }; /* PowerSaveTiming ON (5D3) */ adtg_new[7] = (struct adtg_new) {6, 0x8173, nrzi_encode(fps_timer_b - 1) }; /* PowerSaveTiming OFF (6D/700D) */ adtg_new[8] = (struct adtg_new) {6, 0x8179, nrzi_encode(fps_timer_b - 1) }; /* PowerSaveTiming OFF (5D3/6D/700D) */ adtg_new[9] = (struct adtg_new) {6, 0x8197, nrzi_encode(fps_timer_b - 1) }; /* PowerSaveTiming OFF (5D3) */ adtg_new[10] = (struct adtg_new) {6, 0x82B6, nrzi_encode(readout_end - 1) }; /* PowerSaveTiming ON? (700D); 2 units below the "ON" timing from above */ /* ReadOutTiming registers */ /* these shouldn't be 0, as they affect the image */ adtg_new[11] = (struct adtg_new) {6, 0x82F8, nrzi_encode(readout_end + 1) }; /* ReadOutTiming */ adtg_new[12] = (struct adtg_new) {6, 0x82F9, nrzi_encode(fps_timer_b - 1) }; /* ReadOutTiming end? */ } while(*data_buf != 0xFFFFFFFF) { *copy_ptr = *data_buf; int reg = (*data_buf) >> 16; for (int i = 0; i < COUNT(adtg_new); i++) { if ((reg == adtg_new[i].reg) && (dst & adtg_new[i].dst)) { int new_value = adtg_new[i].val; dbg_printf("ADTG%x[%x] = %x\n", dst, reg, new_value); *(uint16_t*)copy_ptr = new_value; if (reg == blanking_reg_zoom || reg == blanking_reg_nozoom) { /* also override in original data structure */ /* to be picked up on the screen indicators */ *(uint16_t*)data_buf = new_value; } } } data_buf++; copy_ptr++; if (copy_ptr >= copy_end) while(1); } *copy_ptr = 0xFFFFFFFF; /* pass our modified register list to adtg_write */ regs[1] = (uint32_t) copy; } /* this is used to cover the black bar at the top of the image in 1:1 modes */ /* (used in most other presets) */ static inline uint32_t reg_override_top_bar_5d3(uint32_t reg, uint32_t old_val) { switch (reg) { /* raw start line/column */ /* move start line down by 30 pixels */ /* not sure where this offset comes from */ case 0xC0F06800: return 0x1F0017; } return 0; } static inline uint32_t reg_override_top_bar(uint32_t reg, uint32_t old_val) { switch (reg) { /* raw start line/column */ /* move start line down by 30 pixels */ /* not sure where this offset comes from */ //~ case 0xC0F06800: //~ return 0x1F0017; } return 0; } /* these are required for increasing vertical resolution */ /* (used in most other presets) */ static inline uint32_t reg_override_HEAD34(uint32_t reg, uint32_t old_val) { switch (reg) { /* HEAD3 timer */ case 0xC0F0713C: return old_val + yres_delta; /* HEAD4 timer */ case 0xC0F07150: return old_val + yres_delta; } return 0; } static inline uint32_t reg_override_common(uint32_t reg, uint32_t old_val) { uint32_t a = reg_override_top_bar(reg, old_val); if (a) return a; uint32_t b = reg_override_HEAD34(reg, old_val); if (b) return b; return 0; } static inline uint32_t reg_override_fps(uint32_t reg, uint32_t timerA, uint32_t timerB, uint32_t old_val) { /* hardware register requires timer-1 */ timerA--; timerB--; /* only override FPS registers if the old value is what we expect * otherwise we may be in some different video mode for a short time * this race condition is enough to lock up LiveView in some cases * e.g. 5D3 3x3 50/60p when going from photo mode to video mode */ switch (reg) { case 0xC0F06824: case 0xC0F06828: case 0xC0F0682C: case 0xC0F06830: { /* 700D: * timerA - 100 locks up * timerA - 50 kinda works, black bar on the right * timerA - 44 works * timerA + 1 works * timerA + 44 works * timerA + 44 works?! * FFFF works?! * 1080p24: A=20F, this=206 * 1080p25: A=280, this=206 * 1080p24 crop: A=221, this=222 */ return timerA & ~1; //~ /* values might be slightly different; keep the difference */ //~ uint32_t defaultA = default_timerA[get_video_mode_index()] - 1; //~ int32_t delta = timerA - defaultA; //~ return old_val + delta; } case 0xC0F06010: { uint32_t expected = default_timerA[get_video_mode_index()] - 1; //~ if (old_val == expected || old_val == expected + 1) { return timerA; } break; } case 0xC0F06008: case 0xC0F0600C: { uint32_t expected = default_timerA[get_video_mode_index()] - 1; expected |= (expected << 16); //~ if (old_val == expected || old_val == expected + 0x00010001) { return timerA | (timerA << 16); } break; } case 0xC0F06014: { uint32_t expected = default_timerB[get_video_mode_index()] - 1; //~ if (old_val == expected || old_val == expected + 1) { return timerB; } break; } } return 0; } static void fps_update_params() { binning_mode = menu_bin_mode; int skip_left; calc_skip_offsets(&skip_left, 0, 0, 0); int default_xres = get_default_xres(); int default_yres = get_default_yres(); /* first, minimize timer A, just what's required to achieve xres * this minimizes rolling shutter and maximizes vertical resolution */ int min_xres = (PathDriveMode->zoom > 1) ? 2208 : 1440; int max_xres = (binning_mode == BINNING_1x1 || binning_mode == BINNING_3x1) ? sensor_xres : sensor_xres / 3; target_xres = menu_xres ? COERCE(menu_xres/8*8, min_xres, max_xres) : default_xres; if (is_700D) { target_timerA = (272 + target_xres + skip_left) / column_factor; } if (is_5D3) { target_timerA = MAX(400, (352 + target_xres + skip_left) / column_factor); } /* 700D: higher frame rates are working as long as the total area is not smaller * than in the original video mode; otherwise the preview locks up * and camera crashes after some seconds (semErr). Solving this might be * enough to allow higher frame rates. */ int original_area = default_xres * default_yres; int min_yres = original_area / target_xres; int desired_yres = MAX(menu_yres ? menu_yres : default_yres, min_yres); /* now try to get the desired FPS */ int current_fps_x1000 = fps_get_current_x1000(); /* fixme: use default_fps_1k */ int desired_fps_x1000 = (PathDriveMode->fps_mode == FPS_24) ? 23976 : (PathDriveMode->fps_mode == FPS_25) ? 25000 : (PathDriveMode->fps_mode == FPS_30) ? 29970 : (PathDriveMode->fps_mode == FPS_50) ? (PathDriveMode->zoom > 1 ? 25000 : 50000) : /* 50p in x5 doesn't work */ /* (PathDriveMode->fps_mode == FPS_60) */ (PathDriveMode->zoom > 1 ? 29970 : 59940) ; /* neither 60p */ if (fps_mode == -1) { /* we'll get whatever FPS the requested resolution allows */ desired_fps_x1000 = (uint64_t) fps_main_clock * 1000ull / (uint64_t) target_timerA / (desired_yres + 260); } else if (fps_mode == 3) { /* remap 50/60 FPS to 45/48 */ desired_fps_x1000 = (PathDriveMode->fps_mode == FPS_50) ? 45000 : (PathDriveMode->fps_mode == FPS_60) ? 48000 : desired_fps_x1000; } else { /* we'll get the desired FPS, no matter what; * if it's too much, we'll reduce yres */ desired_fps_x1000 /= (1 << fps_mode); } target_timerB = (uint64_t) fps_main_clock * 1000ull / (uint64_t) target_timerA / desired_fps_x1000; int max_yres = (target_timerB - 260) & ~1; while (max_yres < min_yres) { printf("[crop_rec] forcing fps/2 (ylimit = %d,%d)\n", min_yres, max_yres); desired_fps_x1000 /= 2; target_timerB *= 2; max_yres = (target_timerB - 260) & ~1; } target_yres = MIN(desired_yres, max_yres); printf("[crop_rec] source: %dx%d @ %s%d.%03d fps\n", default_xres, default_yres, FMT_FIXEDPOINT3(current_fps_x1000)); printf("[crop_rec] target: %dx%d @ %s%d.%03d fps\n", target_xres, target_yres, FMT_FIXEDPOINT3(desired_fps_x1000)); printf("[crop_rec] ylimit: %d,%d\n", min_yres, max_yres); printf("[crop_rec] timerA: %d -> %d\n", default_timerA[get_video_mode_index()], target_timerA); printf("[crop_rec] timerB: %d -> %d\n", default_timerB[get_video_mode_index()], target_timerB); xres_delta = calc_xres_delta(); yres_delta = calc_yres_delta(); printf("[crop_rec] delta : X%+d Y%+d [z=%d]\n", xres_delta, yres_delta, PathDriveMode->zoom); } static inline int reg_override_generic(uint32_t reg, uint32_t old_val) { int a = reg_override_fps(reg, target_timerA, target_timerB, old_val); if (a) return a; switch (reg) { /* raw resolution (end line/column) */ case 0xC0F06804: return old_val + xres_delta / column_factor + (yres_delta << 16); } return reg_override_common(reg, old_val); } static void FAST engio_write_hook(uint32_t* regs, uint32_t* stack, uint32_t pc) { static int prev_zoom = 0; static int prev_fpsm = 0; int current_zoom = PathDriveMode->zoom; int current_fpsm = PathDriveMode->fps_mode; if (current_zoom != prev_zoom || current_fpsm != prev_fpsm) { /* video mode changed? */ fps_update_params(); cmos_update_params(); prev_zoom = current_zoom; prev_fpsm = current_fpsm; } if (!is_supported_mode()) { /* don't patch other video modes */ return; } for (uint32_t * buf = (uint32_t *) regs[0]; *buf != 0xFFFFFFFF; buf += 2) { uint32_t reg = *buf; uint32_t old = *(buf+1); int new = reg_override_generic(reg, old); if (new) { //~ if (reg == 0xc0f06014 || reg == 0xc0f06008 || reg == 0xc0f06804) //~ printf("[%x] %x: %x -> %x [z=%d]\n", regs[0], reg, old, new, PathDriveMode->zoom); *(buf+1) = new; } } } static int patch_active = 0; static void update_patch() { if (crop_enabled) { /* update preset */ fps_update_params(); cmos_update_params(); /* install our hooks, if we haven't already do so */ if (!patch_active) { patch_hook_function(CMOS_WRITE, MEM_CMOS_WRITE, &cmos_hook, "crop_rec: CMOS[1,2,6] parameters hook"); patch_hook_function(ADTG_WRITE, MEM_ADTG_WRITE, &adtg_hook, "crop_rec: ADTG[8000,8806] parameters hook"); if (ENGIO_WRITE) { patch_hook_function(ENGIO_WRITE, MEM_ENGIO_WRITE, engio_write_hook, "crop_rec: video timers hook"); } patch_active = 1; } } else { /* undo active patches, if any */ if (patch_active) { unpatch_memory(CMOS_WRITE); unpatch_memory(ADTG_WRITE); if (ENGIO_WRITE) { unpatch_memory(ENGIO_WRITE); } patch_active = 0; } } } /* enable patch when switching LiveView (not in the middle of LiveView) */ /* otherwise you will end up with a halfway configured video mode that looks weird */ PROP_HANDLER(PROP_LV_ACTION) { update_patch(); } static MENU_UPDATE_FUNC(crop_update) { if (!crop_enabled) return; const char * binning_labels[] = { "1:1", "3x3", "3x1", "1x3" }; MENU_SET_VALUE("ON, %dx%d %s", target_xres, target_yres, binning_labels[binning_mode]); if (!raw_lv_is_enabled()) { /* H.264 works only if original resolution is not modified */ if (xres_delta || yres_delta) { MENU_SET_WARNING(MENU_WARN_ADVICE, "This preset is not suitable for H.264 recording."); } else if (is_720p()) { MENU_SET_WARNING(MENU_WARN_INFO, "This preset will give distorted image in H.264."); } } } static MENU_UPDATE_FUNC(xres_update) { /* fixme */ //~ fps_update_params(); //~ cmos_update_params(); if (!menu_xres) { MENU_SET_VALUE("No change"); MENU_SET_RINFO("%d%s", target_xres, target_xres == get_default_xres() ? "" : "!"); return; } if (target_xres != menu_xres) { MENU_SET_RINFO("%d!", target_xres); } else if (xres_delta) { MENU_SET_RINFO("%+d", xres_delta); } } static MENU_UPDATE_FUNC(yres_update) { /* fixme */ //~ fps_update_params(); //~ cmos_update_params(); if (!menu_yres) { MENU_SET_VALUE("No change"); MENU_SET_RINFO("%d%s", target_yres, target_yres == get_default_yres() ? "" : "!"); return; } if (target_yres != menu_yres) { MENU_SET_RINFO("%d!", target_yres); } else if (yres_delta) { MENU_SET_RINFO("%+d", yres_delta); } } static MENU_UPDATE_FUNC(fps_update) { if (!fps_mode) { /* fixme: BOOL icon breaks the pickbox */ MENU_SET_ENABLED(0); } } static const struct { enum binning_mode binning_mode; uint32_t menu_xres; uint32_t menu_yres; int fps_mode; int shutter_range; } menu_presets[] = { { BINNING_1x1, 0, 0, 0, 0 }, { BINNING_3x3, 0, 1080, 0, 0 }, { BINNING_3x3, 0, 1080, 3, 0 }, { BINNING_1x1, 0, 0, 0, 0 }, { BINNING_1x1, 5208, 3478, -1, 1 }, { BINNING_1x1, 5208, 0, 0, 0 }, }; static MENU_SELECT_FUNC(select_preset) { int preset_index = (int) priv; binning_mode = menu_presets[preset_index].binning_mode; menu_xres = menu_presets[preset_index].menu_xres; menu_yres = menu_presets[preset_index].menu_yres; fps_mode = menu_presets[preset_index].fps_mode; shutter_range = menu_presets[preset_index].shutter_range; menu_close_submenu(); } static struct menu_entry crop_rec_menu[] = { { .name = "Crop mode", .priv = &crop_enabled, .max = 1, .update = crop_update, .depends_on = DEP_LIVEVIEW, .submenu_width = 710, .children = (struct menu_entry[]) { { .name = "Pixel binning", .priv = &menu_bin_mode, .max = 3, .choices = CHOICES("1:1", "3x3", "1x3", "3x1"), }, { .name = "X resolution", .priv = &menu_xres, .update = xres_update, .max = 5208, /* sensor_xres */ .unit = UNIT_DEC, }, { .name = "Y resolution", .priv = &menu_yres, .update = yres_update, .max = 3478, /* sensor_yres */ .unit = UNIT_DEC, }, { .name = "Frame rate", .priv = &fps_mode, .update = fps_update, .min = -1, .max = 3, //.icon_type = IT_BOOL, .choices = CHOICES("Maximum", "Canon menu", "Half", "Quarter", "45/48 from 50/60"), .help = "Choose the frame rate behavior:", .help2 = "Maximum frame rate possible at selected resolution.\n" "Canon menu: value selected there.\n" "Half: value selected in Canon menu, divided by 2.\n" "Quarter: value selected in Canon menu, divided by 4.\n" "45/48: select 50/60 FPS in Canon menu; they are remapped to 45/48p.\n" }, { .name = "Shutter range", .priv = &shutter_range, .max = 1, .icon_type = IT_BOOL, .choices = CHOICES("No change", "Full range"), .help = "Choose the available shutter speed range:", .help2 = "No change: default range used by Canon in selected video mode.\n" "Full range: from 1/FPS to minimum exposure time allowed by hardware." }, { .name = "Choose preset...", .select = menu_open_submenu, .children = (struct menu_entry[]) { { .name = "1080p 1:1 crop (3x)", .select = select_preset, .priv = 0, }, { .name = "720p 3x3", .select = select_preset, .priv = 1, }, { .name = "1080p45/48 3x3", .select = select_preset, .priv = 2, }, { .name = "Centered x5 zoom", .select = select_preset, .priv = 3, }, { .name = "Full-res LiveView", .select = select_preset, .priv = 4, }, { .name = "Full-width LiveView", .select = select_preset, .priv = 5, }, MENU_EOL }, }, MENU_EOL, }, }, }; static int crop_rec_needs_lv_refresh() { if (!lv) { return 0; } if (crop_enabled) { if (is_supported_mode()) { if (!patch_active || settings_changed) { return 1; } } } else /* crop disabled */ { if (patch_active) { return 1; } } return 0; } static void center_canon_preview() { /* center the preview window on the raw buffer */ /* overriding these registers once will do the trick... * ... until the focus box is moved by the user */ int old = cli(); uint32_t pos1 = shamem_read(0xc0f383d4); uint32_t pos2 = shamem_read(0xc0f383dc); if ((pos1 & 0x80008000) == 0x80008000 && (pos2 & 0x80008000) == 0x80008000) { /* already centered */ sei(old); return; } int x1 = pos1 & 0xFFFF; int x2 = pos2 & 0xFFFF; int y1 = pos1 >> 16; int y2 = pos2 >> 16; dbg_printf("[crop_rec] x1=%d x2=%d y1=%d y2=%d dx=%d dy=%d\n", x1, x2, y1, y2, x2 - x1, y2 - y1); int raw_xc = 0; int raw_yc = 0; if (is_700D) { if (x2 - x1 != 264 && y2 - y1 != 697) { /* not x5/x10 (values hardcoded for 700D) */ sei(old); return; } raw_xc = (72 + 2520) / 2 / 4; /* hardcoded for 5D3 */ raw_yc = (28 + 1080) / 2; /* values from raw_diag in x5 mode, OB zones */ if (1) { /* use the focus box position for moving the preview window around */ /* don't do that while recording! */ printf("[crop_rec] %d,%d ", raw_xc, raw_yc); raw_xc -= 72 / 2 / 4; raw_yc -= 28 / 2; /* this won't change the position if the focus box is centered */ get_afframe_pos(raw_xc * 2, raw_yc * 2, &raw_xc, &raw_yc); raw_xc += 72 / 2 / 4; raw_yc += 28 / 2; raw_xc = COERCE(raw_xc, 136, 510); /* trial and error; image freezes if we push outside these limits */ raw_yc = COERCE(raw_yc, 354, 754); /* trial and error; broken image at the edges, outside these limits */ printf("-> %d,%d using focus box position\n", raw_xc, raw_yc); } } int current_xc = (x1 + x2) / 2; int current_yc = (y1 + y2) / 2; int dx = (raw_xc - current_xc) & ~1; /* just for consistency */ int dy = (raw_yc - current_yc) & ~1; /* this must be even, otherwise the image turns pink */ if (dx || dy) { /* note: bits 0x80008000 appear to have no effect, * so we'll use them to flag the centered zoom mode, * e.g. for focus_box_get_raw_crop_offset */ printf("[crop_rec] centering zoom preview: dx=%d, dy=%d\n", dx, dy); EngDrvOutLV(0xc0f383d4, PACK32(x1 + dx, y1 + dy) | 0x80008000); EngDrvOutLV(0xc0f383dc, PACK32(x2 + dx, y2 + dy) | 0x80008000); } sei(old); } /* faster version than the one from ML core */ static void set_zoom(int zoom) { if (!lv) return; if (RECORDING) return; if (is_movie_mode() && PathDriveMode->DZ) return; zoom = COERCE(zoom, 1, 10); if (zoom > 1 && zoom < 10) zoom = 5; prop_request_change_wait(PROP_LV_DISPSIZE, &zoom, 4, 1000); } /* when closing ML menu, check whether we need to refresh the LiveView */ static unsigned int crop_rec_polling_cbr(unsigned int unused) { draw_line(360, 240-50, 360, 240+50, COLOR_WHITE); draw_line(360-50, 240, 360+50, 240, COLOR_WHITE); /* also check at startup */ static int lv_dirty = 1; int menu_shown = gui_menu_shown(); if (lv && menu_shown) { lv_dirty = 1; } if (!lv || menu_shown || RECORDING_RAW) { /* outside LV: no need to do anything */ /* don't change while browsing the menu, but shortly after closing it */ /* don't change while recording raw, but after recording stops * (H.264 should tolerate this pretty well, except maybe 50D) */ return CBR_RET_CONTINUE; } if (lv_dirty) { /* do we need to refresh LiveView? */ if (crop_rec_needs_lv_refresh()) { /* let's check this once again, just in case */ /* (possible race condition that would result in unnecessary refresh) */ msleep(500); if (crop_rec_needs_lv_refresh()) { info_led_on(); gui_uilock(UILOCK_EVERYTHING); int old_zoom = lv_dispsize; set_zoom(lv_dispsize == 1 ? 5 : 1); set_zoom(old_zoom); gui_uilock(UILOCK_NONE); info_led_off(); } } lv_dirty = 0; } if (binning_mode == BINNING_1x1 && (lv_dispsize == 5 || lv_dispsize == 10)) { center_canon_preview(); } return CBR_RET_CONTINUE; } /* Display recording status in top info bar */ static LVINFO_UPDATE_FUNC(crop_info) { LVINFO_BUFFER(16); /* info about current binning mode */ if (raw_lv_is_enabled()) { /* fixme: raw_capture_info is only updated when LV RAW is active */ if (raw_capture_info.binning_x + raw_capture_info.skipping_x == 1 && raw_capture_info.binning_y + raw_capture_info.skipping_y == 1) { STR_APPEND(buffer, "%s1:1", buffer[0] ? " " : ""); } else { STR_APPEND(buffer, "%s%dx%d", buffer[0] ? " " : "", raw_capture_info.binning_x + raw_capture_info.skipping_x, raw_capture_info.binning_y + raw_capture_info.skipping_y ); } } else if (!is_movie_mode()) { /* fixme: these shouldn't be needed, see above */ switch (binning_mode) { case BINNING_1x1: snprintf(buffer, sizeof(buffer), "1:1"); break; case BINNING_3x3: snprintf(buffer, sizeof(buffer), "3x3"); break; } } warn: if (crop_rec_needs_lv_refresh()) { if (!streq(buffer, SYM_WARNING)) { STR_APPEND(buffer, " " SYM_WARNING); } item->color_fg = COLOR_YELLOW; } } static struct lvinfo_item info_items[] = { { .name = "Crop info", .which_bar = LV_BOTTOM_BAR_ONLY, .update = crop_info, .preferred_position = -50, /* near the focal length display */ .priority = 1, } }; static unsigned int raw_info_update_cbr(unsigned int unused) { if (patch_active) { /* not implemented yet */ raw_capture_info.offset_x = raw_capture_info.offset_y = SHRT_MIN; if (lv_dispsize > 1) { /* raw backend gets it right */ return 0; } /* update horizontal pixel binning parameters */ switch (binning_mode) { case BINNING_1x1: case BINNING_3x1: raw_capture_info.binning_x = raw_capture_info.binning_y = 1; raw_capture_info.skipping_x = raw_capture_info.skipping_y = 0; break; case BINNING_3x3: case BINNING_1x3: raw_capture_info.binning_x = 3; raw_capture_info.skipping_x = 0; break; } /* update vertical pixel binning / line skipping parameters */ switch (binning_mode) { case BINNING_1x1: case BINNING_1x3: raw_capture_info.binning_y = 1; raw_capture_info.skipping_y = 0; break; case BINNING_3x3: case BINNING_3x1: { int b = (is_5D3) ? 3 : 1; int s = (is_5D3) ? 0 : 2; raw_capture_info.binning_y = b; raw_capture_info.skipping_y = s; break; } } if (is_5D3) { /* update skip offsets */ int skip_left, skip_right, skip_top, skip_bottom; calc_skip_offsets(&skip_left, &skip_right, &skip_top, &skip_bottom); raw_set_geometry(raw_info.width, raw_info.height, skip_left, skip_right, skip_top, skip_bottom); } } return 0; } static unsigned int crop_rec_init() { is_digic4 = is_camera("DIGIC", "4"); is_digic5 = is_camera("DIGIC", "5"); if (is_camera("5D3", "1.1.3") || is_camera("5D3", "1.2.3")) { /* same addresses on both 1.1.3 and 1.2.3 */ CMOS_WRITE = 0x119CC; MEM_CMOS_WRITE = 0xE92D47F0; ADTG_WRITE = 0x11640; MEM_ADTG_WRITE = 0xE92D47F0; ENGIO_WRITE = is_camera("5D3", "1.2.3") ? 0xFF290F98 : 0xFF28CC3C; MEM_ENGIO_WRITE = 0xE51FC15C; PathDriveMode = (void *) (is_camera("5D3", "1.2.3") ? 0x56414 : 0x563BC); /* argument of PATH_SelectPathDriveMode */ is_5D3 = 1; sensor_xres = 5796; sensor_yres = 3870; column_factor = 8; fps_main_clock = 24000000; /* 24p, 25p, 30p, 50p, 60p, x5 */ memcpy(default_timerA, (int[]) { 440, 480, 440, 480, 440, 518 }, 24); memcpy(default_timerB, (int[]) { 2275, 2000, 1820, 1000, 910, 1556 }, 24); } else if (is_camera("EOSM", "2.0.2")) { CMOS_WRITE = 0x2998C; MEM_CMOS_WRITE = 0xE92D41F0; ADTG_WRITE = 0x2986C; MEM_ADTG_WRITE = 0xE92D43F8; PathDriveMode = (void *) 0x24AB8; /* argument of PATH_SelectPathDriveMode */ is_EOSM = 1; } else if (is_camera("700D", "1.1.5") || is_camera("650D", "1.0.4")) { CMOS_WRITE = 0x17A1C; MEM_CMOS_WRITE = 0xE92D41F0; ADTG_WRITE = 0x178FC; MEM_ADTG_WRITE = 0xE92D43F8; ENGIO_WRITE = 0xFF2C2D00; MEM_ENGIO_WRITE = 0xE51FC15C; if (is_camera("700D", "1.1.5")) { is_700D = 1; PathDriveMode = (void *) 0x6B7F4; /* argument of PATH_SelectPathDriveMode */ } else if (is_camera("650D", "1.0.4")) { is_650D = 1; PathDriveMode = (void *) 0x6AEC0; /* argument of PATH_SelectPathDriveMode */ } is_700D = 1; sensor_xres = 5208; sensor_yres = 3478; } else if (is_camera("100D", "1.0.1")) { CMOS_WRITE = 0x475B8; MEM_CMOS_WRITE = 0xE92D41F0; ADTG_WRITE = 0x47144; MEM_ADTG_WRITE = 0xE92D43F8; PathDriveMode = (void *) 0x3C358; /* argument of PATH_SelectPathDriveMode */ is_100D = 1; } else if (is_camera("6D", "1.1.6")) { CMOS_WRITE = 0x2420C; MEM_CMOS_WRITE = 0xE92D41F0; ADTG_WRITE = 0x24108; MEM_ADTG_WRITE = 0xE92D41F0; PathDriveMode = (void *) 0x3BD70; /* argument of PATH_SelectPathDriveMode */ is_6D = 1; column_factor = 4; fps_main_clock = 25600000; /* 24p, 25p, 30p, 50p, 60p, x5 */ memcpy(default_timerA, (int[]) { 546, 640, 546, 640, 520, 730 }, 24); memcpy(default_timerB, (int[]) { 1955, 1600, 1564, 800, 821, 1172 }, 24); /* or 1956 1565 822 2445 1956 */ } /* default FPS timers are the same on all these models */ if (is_EOSM || is_700D || is_650D || is_100D) { column_factor = 4; fps_main_clock = 32000000; /* 24p, 25p, 30p, 50p, 60p, x5, c24p, c25p, c30p */ memcpy(default_timerA, (int[]) { 528, 640, 528, 640, 528, 716, 546, 640, 546 }, 36); memcpy(default_timerB, (int[]) { 2527, 2000, 2022, 1000, 1011, 1491, 2444, 2000, 1955 }, 36); /* or 2528 2023 1012 2445 1956 */ } /* FPS in x5 zoom may be model-dependent; assume exact */ default_fps_1k[5] = (uint64_t) fps_main_clock * 1000ULL / default_timerA[5] / default_timerB[5]; printf("[crop_rec] checking FPS timer values...\n"); for (int i = 0; i < COUNT(default_fps_1k); i++) { if (default_timerA[i]) { int fps_i = (uint64_t) fps_main_clock * 1000ULL / default_timerA[i] / default_timerB[i]; if (fps_i == default_fps_1k[i]) { printf("%d) %s%d.%03d: A=%d B=%d (exact)\n", i, FMT_FIXEDPOINT3(default_fps_1k[i]), default_timerA[i], default_timerB[i]); if (i == 5 && default_fps_1k[i] != 29970) { printf("-> unusual FPS in x5 zoom\n", i); } } else { int fps_p = (uint64_t) fps_main_clock * 1000ULL / default_timerA[i] / (default_timerB[i] + 1); if (fps_i > default_fps_1k[i] && fps_p < default_fps_1k[i]) { printf("%d) %s%d.%03d: A=%d B=%d/%d (averaged)\n", i, FMT_FIXEDPOINT3(default_fps_1k[i]), default_timerA[i], default_timerB[i], default_timerB[i] + 1); } else { printf("%d) %s%d.%03d: A=%d B=%d (%s%d.%03d ?!?)\n", i, FMT_FIXEDPOINT3(default_fps_1k[i]), default_timerA[i], default_timerB[i], FMT_FIXEDPOINT3(fps_i)); return CBR_RET_ERROR; } /* assume 25p is exact on all models */ if (i == 1) { printf("-> 25p check error\n"); return CBR_RET_ERROR; } } } } /* this is required */ if (!column_factor) { return CBR_RET_ERROR; } /* this is required for identifying video modes */ if (!PathDriveMode) { return CBR_RET_ERROR; } menu_add("Movie", crop_rec_menu, COUNT(crop_rec_menu)); lvinfo_add_items (info_items, COUNT(info_items)); return 0; } static unsigned int crop_rec_deinit() { return 0; } MODULE_INFO_START() MODULE_INIT(crop_rec_init) MODULE_DEINIT(crop_rec_deinit) MODULE_INFO_END() MODULE_CONFIGS_START() MODULE_CONFIG(crop_enabled) MODULE_CONFIG(menu_bin_mode) MODULE_CONFIG(menu_xres) MODULE_CONFIG(menu_yres) MODULE_CONFIG(fps_mode) MODULE_CONFIG(shutter_range) MODULE_CONFIGS_END() MODULE_CBRS_START() MODULE_CBR(CBR_SHOOT_TASK, crop_rec_polling_cbr, 0) MODULE_CBR(CBR_RAW_INFO_UPDATE, raw_info_update_cbr, 0) MODULE_CBRS_END() MODULE_PROPHANDLERS_START() MODULE_PROPHANDLER(PROP_LV_ACTION) MODULE_PROPHANDLERS_END()