本讲是android camera专题系列的第28讲,我们介绍android camera2 api专题的连拍实战。
更多资源:
资源 | 描述 |
---|---|
在线课程 | |
知识星球 | 星球名称:深入浅出android camera 星球id: 17296815 |
极客笔记圈 |
通过连拍实现三种连续拍图的需求
连拍获取多张图片
连拍获取多张不同曝光的图片
连拍获取多张不同对焦距离的图片
geekcamera2连拍功能
burst type
连拍实现方式
-
captureburst
-
多次调用capture方法
点击拍照调用流程
mainactivity#clickedtakephoto
mainactivity#takepicture
mainactivity#takepicturepressed
preview#takepicturepressed
preview#takepicture
preview#takephoto
preview#takephotowhenfocused 决定burst type
cameracontroller2#setbursttype
cameracontroller2#takepicture
cameracontroller2#takepictureafterprecapture
连拍获取多张不同曝光或不同对焦距离的图片
private void takepictureburstbracketing() {
if( mydebug.log )
log.i(tag, "takepictureburstbracketing");
if( burst_type != bursttype.bursttype_expo && burst_type != bursttype.bursttype_focus ) {
log.e(tag, "takepictureburstbracketing called but unexpected burst_type: " burst_type);
}
list requests = new arraylist<>();
boolean ok = true;
errorcallback push_take_picture_error_cb = null;
synchronized( background_camera_lock ) {
if( mcameradevice == null || mcameracapturesession == null ) {
if( mydebug.log )
log.i(tag, "no camera or capture session");
return;
}
try {
if( mydebug.log ) {
log.i(tag, "imagereader: " imagereader.tostring());
log.i(tag, "imagereader surface: " imagereader.getsurface().tostring());
}
capturerequest.builder stillbuilder = mcameradevice.createcapturerequest(previewisvideomode ? cameradevice.template_video_snapshot : cameradevice.template_still_capture);
stillbuilder.set(capturerequest.control_capture_intent, capturerequest.control_capture_intent_still_capture);
// n.b., don't set requesttagtype.capture here - we only do it for the last of the burst captures (see below)
camera_settings.setupbuilder(stillbuilder, true);
clearpending();
// shouldn't add preview surface as a target - see note in takepictureafterprecapture()
// but also, adding the preview surface causes the dark/light exposures to be visible, which we don't want
stillbuilder.addtarget(imagereader.getsurface());
if( raw_todo )
stillbuilder.addtarget(imagereaderraw.getsurface());
if( burst_type == bursttype.bursttype_expo ) {
if( mydebug.log )
log.i(tag, "[cs]expo bracketing");
/*stillbuilder.set(capturerequest.control_ae_mode, camerametadata.control_ae_mode_on);
stillbuilder.set(capturerequest.flash_mode, camerametadata.flash_mode_off);
stillbuilder.set(capturerequest.control_ae_exposure_compensation, -6);
requests.add( stillbuilder.build() );
stillbuilder.set(capturerequest.control_ae_exposure_compensation, 0);
requests.add( stillbuilder.build() );
stillbuilder.set(capturerequest.control_ae_exposure_compensation, 6);
requests.add( stillbuilder.build() );*/
stillbuilder.set(capturerequest.control_ae_mode, camerametadata.control_ae_mode_off);
if( use_fake_precapture_mode && fake_precapture_torch_performed ) {
if( mydebug.log )
log.i(tag, "setting torch for capture");
stillbuilder.set(capturerequest.flash_mode, camerametadata.flash_mode_torch);
test_fake_flash_photo ;
}
// else don't turn torch off, as user may be in torch on mode
range iso_range = characteristics.get(cameracharacteristics.sensor_info_sensitivity_range); // may be null on some devices
if( iso_range == null ) {
log.e(tag, "takepictureburstbracketing called but null iso_range");
}
else {
// set iso
int iso = 800;
// obtain current iso/etc settings from the capture result - but if we're in manual iso mode,
// might as well use the settings the user has actually requested (also useful for workaround for
// oneplus 3t bug where the reported iso and exposure_time are wrong in dark scenes)
if( camera_settings.has_iso )
iso = camera_settings.iso;
else if( capture_result_has_iso )
iso = capture_result_iso;
// see https://sourceforge.net/p/opencamera/tickets/321/ - some devices may have auto iso that's
// outside of the allowed manual iso range!
iso = math.max(iso, iso_range.getlower());
iso = math.min(iso, iso_range.getupper());
stillbuilder.set(capturerequest.sensor_sensitivity, iso );
}
if( capture_result_has_frame_duration )
stillbuilder.set(capturerequest.sensor_frame_duration, capture_result_frame_duration);
else
stillbuilder.set(capturerequest.sensor_frame_duration, 1000000000l/30);
long base_exposure_time = 1000000000l/30;
if( camera_settings.has_iso )
base_exposure_time = camera_settings.exposure_time;
else if( capture_result_has_exposure_time )
base_exposure_time = capture_result_exposure_time;
int n_half_images = expo_bracketing_n_images/2;
long min_exposure_time = base_exposure_time;
long max_exposure_time = base_exposure_time;
final double scale = math.pow(2.0, expo_bracketing_stops/(double)n_half_images);
range exposure_time_range = characteristics.get(cameracharacteristics.sensor_info_exposure_time_range); // may be null on some devices
if( exposure_time_range != null ) {
min_exposure_time = exposure_time_range.getlower();
max_exposure_time = exposure_time_range.getupper();
}
if( mydebug.log ) {
log.i(tag, "taking expo bracketing with n_images: " expo_bracketing_n_images);
log.i(tag, "iso: " stillbuilder.get(capturerequest.sensor_sensitivity));
log.i(tag, "frame duration: " stillbuilder.get(capturerequest.sensor_frame_duration));
log.i(tag, "base exposure time: " base_exposure_time);
log.i(tag, "min exposure time: " min_exposure_time);
log.i(tag, "max exposure time: " max_exposure_time);
}
// darker images
for(int i=0;i max_exposure_time )
exposure_time = max_exposure_time;
if( mydebug.log ) {
log.i(tag, "add burst request for " i "th light image:");
log.i(tag, " this_scale: " this_scale);
log.i(tag, " exposure_time: " exposure_time);
}
stillbuilder.set(capturerequest.sensor_exposure_time, exposure_time);
if( i == n_half_images - 1 ) {
// requesttagtype.capture should only be set for the last request, otherwise we'll may do things like turning
// off torch (for fake flash) before all images are received
// more generally, doesn't seem a good idea to be doing the post-capture commands (resetting ae state etc)
// multiple times, and before all captures are complete!
if( mydebug.log )
log.i(tag, "set requesttagtype.capture for last burst request");
stillbuilder.settag(new requesttagobject(requesttagtype.capture));
}
else {
stillbuilder.settag(new requesttagobject(requesttagtype.capture_burst_in_progress));
}
requests.add( stillbuilder.build() );
}
}
burst_single_request = true;
} else { // bursttype_focus
if( mydebug.log )
log.i(tag, "[cs] focus bracketing");
if( use_fake_precapture_mode && fake_precapture_torch_performed ) {
if( mydebug.log )
log.i(tag, "setting torch for capture");
if( !camera_settings.has_iso )
stillbuilder.set(capturerequest.control_ae_mode, camerametadata.control_ae_mode_on);
stillbuilder.set(capturerequest.flash_mode, camerametadata.flash_mode_torch);
test_fake_flash_photo ;
}
stillbuilder.set(capturerequest.control_af_mode, camerametadata.control_af_mode_off); // just in case
if( math.abs(camera_settings.focus_distance - focus_bracketing_source_distance) < 1.0e-5 ) {
if( mydebug.log )
log.i(tag, "current focus matches source");
}
else if( math.abs(camera_settings.focus_distance - focus_bracketing_target_distance) < 1.0e-5 ) {
if( mydebug.log )
log.i(tag, "current focus matches target");
}
else {
log.i(tag, "current focus matches neither source nor target");
}
list focus_distances = setupfocusbracketingdistances(focus_bracketing_source_distance, focus_bracketing_target_distance, focus_bracketing_n_images);
if( focus_bracketing_add_infinity ) {
focus_distances.add(0.0f);
}
for(int i=0;i
正常连拍
private void takepictureburst(boolean continuing_fast_burst) {
if( mydebug.log )
log.i(tag, "takepictureburst continuing_fast_burst:" continuing_fast_burst);
if( burst_type != bursttype.bursttype_normal && burst_type != bursttype.bursttype_continuous ) {
log.e(tag, "takepictureburstbracketing called but unexpected burst_type: " burst_type);
}
boolean is_new_burst = true;
capturerequest request = null;
capturerequest last_request = null;
boolean ok = true;
errorcallback push_take_picture_error_cb = null;
synchronized( background_camera_lock ) {
if( mcameradevice == null || mcameracapturesession == null ) {
if( mydebug.log )
log.i(tag, "no camera or capture session");
return;
}
try {
if( mydebug.log ) {
log.i(tag, "imagereader: " imagereader.tostring());
log.i(tag, "imagereader surface: " imagereader.getsurface().tostring());
}
capturerequest.builder stillbuilder = mcameradevice.createcapturerequest(previewisvideomode ? cameradevice.template_video_snapshot : cameradevice.template_still_capture);
stillbuilder.set(capturerequest.control_capture_intent, capturerequest.control_capture_intent_still_capture);
// n.b., don't set requesttagtype.capture here - we only do it for the last of the burst captures (see below)
camera_settings.setupbuilder(stillbuilder, true);
if( use_fake_precapture_mode && fake_precapture_torch_performed ) {
if( mydebug.log )
log.i(tag, "setting torch for capture");
if( !camera_settings.has_iso )
stillbuilder.set(capturerequest.control_ae_mode, camerametadata.control_ae_mode_on);
stillbuilder.set(capturerequest.flash_mode, camerametadata.flash_mode_torch);
test_fake_flash_photo ;
}
if( burst_type == bursttype.bursttype_normal && burst_for_noise_reduction ) {
// must be done after calling setupbuilder(), so we override the default edge_mode and noise_reduction_mode
if( mydebug.log )
log.i(tag, "optimise settings for burst_for_noise_reduction");
stillbuilder.set(capturerequest.noise_reduction_mode, capturerequest.noise_reduction_mode_off);
stillbuilder.set(capturerequest.color_correction_aberration_mode, capturerequest.color_correction_aberration_mode_off);
stillbuilder.set(capturerequest.edge_mode, capturerequest.edge_mode_off);
}
if( !continuing_fast_burst ) {
clearpending();
}
// shouldn't add preview surface as a target - see note in takepictureafterprecapture()
stillbuilder.addtarget(imagereader.getsurface());
// raw target added below
if( use_fake_precapture_mode && fake_precapture_torch_performed ) {
stillbuilder.set(capturerequest.flash_mode, camerametadata.flash_mode_torch);
test_fake_flash_photo ;
}
// else don't turn torch off, as user may be in torch on mode
if( burst_type == bursttype.bursttype_continuous ) {
if( mydebug.log )
log.i(tag, "continuous burst mode");
raw_todo = false; // raw works in continuous burst mode, but makes things very slow...
if( continuing_fast_burst ) {
if( mydebug.log )
log.i(tag, "continuing fast burst");
n_burst ;
is_new_burst = false;
/*if( !continuous_burst_in_progress ) // test bug where we call callback oncompleted() before all burst images are received
n_burst = 1;*/
}
else {
if( mydebug.log )
log.i(tag, "start continuous burst");
continuous_burst_in_progress = true;
n_burst = 1;
n_burst_taken = 0;
}
if( mydebug.log )
log.i(tag, "n_burst is now " n_burst);
}
else if( burst_for_noise_reduction ) {
if( mydebug.log )
log.i(tag, "choose n_burst for burst_for_noise_reduction");
n_burst = 4;
n_burst_taken = 0;
if( capture_result_has_iso ) {
// for nexus 6, max reported iso is 1196, so the limit for dark scenes shouldn't be more than this
// nokia 8's max reported iso is 1551
// note that oneplus 3t has max reported iso of 800, but this is a device bug
if( capture_result_iso >= iso_for_dark ) {
if( mydebug.log )
log.i(tag, "optimise for dark scene");
n_burst = noise_reduction_low_light ? n_images_nr_dark_low_light : n_images_nr_dark;
boolean is_oneplus = build.manufacturer.tolowercase(locale.us).contains("oneplus");
// oneplus 3t at least has bug where manual iso can't be set to above 800, so dark images end up too dark -
// so no point enabling this code, which is meant to brighten the scene, not make it darker!
if( !camera_settings.has_iso && !is_oneplus ) {
long exposure_time = noise_reduction_low_light ? 1000000000l/3 : 1000000000l/10;
if( !capture_result_has_exposure_time || capture_result_exposure_time < exposure_time ) {
if( mydebug.log )
log.i(tag, "also set long exposure time");
modified_from_camera_settings = true;
setmanualexposuretime(stillbuilder, exposure_time);
}
else {
if( mydebug.log )
log.i(tag, "no need to extend exposure time for dark scene, already long enough: " exposure_time);
}
}
}
else if( capture_result_has_exposure_time ) {
//final double full_exposure_time_scale = 0.5;
final double full_exposure_time_scale = math.pow(2.0, -0.5);
final long fixed_exposure_time = 1000000000l/60; // we only scale the exposure time at all if it's less than this value
final long scaled_exposure_time = 1000000000l/120; // we only scale the exposure time by the full_exposure_time_scale if the exposure time is less than this value
long exposure_time = capture_result_exposure_time;
if( exposure_time <= fixed_exposure_time ) {
if( mydebug.log )
log.i(tag, "optimise for bright scene");
//n_burst = 2;
n_burst = 3;
if( !camera_settings.has_iso ) {
double exposure_time_scale = getscaleforexposuretime(exposure_time, fixed_exposure_time, scaled_exposure_time, full_exposure_time_scale);
exposure_time *= exposure_time_scale;
if( mydebug.log ) {
log.i(tag, "reduce exposure shutter speed further, was: " exposure_time);
log.i(tag, "exposure_time_scale: " exposure_time_scale);
}
modified_from_camera_settings = true;
setmanualexposuretime(stillbuilder, exposure_time);
}
}
}
}
}
else {
if( mydebug.log )
log.i(tag, "user requested n_burst");
n_burst = burst_requested_n_images;
n_burst_taken = 0;
}
if( raw_todo )
stillbuilder.addtarget(imagereaderraw.getsurface());
n_burst_total = n_burst;
n_burst_raw = raw_todo ? n_burst : 0;
burst_single_request = false;
if( mydebug.log )
log.i(tag, "n_burst: " n_burst);
stillbuilder.settag(new requesttagobject(requesttagtype.capture_burst_in_progress));
request = stillbuilder.build();
stillbuilder.settag(new requesttagobject(requesttagtype.capture));
last_request = stillbuilder.build();
// n.b., don't stop the preview with stop.repeating when capturing a burst
}
catch(cameraaccessexception e) {
if( mydebug.log ) {
log.e(tag, "failed to take picture burst");
log.e(tag, "reason: " e.getreason());
log.e(tag, "message: " e.getmessage());
}
e.printstacktrace();
ok = false;
jpeg_todo = false;
raw_todo = false;
picture_cb = null;
push_take_picture_error_cb = take_picture_error_cb;
}
}
// need to call callbacks without a lock
if( ok && picture_cb != null && is_new_burst ) {
if( mydebug.log )
log.i(tag, "call onstarted() in callback");
picture_cb.onstarted();
}
if( ok ) {
synchronized( background_camera_lock ) {
if( mcameradevice == null || mcameracapturesession == null ) {
if( mydebug.log )
log.i(tag, "no camera or capture session");
return;
}
try {
final boolean use_burst = true;
//final boolean use_burst = false;
if( burst_type == bursttype.bursttype_continuous ) {
if( mydebug.log ) {
log.i(tag, "continuous capture");
if( !continuous_burst_in_progress )
log.i(tag, " last continuous capture");
}
continuous_burst_requested_last_capture = !continuous_burst_in_progress;
int sequenceid = mcameracapturesession.capture(continuous_burst_in_progress ? request : last_request, previewcapturecallback, mcamerabackgroundhandler);
log.i(tag, "[create_sequence] takepictureburst bursttype_continuous capture sequenceid:" sequenceid);
if( continuous_burst_in_progress ) {
final int continuous_burst_rate_ms = 100;
// also take the next burst after a delay
mcamerabackgroundhandler.postdelayed(new runnable() {
@override
public void run() {
// note, even if continuous_burst_in_progress has become false by this point, still take one last
// photo, as need to ensure that we have a request with requesttagtype.capture, as well as ensuring
// we call the oncompleted() method of the callback
if( mydebug.log ) {
log.i(tag, "take next continuous burst");
log.i(tag, "continuous_burst_in_progress: " continuous_burst_in_progress);
log.i(tag, "n_burst: " n_burst);
}
if( n_burst >= 10 || n_burst_raw >= 10 ) {
// nokia 8 in std mode without post-processing options doesn't hit this limit (we only hit this
// if it's set to "n_burst >= 5")
if( mydebug.log ) {
log.i(tag, "...but wait for continuous burst, as waiting for too many photos");
}
//throw new runtimeexception(); // test
mcamerabackgroundhandler.postdelayed(this, continuous_burst_rate_ms);
}
else if( picture_cb.imagequeuewouldblock(n_burst_raw, n_burst 1) ) {
if( mydebug.log ) {
log.i(tag, "...but wait for continuous burst, as image queue would block");
}
//throw new runtimeexception(); // test
mcamerabackgroundhandler.postdelayed(this, continuous_burst_rate_ms);
}
else {
takepictureburst(true);
}
}
}, continuous_burst_rate_ms);
}
}
else if( use_burst ) {
list requests = new arraylist<>();
for(int i=0;i 0 ) {
mcamerabackgroundhandler.postdelayed(this, burst_delay);
}
}
catch(cameraaccessexception e) {
if( mydebug.log ) {
log.e(tag, "failed to take picture burst");
log.e(tag, "reason: " e.getreason());
log.e(tag, "message: " e.getmessage());
}
e.printstacktrace();
jpeg_todo = false;
raw_todo = false;
picture_cb = null;
push_take_picture_error_cb = take_picture_error_cb;
}
// need to call callbacks without a lock
if( push_take_picture_error_cb != null ) {
push_take_picture_error_cb.onerror();
}
}
}
}.run();
}
if( !continuing_fast_burst ) {
playsound(mediaactionsound.shutter_click); // play shutter sound asap, otherwise user has the illusion of being slow to take photos
}
}
catch(cameraaccessexception e) {
if( mydebug.log ) {
log.e(tag, "failed to take picture burst");
log.e(tag, "reason: " e.getreason());
log.e(tag, "message: " e.getmessage());
}
e.printstacktrace();
//noinspection unusedassignment
ok = false;
jpeg_todo = false;
raw_todo = false;
picture_cb = null;
push_take_picture_error_cb = take_picture_error_cb;
}
}
}
// need to call callbacks without a lock
if( push_take_picture_error_cb != null ) {
push_take_picture_error_cb.onerror();
}
}