From d51a2fb2632b78aca628b4a949f26f3926e94dd0 Mon Sep 17 00:00:00 2001 From: Martijn Braam Date: Thu, 20 Jul 2023 16:20:23 +0200 Subject: [PATCH] Refactor controls --- CMakeLists.txt | 1 + src/io_pipeline.c | 404 +++++++++++++++++++++-------------------- src/main.c | 23 ++- src/process_pipeline.c | 4 + src/process_pipeline.h | 5 + src/state.h | 42 ++++- 6 files changed, 273 insertions(+), 206 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f73446d..d93c0e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,7 @@ set_source_files_properties( ) add_dependencies(megapixels-gtk dummy-resource) target_link_libraries(megapixels-gtk + m ${GTK4_LIBRARIES} ${FEEDBACK_LIBRARIES} ${TIFF_LIBRARIES} diff --git a/src/io_pipeline.c b/src/io_pipeline.c index 90d581c..618d018 100644 --- a/src/io_pipeline.c +++ b/src/io_pipeline.c @@ -4,6 +4,7 @@ #include "flash.h" #include "pipeline.h" #include "process_pipeline.h" +#include "state.h" #include #include #include @@ -13,36 +14,10 @@ #include #include -libmegapixels_camera *io_camera = NULL; -libmegapixels_mode *mode_capture = NULL; -libmegapixels_mode *mode_preview = NULL; +mp_state_io state_io; + MPCamera *mpcamera = NULL; -static bool just_switched_mode = false; -static int blank_frame_count = 0; - -static int burst_length; -static int captures_remaining = 0; - -static int preview_width; -static int preview_height; - -static int device_rotation; - -struct control_state { - bool gain_is_manual; - int gain; - - bool exposure_is_manual; - int exposure; -}; - -static struct control_state desired_controls = {}; -static struct control_state current_controls = {}; - -static bool flash_enabled = false; - -static bool want_focus = false; static MPPipeline *pipeline; static GSource *capture_source; @@ -57,9 +32,7 @@ void mp_io_pipeline_start() { mp_process_pipeline_start(); - pipeline = mp_pipeline_new(); - mp_pipeline_invoke(pipeline, setup, NULL, 0); } @@ -69,57 +42,59 @@ mp_io_pipeline_stop() if (capture_source) { g_source_destroy(capture_source); } - mp_pipeline_free(pipeline); - mp_process_pipeline_stop(); } +/* + * Update state from IO -> Process + */ static void update_process_pipeline() { // Grab the latest control values - if (!current_controls.gain_is_manual) { - // current_controls.gain = - // mp_camera_control_get_int32(info->camera, - // info->gain_ctrl); + if (!state_io.gain.manual && state_io.gain.control) { + state_io.gain.value = mp_camera_control_get_int32( + state_io.camera, state_io.gain.control); } - if (!current_controls.exposure_is_manual) { - // current_controls.exposure = - // mp_camera_control_get_int32(info->camera, - // V4L2_CID_EXPOSURE); + + if (!state_io.exposure.manual && state_io.exposure.control) { + state_io.exposure.value = mp_camera_control_get_int32( + state_io.camera, state_io.exposure.control); } MPControl control; float balance_red = 1.0f; float balance_blue = 1.0f; - /* - if (mp_camera_query_control(info->camera, V4L2_CID_RED_BALANCE, &control)) { - int red = mp_camera_control_get_int32(info->camera, - V4L2_CID_RED_BALANCE); - int blue = mp_camera_control_get_int32(info->camera, - V4L2_CID_BLUE_BALANCE); - balance_red = (float)red / (float)control.max; - balance_blue = (float)blue / (float)control.max; + if (state_io.red.control && state_io.blue.control) { + int red = mp_camera_control_get_int32(state_io.camera, + state_io.red.control); + int blue = mp_camera_control_get_int32(state_io.camera, + state_io.blue.control); + balance_red = (float)red / (float)state_io.red.max; + balance_blue = (float)blue / (float)state_io.blue.max; } - */ struct mp_process_pipeline_state pipeline_state = { - .camera = io_camera, - .burst_length = burst_length, - .preview_width = preview_width, - .preview_height = preview_height, - .device_rotation = device_rotation, - .gain_is_manual = current_controls.gain_is_manual, - .gain = current_controls.gain, - .gain_max = 1, // TODO: Fix + .camera = state_io.camera, + .burst_length = state_io.burst_length, + .preview_width = state_io.preview_width, + .preview_height = state_io.preview_height, + .device_rotation = state_io.device_rotation, + .gain_is_manual = state_io.gain.manual, + .gain = state_io.gain.value, + .gain_max = state_io.gain.max, .balance_red = balance_red, .balance_blue = balance_blue, - .exposure_is_manual = current_controls.exposure_is_manual, - .exposure = current_controls.exposure, - .has_auto_focus_continuous = false, // TODO: fix - .has_auto_focus_start = false, // TODO: fix - .flash_enabled = flash_enabled, + .exposure_is_manual = state_io.exposure.manual, + .exposure = state_io.exposure.value, + .has_auto_focus_continuous = state_io.focus.control != 0, + .has_auto_focus_start = state_io.can_af_trigger, + .flash_enabled = state_io.flash_enabled, + .control_gain = state_io.gain.control != 0, + .control_exposure = state_io.exposure.control != 0, + .control_focus = state_io.focus.control != 0, + .control_flash = true, }; mp_process_pipeline_update_state(&pipeline_state); } @@ -127,7 +102,7 @@ update_process_pipeline() static void focus(MPPipeline *pipeline, const void *data) { - want_focus = true; + state_io.trigger_af = true; } void @@ -139,32 +114,30 @@ mp_io_pipeline_focus() static void capture(MPPipeline *pipeline, const void *data) { - uint32_t gain; float gain_norm; // Disable the autogain/exposure while taking the burst - /* TODO: Fix - mp_camera_control_set_int32(info->camera, V4L2_CID_AUTOGAIN, 0); + mp_camera_control_set_int32(state_io.camera, V4L2_CID_AUTOGAIN, 0); mp_camera_control_set_int32( - info->camera, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL); - */ + state_io.camera, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_MANUAL); // Get current gain to calculate a burst length; // with low gain there's 3, with the max automatic gain of the ov5640 // the value seems to be 248 which creates a 5 frame burst // for manual gain you can go up to 11 frames - gain = mp_camera_control_get_int32(io_camera, V4L2_CID_GAIN); - // gain_norm = (float)gain / (float)mpcamera.gain_max; - // burst_length = (int)fmax(sqrt(gain_norm) * 10, 2) + 1; - burst_length = MIN(1, burst_length); - captures_remaining = burst_length; + state_io.gain.value = + mp_camera_control_get_int32(state_io.camera, V4L2_CID_GAIN); + gain_norm = (float)state_io.gain.value / (float)state_io.gain.max; + state_io.burst_length = (int)fmax(sqrtf(gain_norm) * 10, 2) + 1; + state_io.burst_length = MIN(1, state_io.burst_length); + state_io.captures_remaining = state_io.burst_length; // Change camera mode for capturing mp_process_pipeline_sync(); mp_camera_stop_capture(mpcamera); - struct v4l2_format format = {0}; - libmegapixels_select_mode(io_camera, mode_capture, &format); - just_switched_mode = true; + struct v4l2_format format = { 0 }; + libmegapixels_select_mode(state_io.camera, state_io.mode_capture, &format); + state_io.flush_pipeline = true; mp_camera_start_capture(mpcamera); @@ -211,59 +184,58 @@ start_focus() !mp_camera_check_task_complete(mpcamera, focus_continuous_task)) return; - /* TODO: implement - if (mpcamera.has_auto_focus_continuous) { + if (state_io.focus.control) { focus_continuous_task = mp_camera_control_set_bool_bg( - info->camera, V4L2_CID_FOCUS_AUTO, 1); - } else if (info->has_auto_focus_start) { + state_io.camera, state_io.focus.control, 1); + } else if (state_io.can_af_trigger) { start_focus_task = mp_camera_control_set_bool_bg( - info->camera, V4L2_CID_AUTO_FOCUS_START, 1); + state_io.camera, V4L2_CID_AUTO_FOCUS_START, 1); } - */ } static void update_controls() { // Don't update controls while capturing - if (captures_remaining > 0) { + if (state_io.captures_remaining > 0) { return; } - /* TODO: implement - if (want_focus) { - start_focus(mpcamera); - want_focus = false; + + if (state_io.trigger_af) { + state_io.trigger_af = false; + start_focus(); } - if (current_controls.gain_is_manual != desired_controls.gain_is_manual) { - mp_camera_control_set_bool_bg(info->camera, + if (state_io.gain.manual != state_io.gain.manual_req) { + mp_camera_control_set_bool_bg(state_io.camera, V4L2_CID_AUTOGAIN, - !desired_controls.gain_is_manual); + !state_io.gain.manual_req); + state_io.gain.manual = state_io.gain.manual_req; } - if (desired_controls.gain_is_manual && - current_controls.gain != desired_controls.gain) { - mp_camera_control_set_int32_bg( - info->camera, info->gain_ctrl, desired_controls.gain); + if (state_io.gain.manual && state_io.gain.value != state_io.gain.value_req) { + mp_camera_control_set_int32_bg(state_io.camera, + state_io.gain.control, + state_io.gain.value_req); + state_io.gain.value = state_io.gain.value_req; } - if (current_controls.exposure_is_manual != - desired_controls.exposure_is_manual) { - mp_camera_control_set_int32_bg(info->camera, - V4L2_CID_EXPOSURE_AUTO, - desired_controls.exposure_is_manual ? - V4L2_EXPOSURE_MANUAL : - V4L2_EXPOSURE_AUTO); + if (state_io.exposure.manual != state_io.exposure.manual_req) { + mp_camera_control_set_bool_bg(state_io.camera, + V4L2_CID_EXPOSURE_AUTO, + state_io.exposure.manual_req ? + V4L2_EXPOSURE_MANUAL : + V4L2_EXPOSURE_AUTO); + state_io.exposure.manual = state_io.exposure.manual_req; } - if (desired_controls.exposure_is_manual && - current_controls.exposure != desired_controls.exposure) { - mp_camera_control_set_int32_bg( - info->camera, V4L2_CID_EXPOSURE, desired_controls.exposure); + if (state_io.exposure.manual && + state_io.exposure.value != state_io.exposure.value_req) { + mp_camera_control_set_int32_bg(state_io.camera, + state_io.exposure.control, + state_io.exposure.value_req); + state_io.exposure.value = state_io.exposure.value_req; } - - current_controls = desired_controls; - */ } static void @@ -274,11 +246,12 @@ on_frame(MPBuffer buffer, void *_data) // When the mode is switched while capturing we get a couple blank frames, // presumably from buffers made ready during the switch. Ignore these. - if (just_switched_mode) { - if (blank_frame_count < 20) { + if (state_io.flush_pipeline) { + if (state_io.blank_frame_count < 20) { // Only check a 10x10 area - size_t test_size = MIN(10, io_camera->current_mode->width) * - MIN(10, io_camera->current_mode->height); + size_t test_size = + MIN(10, state_io.camera->current_mode->width) * + MIN(10, state_io.camera->current_mode->height); bool image_is_blank = true; for (size_t i = 0; i < test_size; ++i) { @@ -288,45 +261,44 @@ on_frame(MPBuffer buffer, void *_data) } if (image_is_blank) { - ++blank_frame_count; + ++state_io.blank_frame_count; return; } } else { printf("Blank image limit reached, resulting capture may be blank\n"); } - just_switched_mode = false; - blank_frame_count = 0; + state_io.flush_pipeline = false; + state_io.blank_frame_count = 0; } // Send the image off for processing mp_process_pipeline_process_image(buffer); - if (captures_remaining > 0) { - --captures_remaining; + if (state_io.captures_remaining > 0) { + --state_io.captures_remaining; - if (captures_remaining == 0) { + if (state_io.captures_remaining == 0) { // Restore the auto exposure and gain if needed - if (!current_controls.exposure_is_manual) { + if (!state_io.exposure.manual) { mp_camera_control_set_int32_bg( - io_camera, + state_io.camera, V4L2_CID_EXPOSURE_AUTO, V4L2_EXPOSURE_AUTO); } - /* TODO: implement - if (!current_controls.gain_is_manual) { + if (!state_io.gain.manual) { mp_camera_control_set_bool_bg( - info->camera, V4L2_CID_AUTOGAIN, true); + state_io.camera, V4L2_CID_AUTOGAIN, true); } - */ // Go back to preview mode mp_process_pipeline_sync(); mp_camera_stop_capture(mpcamera); - struct v4l2_format format = {0}; - libmegapixels_select_mode(io_camera, mode_preview, &format); - just_switched_mode = true; + struct v4l2_format format = { 0 }; + libmegapixels_select_mode( + state_io.camera, state_io.mode_preview, &format); + state_io.flush_pipeline = true; mp_camera_start_capture(mpcamera); @@ -342,20 +314,82 @@ on_frame(MPBuffer buffer, void *_data) } } +static void +init_controls() +{ + if (mp_camera_query_control( + state_io.camera, V4L2_CID_FOCUS_ABSOLUTE, NULL)) { + // TODO: Set focus state + state_io.focus.control = V4L2_CID_FOCUS_ABSOLUTE; + } else { + state_io.focus.control = 0; + } + + if (mp_camera_query_control(state_io.camera, V4L2_CID_FOCUS_AUTO, NULL)) { + mp_camera_control_set_bool_bg( + state_io.camera, V4L2_CID_FOCUS_AUTO, true); + } + + state_io.can_af_trigger = mp_camera_query_control( + state_io.camera, V4L2_CID_AUTO_FOCUS_START, NULL); + + MPControl control; + if (mp_camera_query_control(state_io.camera, V4L2_CID_GAIN, &control)) { + state_io.gain.control = V4L2_CID_GAIN; + state_io.gain.max = control.max; + } else if (mp_camera_query_control( + state_io.camera, V4L2_CID_ANALOGUE_GAIN, &control)) { + state_io.gain.control = V4L2_CID_ANALOGUE_GAIN; + state_io.gain.max = control.max; + } else { + state_io.gain.max = 0; + state_io.gain.control = 0; + } + if (state_io.gain.control) { + state_io.gain.value = mp_camera_control_get_int32( + state_io.camera, state_io.gain.control); + } else { + state_io.gain.value = 0; + } + + state_io.gain.manual = + mp_camera_control_get_bool(state_io.camera, V4L2_CID_AUTOGAIN) == 0; + + state_io.exposure.value = + mp_camera_control_get_int32(state_io.camera, V4L2_CID_EXPOSURE); + state_io.exposure.manual = + mp_camera_control_get_int32(state_io.camera, + V4L2_CID_EXPOSURE_AUTO) == + V4L2_EXPOSURE_MANUAL; + + if (mp_camera_query_control( + state_io.camera, V4L2_CID_RED_BALANCE, &control)) { + state_io.red.control = V4L2_CID_RED_BALANCE; + state_io.red.max = control.max; + } else { + state_io.red.control = 0; + } + + if (mp_camera_query_control( + state_io.camera, V4L2_CID_BLUE_BALANCE, &control)) { + state_io.blue.control = V4L2_CID_BLUE_BALANCE; + state_io.blue.max = control.max; + } else { + state_io.blue.control = 0; + } +} + +/* + * State transfer from Main -> IO + */ static void update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) { - // Make sure the state isn't updated more than it needs to be by checking - // whether this state change actually changes anything. - bool has_changed = false; - - if (io_camera != state->camera) { - has_changed = true; - - if (io_camera != NULL) { + if (state_io.camera != state->camera) { + if (state_io.camera != NULL) { mp_process_pipeline_sync(); mp_camera_stop_capture(mpcamera); - libmegapixels_close(io_camera); + libmegapixels_close(state_io.camera); } if (capture_source) { @@ -363,90 +397,68 @@ update_state(MPPipeline *pipeline, const struct mp_io_pipeline_state *state) capture_source = NULL; } - io_camera = state->camera; - if (io_camera) { - libmegapixels_open(io_camera); - mpcamera = mp_camera_new(io_camera); - mode_preview = NULL; - mode_capture = NULL; - for (int m = 0; m < io_camera->num_modes; m++) { - if (io_camera->modes[m]->rate > 29) { - mode_preview = io_camera->modes[m]; + state_io.camera = state->camera; + if (state_io.camera) { + libmegapixels_open(state_io.camera); + mpcamera = mp_camera_new(state_io.camera); + state_io.mode_preview = NULL; + state_io.mode_capture = NULL; + for (int m = 0; m < state_io.camera->num_modes; m++) { + if (state_io.camera->modes[m]->rate > 29) { + state_io.mode_preview = + state_io.camera->modes[m]; break; } } long area = 0; - for (int m = 0; m < io_camera->num_modes; m++) { - long this_pixels = io_camera->modes[m]->width * - io_camera->modes[m]->height; + for (int m = 0; m < state_io.camera->num_modes; m++) { + long this_pixels = state_io.camera->modes[m]->width * + state_io.camera->modes[m]->height; if (this_pixels > area) { area = this_pixels; - mode_capture = io_camera->modes[m]; + state_io.mode_capture = + state_io.camera->modes[m]; } } - if (mode_preview == NULL && mode_capture != NULL) { - // If no fast preview mode is available, make do with - // slow modes. - mode_preview = mode_capture; + if (state_io.mode_preview == NULL && + state_io.mode_capture != NULL) { + // If no fast preview mode is available, make due + // with slow modes. + state_io.mode_preview = state_io.mode_capture; } - if (mode_preview != NULL) { - if (io_camera->video_fd == 0) { - libmegapixels_open(io_camera); + if (state_io.mode_preview != NULL) { + if (state_io.camera->video_fd == 0) { + libmegapixels_open(state_io.camera); } - struct v4l2_format format = {0}; - libmegapixels_select_mode(io_camera, mode_preview, &format); + struct v4l2_format format = { 0 }; + libmegapixels_select_mode(state_io.camera, + state_io.mode_preview, + &format); } mp_camera_start_capture(mpcamera); capture_source = mp_pipeline_add_capture_source( pipeline, mpcamera, on_frame, NULL); - current_controls.gain_is_manual = - mp_camera_control_get_bool(io_camera, - V4L2_CID_AUTOGAIN) == 0; - // current_controls.gain = - // mp_camera_control_get_int32(camera, - // info->gain_ctrl); - - // current_controls.exposure_is_manual = - // mp_camera_control_get_int32( - // info->camera, V4L2_CID_EXPOSURE_AUTO) == - // V4L2_EXPOSURE_MANUAL; - // current_controls.exposure = mp_camera_control_get_int32( - // info->camera, V4L2_CID_EXPOSURE); + init_controls(); } } - has_changed = has_changed || burst_length != state->burst_length || - preview_width != state->preview_width || - preview_height != state->preview_height || - device_rotation != state->device_rotation; + state_io.burst_length = state->burst_length; + state_io.preview_width = state->preview_width; + state_io.preview_height = state->preview_height; + state_io.device_rotation = state->device_rotation; - burst_length = state->burst_length; - preview_width = state->preview_width; - preview_height = state->preview_height; - device_rotation = state->device_rotation; + if (state_io.camera) { + state_io.gain.manual_req = state->gain_is_manual; + state_io.gain.value_req = state->gain; + state_io.exposure.manual_req = state->exposure_is_manual; + state_io.exposure.value_req = state->exposure; - if (io_camera) { - struct control_state previous_desired = desired_controls; - - desired_controls.gain_is_manual = state->gain_is_manual; - desired_controls.gain = state->gain; - desired_controls.exposure_is_manual = state->exposure_is_manual; - desired_controls.exposure = state->exposure; - - has_changed = has_changed || - memcmp(&previous_desired, - &desired_controls, - sizeof(struct control_state)) != 0 || - flash_enabled != state->flash_enabled; - - flash_enabled = state->flash_enabled; + state_io.flash_enabled = state->flash_enabled; } - assert(has_changed); - update_process_pipeline(); } diff --git a/src/main.c b/src/main.c index 3087e6e..b4a4de9 100644 --- a/src/main.c +++ b/src/main.c @@ -48,8 +48,6 @@ RENDERDOC_API_1_1_2 *rdoc_api = NULL; mp_state_main state; -static bool camera_is_initialized = false; - static MPProcessPipelineBuffer *current_preview_buffer = NULL; static char last_path[260] = ""; @@ -65,7 +63,11 @@ GtkWidget *process_spinner; GtkWidget *scanned_codes; GtkWidget *preview_top_box; GtkWidget *preview_bottom_box; + GtkWidget *flash_button; +GtkWidget *iso_button; +GtkWidget *shutter_button; + LfbEvent *capture_event; GSettings *settings; @@ -106,16 +108,14 @@ update_io_pipeline() mp_io_pipeline_update_state(&io_state); // Make the right settings available for the camera - gtk_widget_set_visible(flash_button, state.flash_enabled); + gtk_widget_set_visible(flash_button, state.control_flash); + gtk_widget_set_visible(iso_button, state.control_gain); + gtk_widget_set_visible(shutter_button, state.control_exposure); } static bool update_state(const mp_state_main *new_state) { - if (!camera_is_initialized) { - camera_is_initialized = true; - } - if (state.camera == new_state->camera) { state.gain_is_manual = new_state->gain_is_manual; state.gain = new_state->gain; @@ -131,6 +131,11 @@ update_state(const mp_state_main *new_state) state.preview_buffer_width = new_state->preview_buffer_width; state.preview_buffer_height = new_state->preview_buffer_height; + + state.control_gain = new_state->control_gain; + state.control_exposure = new_state->control_exposure; + state.control_focus = new_state->control_focus; + state.control_flash = new_state->control_flash; return false; } @@ -1121,9 +1126,9 @@ activate(GtkApplication *app, gpointer data) "/org/postmarketos/Megapixels/camera.ui"); GtkWidget *window = GTK_WIDGET(gtk_builder_get_object(builder, "window")); - GtkWidget *iso_button = + iso_button = GTK_WIDGET(gtk_builder_get_object(builder, "iso-controls-button")); - GtkWidget *shutter_button = GTK_WIDGET( + shutter_button = GTK_WIDGET( gtk_builder_get_object(builder, "shutter-controls-button")); flash_button = GTK_WIDGET(gtk_builder_get_object(builder, "flash-controls-button")); diff --git a/src/process_pipeline.c b/src/process_pipeline.c index 97be8ea..1015e8e 100644 --- a/src/process_pipeline.c +++ b/src/process_pipeline.c @@ -1083,6 +1083,10 @@ update_state(MPPipeline *pipeline, const struct mp_process_pipeline_state *state .has_auto_focus_start = state->has_auto_focus_start, .preview_buffer_width = output_buffer_width, .preview_buffer_height = output_buffer_height, + .control_gain = state->control_gain, + .control_exposure = state->control_exposure, + .control_focus = state->control_focus, + .control_flash = state->control_flash, }; mp_main_update_state(&new_state); } diff --git a/src/process_pipeline.h b/src/process_pipeline.h index 705f257..b237bc8 100644 --- a/src/process_pipeline.h +++ b/src/process_pipeline.h @@ -29,6 +29,11 @@ struct mp_process_pipeline_state { bool has_auto_focus_start; bool flash_enabled; + + bool control_gain; + bool control_exposure; + bool control_flash; + bool control_focus; }; bool mp_process_find_processor(char *script); diff --git a/src/state.h b/src/state.h index 22556d2..1de92f1 100644 --- a/src/state.h +++ b/src/state.h @@ -19,6 +19,11 @@ typedef struct state_main { int burst_length; // Control state + bool control_gain; + bool control_exposure; + bool control_flash; + bool control_focus; + bool gain_is_manual; int gain; int gain_max; @@ -30,4 +35,39 @@ typedef struct state_main { bool has_auto_focus_continuous; bool has_auto_focus_start; -} mp_state_main; \ No newline at end of file +} mp_state_main; + +typedef struct cstate { + uint32_t control; + int32_t value; + int32_t value_req; + int32_t max; + bool manual; + bool manual_req; +} controlstate; + +typedef struct state_io { + libmegapixels_camera *camera; + libmegapixels_mode *mode_capture; + libmegapixels_mode *mode_preview; + + int burst_length; + int captures_remaining; + bool flush_pipeline; + int blank_frame_count; + + // Control state + controlstate gain; + controlstate exposure; + controlstate focus; + controlstate red; + controlstate blue; + bool can_af_trigger; + bool trigger_af; + bool flash_enabled; + + // State passed through to the process pipeline + int preview_width; + int preview_height; + int device_rotation; +} mp_state_io; \ No newline at end of file