diff options
author | Shuzhen Wang <shuzhenwang@google.com> | 2019-11-30 22:21:24 -0800 |
---|---|---|
committer | Shuzhen Wang <shuzhenwang@google.com> | 2019-12-11 15:56:27 -0800 |
commit | 4001a939cdba890e13c898fa3c9bb3b360c2316e (patch) | |
tree | 02bb3bbb63937811a9f5b225e5d8421966c7945c /devices | |
parent | ced12830a96a5008f86da038091d1562e2bc9813 (diff) | |
download | camera-4001a939cdba890e13c898fa3c9bb3b360c2316e.tar.gz |
EmulatedCamera: Add zoom ratio support
- Add zoom ratio support for both front and back cameras
- Add zoom ratio ranges for bokeh modes
- Handle zoom ratio withn EmulatorSensor to output zoomed image
Test: testZoomRatio, testBokehModes, manual testing of zoom
Bug: 130025314
Change-Id: I55f35d3769a2975a057f00eda73608e9e053b2cd
Diffstat (limited to 'devices')
-rw-r--r-- | devices/EmulatedCamera/hwl/EmulatedRequestState.cpp | 143 | ||||
-rw-r--r-- | devices/EmulatedCamera/hwl/EmulatedRequestState.h | 15 | ||||
-rw-r--r-- | devices/EmulatedCamera/hwl/EmulatedSensor.cpp | 36 | ||||
-rw-r--r-- | devices/EmulatedCamera/hwl/EmulatedSensor.h | 6 | ||||
-rw-r--r-- | devices/EmulatedCamera/hwl/configs/emu_camera_back.json | 19 | ||||
-rw-r--r-- | devices/EmulatedCamera/hwl/configs/emu_camera_front.json | 60 |
6 files changed, 230 insertions, 49 deletions
diff --git a/devices/EmulatedCamera/hwl/EmulatedRequestState.cpp b/devices/EmulatedCamera/hwl/EmulatedRequestState.cpp index a2929fe..09c66ed 100644 --- a/devices/EmulatedCamera/hwl/EmulatedRequestState.cpp +++ b/devices/EmulatedCamera/hwl/EmulatedRequestState.cpp @@ -631,12 +631,15 @@ status_t EmulatedRequestState::InitializeSensorSettings( } } + float min_zoom = min_zoom_, max_zoom = max_zoom_; ret = request_settings_->Get(ANDROID_CONTROL_BOKEH_MODE, &entry); if ((ret == OK) && (entry.count == 1)) { bool bokeh_mode_valid = false; for (const auto& bokeh_cap : available_bokeh_caps_) { if (bokeh_cap.mode == entry.data.u8[0]) { bokeh_mode_ = entry.data.u8[0]; + min_zoom = bokeh_cap.min_zoom; + max_zoom = bokeh_cap.max_zoom; bokeh_mode_valid = true; break; } @@ -650,6 +653,12 @@ status_t EmulatedRequestState::InitializeSensorSettings( } } + // Check zoom ratio range and override to supported range + ret = request_settings_->Get(ANDROID_CONTROL_ZOOM_RATIO, &entry); + if ((ret == OK) && (entry.count == 1)) { + zoom_ratio_ = std::min(std::max(entry.data.f[0], min_zoom), max_zoom); + } + // 3A modes are active in case the scene is disabled or set to face priority // or the control mode is not using scenes if ((scene_mode_ == ANDROID_CONTROL_SCENE_MODE_DISABLED) || @@ -731,6 +740,7 @@ status_t EmulatedRequestState::InitializeSensorSettings( sensor_settings->report_neutral_color_point = report_neutral_color_point_; sensor_settings->report_green_split = report_green_split_; sensor_settings->report_noise_profile = report_noise_profile_; + sensor_settings->zoom_ratio = zoom_ratio_; return OK; } @@ -863,10 +873,12 @@ std::unique_ptr<HwlPipelineResult> EmulatedRequestState::InitializeResult( result->result_metadata->Set(ANDROID_STATISTICS_SCENE_FLICKER, ¤t_scene_flicker_, 1); } + if (zoom_ratio_supported_) { + result->result_metadata->Set(ANDROID_CONTROL_ZOOM_RATIO, &zoom_ratio_, 1); + } if (report_bokeh_mode_) { result->result_metadata->Set(ANDROID_CONTROL_BOKEH_MODE, &bokeh_mode_, 1); } - return result; } @@ -1608,41 +1620,135 @@ status_t EmulatedRequestState::InitializeControlDefaults() { return BAD_VALUE; } - ret = static_metadata_->Get(ANDROID_CONTROL_AVAILABLE_BOKEH_CAPABILITIES, + ret = static_metadata_->Get(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, + &entry); + if ((ret == OK) && (entry.count > 0)) { + if (entry.count != 1) { + ALOGE("%s: Invalid max digital zoom capability!", __FUNCTION__); + return BAD_VALUE; + } + max_zoom_ = entry.data.f[0]; + } else { + ALOGE("%s: No available max digital zoom", __FUNCTION__); + return BAD_VALUE; + } + + ret = static_metadata_->Get(ANDROID_CONTROL_ZOOM_RATIO_RANGE, &entry); + if ((ret == OK) && (entry.count > 0)) { + if (entry.count != 2) { + ALOGE("%s: Invalid zoom ratio range capability!", __FUNCTION__); + return BAD_VALUE; + } + + if (entry.data.f[1] != max_zoom_) { + ALOGE("%s: Max zoom ratio must be equal to max digital zoom", + __FUNCTION__); + return BAD_VALUE; + } + + if (entry.data.f[1] < entry.data.f[0]) { + ALOGE("%s: Max zoom ratio must be larger than min zoom ratio", + __FUNCTION__); + return BAD_VALUE; + } + + // Sanity check request and result keys + if (available_requests_.find(ANDROID_CONTROL_ZOOM_RATIO) == + available_requests_.end()) { + ALOGE("%s: Zoom ratio tag must be available in available request keys", + __FUNCTION__); + return BAD_VALUE; + } + if (available_results_.find(ANDROID_CONTROL_ZOOM_RATIO) == + available_results_.end()) { + ALOGE("%s: Zoom ratio tag must be available in available result keys", + __FUNCTION__); + return BAD_VALUE; + } + + zoom_ratio_supported_ = true; + min_zoom_ = entry.data.f[0]; + } + + ret = static_metadata_->Get(ANDROID_CONTROL_AVAILABLE_BOKEH_MAX_SIZES, &entry); if ((ret == OK) && (entry.count > 0)) { if (entry.count % 3 != 0) { ALOGE("%s: Invalid bokeh capabilities!", __FUNCTION__); return BAD_VALUE; } + + camera_metadata_ro_entry_t zoom_ratio_ranges_entry; + ret = static_metadata_->Get( + ANDROID_CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES, + &zoom_ratio_ranges_entry); + if (ret != OK || + zoom_ratio_ranges_entry.count / 2 != entry.count / 3 - 1) { + ALOGE("%s: Invalid bokeh mode zoom ratio ranges.", __FUNCTION__); + return BAD_VALUE; + } + + // Sanity check request and characteristics keys + if (available_requests_.find(ANDROID_CONTROL_BOKEH_MODE) == + available_requests_.end()) { + ALOGE("%s: Bokeh mode must be configurable for this device", + __FUNCTION__); + return BAD_VALUE; + } + if (available_characteristics_.find( + ANDROID_CONTROL_AVAILABLE_BOKEH_MAX_SIZES) == + available_characteristics_.end() || + available_characteristics_.find( + ANDROID_CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES) == + available_characteristics_.end()) { + ALOGE( + "%s: Bokeh maxSizes and zoomRatioRanges characteristics keys must " + "be available", + __FUNCTION__); + return BAD_VALUE; + } + + // Derive available bokeh caps. StreamConfigurationMap stream_configuration_map(*static_metadata_); std::set<StreamSize> yuv_sizes = stream_configuration_map.GetOutputSizes( HAL_PIXEL_FORMAT_YCBCR_420_888); - bool hasBokehOff = false; - for (size_t i = 0; i < entry.count; i += 3) { - BokehCapability bokeh(entry.data.i32[i], entry.data.i32[i + 1], - entry.data.i32[i + 2]); - if (bokeh.mode < ANDROID_CONTROL_BOKEH_MODE_OFF || - bokeh.mode > ANDROID_CONTROL_BOKEH_MODE_CONTINUOUS) { - ALOGE("%s: Invalid bokeh mode %d", __FUNCTION__, bokeh.mode); + bool has_bokeh_off = false; + for (size_t i = 0, j = 0; i < entry.count; i += 3) { + int32_t mode = entry.data.i32[i]; + int32_t max_width = entry.data.i32[i + 1]; + int32_t max_height = entry.data.i32[i + 2]; + float min_zoom_ratio, max_zoom_ratio; + + if (mode < ANDROID_CONTROL_BOKEH_MODE_OFF || + mode > ANDROID_CONTROL_BOKEH_MODE_CONTINUOUS) { + ALOGE("%s: Invalid bokeh mode %d", __FUNCTION__, mode); return BAD_VALUE; } - if (bokeh.mode == ANDROID_CONTROL_BOKEH_MODE_OFF) { - hasBokehOff = true; - if (bokeh.max_width != 0 || bokeh.max_height != 0) { + + if (mode == ANDROID_CONTROL_BOKEH_MODE_OFF) { + has_bokeh_off = true; + if (max_width != 0 || max_height != 0) { ALOGE("%s: Invalid max width or height for BOKEH_MODE_OFF", __FUNCTION__); return BAD_VALUE; } - } else if (yuv_sizes.find({bokeh.max_width, bokeh.max_height}) == - yuv_sizes.end()) { + min_zoom_ratio = min_zoom_; + max_zoom_ratio = max_zoom_; + } else if (yuv_sizes.find({max_width, max_height}) == yuv_sizes.end()) { ALOGE("%s: Invalid max width or height for bokeh mode %d", - __FUNCTION__, bokeh.mode); + __FUNCTION__, mode); return BAD_VALUE; + } else { + min_zoom_ratio = zoom_ratio_ranges_entry.data.f[j]; + max_zoom_ratio = zoom_ratio_ranges_entry.data.f[j + 1]; + j += 2; } + + BokehCapability bokeh(mode, max_width, max_height, min_zoom_ratio, + max_zoom_ratio); available_bokeh_caps_.push_back(bokeh); } - if (!hasBokehOff) { + if (!has_bokeh_off) { ALOGE("%s: Off bokeh mode not supported!", __FUNCTION__); return BAD_VALUE; } @@ -1733,6 +1839,7 @@ status_t EmulatedRequestState::InitializeControlDefaults() { uint8_t ae_lock = ANDROID_CONTROL_AE_LOCK_OFF; uint8_t awb_lock = ANDROID_CONTROL_AWB_LOCK_OFF; int32_t ae_target_fps[] = {ae_target_fps_.min_fps, ae_target_fps_.max_fps}; + float zoom_ratio = 1.0f; switch (template_idx) { case RequestTemplate::kManual: intent = ANDROID_CONTROL_CAPTURE_INTENT_MANUAL; @@ -1813,6 +1920,10 @@ status_t EmulatedRequestState::InitializeControlDefaults() { default_requests_[idx]->Set(ANDROID_CONTROL_AE_EXPOSURE_COMPENSATION, &exposure_compensation_, 1); } + if (zoom_ratio_supported_) { + default_requests_[idx]->Set(ANDROID_CONTROL_ZOOM_RATIO, &zoom_ratio, + 1); + } bool is_auto_antbanding_supported = available_antibanding_modes_.find( ANDROID_CONTROL_AE_ANTIBANDING_MODE_AUTO) != diff --git a/devices/EmulatedCamera/hwl/EmulatedRequestState.h b/devices/EmulatedCamera/hwl/EmulatedRequestState.h index 590da70..ab86656 100644 --- a/devices/EmulatedCamera/hwl/EmulatedRequestState.h +++ b/devices/EmulatedCamera/hwl/EmulatedRequestState.h @@ -151,11 +151,16 @@ class EmulatedRequestState { struct BokehCapability { int32_t mode, max_width, max_height; + float min_zoom, max_zoom; BokehCapability() - : mode(ANDROID_CONTROL_BOKEH_MODE_OFF), max_width(-1), max_height(-1) { + : mode(ANDROID_CONTROL_BOKEH_MODE_OFF), + max_width(-1), + max_height(-1), + min_zoom(1.0f), + max_zoom(1.0f) { } - BokehCapability(int32_t m, int32_t w, int32_t h) - : mode(m), max_width(w), max_height(h) { + BokehCapability(int32_t m, int32_t w, int32_t h, float min_z, float max_z) + : mode(m), max_width(w), max_height(h), min_zoom(min_z), max_zoom(max_z) { } }; @@ -171,6 +176,9 @@ class EmulatedRequestState { std::unordered_map<uint8_t, SceneOverride> scene_overrides_; std::vector<FPSRange> available_fps_ranges_; int32_t exposure_compensation_range_[2] = {0, 0}; + float max_zoom_ = 1.0f; + bool zoom_ratio_supported_ = false; + float min_zoom_ = 1.0f; camera_metadata_rational exposure_compensation_step_ = {0, 1}; bool exposure_compensation_supported_ = false; int32_t exposure_compensation_ = 0; @@ -193,6 +201,7 @@ class EmulatedRequestState { uint8_t af_trigger_ = ANDROID_CONTROL_AF_TRIGGER_IDLE; uint8_t ae_trigger_ = ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_IDLE; FPSRange ae_target_fps_ = {0, 0}; + float zoom_ratio_ = 1.0f; uint8_t bokeh_mode_ = ANDROID_CONTROL_BOKEH_MODE_OFF; static const int32_t kMinimumStreamingFPS = 20; bool ae_lock_available_ = false; diff --git a/devices/EmulatedCamera/hwl/EmulatedSensor.cpp b/devices/EmulatedCamera/hwl/EmulatedSensor.cpp index 1ba2bcb..fe2cf41 100644 --- a/devices/EmulatedCamera/hwl/EmulatedSensor.cpp +++ b/devices/EmulatedCamera/hwl/EmulatedSensor.cpp @@ -634,9 +634,10 @@ bool EmulatedSensor::threadLoop() { .height = jpeg_input->height, .planes = jpeg_input->yuv_planes}; - auto ret = ProcessYUV420(yuv_input, yuv_output, - device_settings->second.gain, - reprocess_request, device_chars->second); + auto ret = ProcessYUV420( + yuv_input, yuv_output, device_settings->second.gain, + reprocess_request, device_settings->second.zoom_ratio, + device_chars->second); if (ret != 0) { (*b)->stream_buffer.status = BufferStatus::kError; break; @@ -674,9 +675,10 @@ bool EmulatedSensor::threadLoop() { YUV420Frame yuv_output{.width = (*b)->width, .height = (*b)->height, .planes = (*b)->plane.img_y_crcb}; - auto ret = - ProcessYUV420(yuv_input, yuv_output, device_settings->second.gain, - reprocess_request, device_chars->second); + auto ret = ProcessYUV420( + yuv_input, yuv_output, device_settings->second.gain, + reprocess_request, device_settings->second.zoom_ratio, + device_chars->second); if (ret != 0) { (*b)->stream_buffer.status = BufferStatus::kError; } @@ -961,6 +963,7 @@ void EmulatedSensor::CaptureRGB(uint8_t* img, uint32_t width, uint32_t height, void EmulatedSensor::CaptureYUV420(YCbCrPlanes yuv_layout, uint32_t width, uint32_t height, uint32_t gain, + float zoom_ratio, const SensorCharacteristics& chars) { ATRACE_CALL(); float total_gain = gain / 100.0 * GetBaseGainFactor(chars.max_raw_value); @@ -979,14 +982,19 @@ void EmulatedSensor::CaptureYUV420(YCbCrPlanes yuv_layout, uint32_t width, const int scale_out_sq = scale_out * scale_out; // after multiplies // inc = how many pixels to skip while reading every next pixel - uint32_t inc_h = ceil((float)chars.width / width); - uint32_t inc_v = ceil((float)chars.height / height); - for (unsigned int y = 0, out_y = 0; y < chars.height; y += inc_v, out_y++) { + for (unsigned int out_y = 0; out_y < height; out_y++) { uint8_t* px_y = yuv_layout.img_y + out_y * yuv_layout.y_stride; uint8_t* px_cb = yuv_layout.img_cb + (out_y / 2) * yuv_layout.cbcr_stride; uint8_t* px_cr = yuv_layout.img_cr + (out_y / 2) * yuv_layout.cbcr_stride; - scene_->SetReadoutPixel(0, y); for (unsigned int out_x = 0; out_x < yuv_layout.y_stride; out_x++) { + int x = static_cast<int>(chars.width * (0.5f - 0.5f / zoom_ratio + + out_x / (width * zoom_ratio))); + int y = static_cast<int>(chars.height * (0.5f - 0.5f / zoom_ratio + + out_y / (height * zoom_ratio))); + x = std::min(std::max(x, 0), (int)chars.width - 1); + y = std::min(std::max(y, 0), (int)chars.height - 1); + scene_->SetReadoutPixel(x, y); + int32_t r_count, g_count, b_count; // TODO: Perfect demosaicing is a cheat const uint32_t* pixel = scene_->GetPixelElectrons(); @@ -1015,9 +1023,6 @@ void EmulatedSensor::CaptureYUV420(YCbCrPlanes yuv_layout, uint32_t width, px_cr += yuv_layout.cbcr_step; px_cb += yuv_layout.cbcr_step; } - - // Skip unprocessed pixels from sensor. - for (unsigned int j = 1; j < inc_h; j++) scene_->GetPixelElectrons(); } } ALOGVV("YUV420 sensor image captured"); @@ -1054,7 +1059,7 @@ void EmulatedSensor::CaptureDepth(uint8_t* img, uint32_t gain, uint32_t width, status_t EmulatedSensor::ProcessYUV420(const YUV420Frame& input, const YUV420Frame& output, uint32_t gain, - bool reprocess_request, + bool reprocess_request, float zoom_ratio, const SensorCharacteristics& chars) { ATRACE_CALL(); size_t input_width, input_height; @@ -1100,7 +1105,8 @@ status_t EmulatedSensor::ProcessYUV420(const YUV420Frame& input, .y_stride = static_cast<uint32_t>(input_width), .cbcr_stride = static_cast<uint32_t>(input_width) / 2, .cbcr_step = 1}; - CaptureYUV420(input_planes, input_width, input_height, gain, chars); + CaptureYUV420(input_planes, input_width, input_height, gain, zoom_ratio, + chars); } output_planes = output.planes; diff --git a/devices/EmulatedCamera/hwl/EmulatedSensor.h b/devices/EmulatedCamera/hwl/EmulatedSensor.h index 3f19754..15a144c 100644 --- a/devices/EmulatedCamera/hwl/EmulatedSensor.h +++ b/devices/EmulatedCamera/hwl/EmulatedSensor.h @@ -186,6 +186,7 @@ class EmulatedSensor : private Thread, public virtual RefBase { bool report_neutral_color_point = false; bool report_green_split = false; bool report_noise_profile = false; + float zoom_ratio = 1.0f; }; // Maps physical and logical camera ids to individual device settings @@ -289,7 +290,8 @@ class EmulatedSensor : private Thread, public virtual RefBase { uint32_t stride, RGBLayout layout, uint32_t gain, const SensorCharacteristics& chars); void CaptureYUV420(YCbCrPlanes yuv_layout, uint32_t width, uint32_t height, - uint32_t gain, const SensorCharacteristics& chars); + uint32_t gain, float zoom_ratio, + const SensorCharacteristics& chars); void CaptureDepth(uint8_t* img, uint32_t gain, uint32_t width, uint32_t height, uint32_t stride, const SensorCharacteristics& chars); @@ -301,7 +303,7 @@ class EmulatedSensor : private Thread, public virtual RefBase { status_t ProcessYUV420(const YUV420Frame& input, const YUV420Frame& output, uint32_t gain, bool reprocess_request, - const SensorCharacteristics& chars); + float zoom_ratio, const SensorCharacteristics& chars); inline int32_t ApplysRGBGamma(int32_t value, int32_t saturation); diff --git a/devices/EmulatedCamera/hwl/configs/emu_camera_back.json b/devices/EmulatedCamera/hwl/configs/emu_camera_back.json index b321c74..5680c9f 100644 --- a/devices/EmulatedCamera/hwl/configs/emu_camera_back.json +++ b/devices/EmulatedCamera/hwl/configs/emu_camera_back.json @@ -42,13 +42,17 @@ "3", "4" ], - "android.control.availableBokehCapabilities": [ + "android.control.availableBokehMaxSizes": [ "0", "0", "0", "1", "1856", - "1392" + "1392" + ], + "android.control.availableBokehZoomRatioRanges": [ + "0.5", + "4.0" ], "android.control.availableEffects": [ "0" @@ -89,6 +93,10 @@ "1", "1" ], + "android.control.zoomRatioRange": [ + "1.0", + "10.0" + ], "android.edge.availableEdgeModes": [ "0", "1", @@ -249,6 +257,7 @@ "786447", "65575", "65579", + "65580", "983050", "393217", "1572865", @@ -320,7 +329,8 @@ "3", "917528", "65576", - "65580", + "65581", + "65583", "-2080374783", "-2080374782", "-2080374780" @@ -396,7 +406,8 @@ "917520", "917522", "65576", - "65580", + "65581", + "65583", "917523", "917526", "-2080374783", diff --git a/devices/EmulatedCamera/hwl/configs/emu_camera_front.json b/devices/EmulatedCamera/hwl/configs/emu_camera_front.json index 382ce95..40812c5 100644 --- a/devices/EmulatedCamera/hwl/configs/emu_camera_front.json +++ b/devices/EmulatedCamera/hwl/configs/emu_camera_front.json @@ -46,7 +46,7 @@ "3", "4" ], - "android.control.availableBokehCapabilities": [ + "android.control.availableBokehMaxSizes": [ "0", "0", "0", @@ -57,6 +57,12 @@ "1920", "1440" ], + "android.control.availableBokehZoomRatioRanges": [ + "1.0", + "1.0", + "1.0", + "1.0" + ], "android.control.availableEffects": [ "0" ], @@ -160,6 +166,10 @@ "1", "0" ], + "android.control.zoomRatioRange": [ + "1.0", + "8.0" + ], "android.distortionCorrection.availableModes": [ "0" ], @@ -304,6 +314,8 @@ "65574", "65575", "65579", + "65580", + "65582", "1638402", "1638401", "1638403", @@ -410,7 +422,8 @@ "65552", "65553", "65576", - "65580", + "65581", + "65583", "1769472", "196608", "262146", @@ -460,7 +473,8 @@ "65570", "65551", "65576", - "65580", + "65581", + "65583", "196608", "262146", "262149", @@ -1231,7 +1245,7 @@ "3", "4" ], - "android.control.availableBokehCapabilities": [ + "android.control.availableBokehMaxSizes": [ "0", "0", "0", @@ -1242,6 +1256,12 @@ "1920", "1440" ], + "android.control.availableBokehZoomRatioRanges": [ + "1.0", + "1.0", + "1.0", + "1.0" + ], "android.control.availableEffects": [ "0" ], @@ -1344,6 +1364,10 @@ "1", "0" ], + "android.control.zoomRatioRange": [ + "1.0", + "8.0" + ], "android.distortionCorrection.availableModes": [ "0" ], @@ -1479,6 +1503,8 @@ "65574", "65575", "65579", + "65580", + "65582", "1638402", "1638401", "1638403", @@ -1585,7 +1611,8 @@ "65552", "65553", "65576", - "65580", + "65581", + "65583", "1769472", "196608", "262146", @@ -1635,7 +1662,8 @@ "65570", "65551", "65576", - "65580", + "65581", + "65583", "196608", "262146", "262149", @@ -2409,7 +2437,7 @@ "3", "4" ], - "android.control.availableBokehCapabilities": [ + "android.control.availableBokehMaxSizes": [ "0", "0", "0", @@ -2420,6 +2448,12 @@ "1920", "1440" ], + "android.control.availableBokehZoomRatioRanges": [ + "1.0", + "1.0", + "1.0", + "1.0" + ], "android.control.availableEffects": [ "0" ], @@ -2523,6 +2557,10 @@ "1", "0" ], + "android.control.zoomRatioRange": [ + "1.0", + "8.0" + ], "android.distortionCorrection.availableModes": [ "0" ], @@ -2658,6 +2696,8 @@ "65574", "65575", "65579", + "65580", + "65582", "1638402", "1638401", "1638403", @@ -2764,7 +2804,8 @@ "65552", "65553", "65576", - "65580", + "65581", + "65583", "1769472", "196608", "262146", @@ -2814,7 +2855,8 @@ "65570", "65551", "65576", - "65580", + "65581", + "65583", "196608", "262146", "262149", |