I'm experiencing a bad behavior when capturing a photo on a Samsung Galaxy S7 edge device.
I wait simultaneously for focus and exposure before taking the picture but at the end I have a kind of blurred image compared to the native camera app output.
The problem is especially visible on zoomed images but is present also when not zoomed. I've also tried to enable the optical image stabilization but the problem isn't fixed.
Below are linked sample images explaining the problem.
Native camera image
My camera image
Here is the code:
Method for picture capture step 1:
public void capturePicture() {
CameraState state = getState();
if (state != IDLE && state != CLOSING && state != TAKE_PICTURE) {
boolean af = false;
boolean ae = false;
if (isAFEnabled()) {
af = true;
} else if (isAEEnabled()) {
ae = true;
}
if (!af && !ae) {
takePicture();
} else {
triggerFocusAndExposure(true);
}
}
return;
}
Method for triggering focus and exposure:
private void triggerFocusAndExposure(boolean picture) {
setState(WAIT_PRECAPTURE_PICTURE);
if (isAFEnabled()) {
previewBuilder.set(CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_START);
}
if (isAEEnabled()) {
previewBuilder.set(CONTROL_AE_PRECAPTURE_TRIGGER, CONTROL_AE_PRECAPTURE_TRIGGER_START);
}
try {
cameraSession.capture(previewBuilder.build(), new SCameraCaptureSession.CaptureCallback() {
#Override public void onCaptureCompleted(SCameraCaptureSession session, SCaptureRequest request, STotalCaptureResult result) {
setState(PRECAPTURE_TRIGGERED_PICTURE);
}
}, backgroundHandler);
} catch (CameraAccessException e) {
return;
}
try {
previewBuilder.set(CONTROL_AF_TRIGGER, CONTROL_AF_TRIGGER_IDLE);
previewBuilder.set(CONTROL_AE_PRECAPTURE_TRIGGER, CONTROL_AE_PRECAPTURE_TRIGGER_IDLE);
cameraSession.setRepeatingRequest(previewBuilder.build(), mSessionCaptureCallback, backgroundHandler);
} catch (CameraAccessException e) {
}
}
Methods for waiting focus and exposure:
private void waitPrecapture(STotalCaptureResult result, boolean picture) {
// Check if AF/AE triggered and/or finished
if ((!isAFTriggered(result) || isAfFinished(result)) && (!isAETriggered(result) || isAEFinished(result))) {
takePicture();
}
}
private boolean isAFTriggered(STotalCaptureResult result) {
Integer afMode = result.get(SCaptureResult.CONTROL_AF_MODE);
return afMode != CONTROL_AF_MODE_OFF &&
afMode != CONTROL_AF_MODE_EDOF;
}
private boolean isAfFinished(STotalCaptureResult result) {
int afState = result.get(SCaptureResult.CONTROL_AF_STATE);
return afState == CONTROL_AF_STATE_FOCUSED_LOCKED || afState == CONTROL_AF_STATE_NOT_FOCUSED_LOCKED
|| afState == CONTROL_AF_STATE_PASSIVE_FOCUSED || afState == CONTROL_AF_STATE_PASSIVE_UNFOCUSED;
}
private boolean isAETriggered(STotalCaptureResult result) {
boolean aeMode = result.get(SCaptureResult.CONTROL_AE_MODE) != SCaptureResult.CONTROL_AE_MODE_OFF;
return aeMode && cameraCharacteristics.get(SCameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
!= SCameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
}
private boolean isAEFinished(STotalCaptureResult result) {
Integer aeState = result.get(SCaptureResult.CONTROL_AE_STATE);
return aeState == null || aeState == SCaptureResult.CONTROL_AE_STATE_CONVERGED || aeState == SCaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED
|| aeState == SCaptureResult.CONTROL_AE_STATE_LOCKED;
}
Method for picture capture step 2:
public void takePicture(){
imageReader.setOnImageAvailableListener(reader -> {
Image image = reader.acquireLatestImage();
ByteBuffer buffer = image.getPlanes()[0].getBuffer();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
image.close();
singleSubscriber.onSuccess(bytes);
}, imageSavingHandler);
try {
cameraSession.capture(captureBuilder.build(), new SCameraCaptureSession.CaptureCallback() {
#Override
public void onCaptureCompleted(SCameraCaptureSession session, SCaptureRequest request, STotalCaptureResult result) {
if (getState() == CameraState.CLOSING) {
return;
}
cancelAF();
}
#Override
public void onCaptureStarted(SCameraCaptureSession session, SCaptureRequest request, long timestamp, long frameNumber) {
super.onCaptureStarted(session, request, timestamp, frameNumber);
shutterCallback.call();
}
#Override public void onCaptureFailed(SCameraCaptureSession session, SCaptureRequest request, SCaptureFailure failure) {
singleSubscriber.onError(new RuntimeException("Error taking picture, onCaptureFailed"));
if (getState() == CameraState.CLOSING) {
return;
}
cancelAF();
}
}, backgroundHandler);
setState(CameraState.TAKE_PICTURE);
} catch (CameraAccessException e) {
singleSubscriber.onError(new RuntimeException("Error capturing image", e));
}
}
I've found the fix.
captureBuilder automatically sets CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY and CaptureRequest.EDGE_MODE_HIGH_QUALITY but in my case the device has some problem handling these options. Setting CaptureRequest.NOISE_REDUCTION_MODE_FAST and CaptureRequest.EDGE_MODE_FAST fixes the problem.
Related
I use this code for Camera X binding. When I run app for the first time everything works fine. But when I close app and start it again I get "ViewPort is NULL". Why viewPort is NULL I do not understand. What is wrong with my code?
#OptIn(markerClass = androidx.camera.lifecycle.ExperimentalUseCaseGroupLifecycle.class)
private void bindAllCameraUseCases() {
if (cameraProvider != null) {
// As required by CameraX API, unbinds all use cases before trying to re-bind any of them.
cameraProvider.unbindAll();
createPreviewUseCase();
createImageCaptureUseCase();
createAnalysisUseCase();
if (previewUseCase != null && analysisUseCase != null && imageCaptureUseCase != null) {
ViewPort viewPort = ((PreviewView)findViewById(R.id.preview_view)).getViewPort();
if (viewPort != null) {
#OptIn(markerClass = androidx.camera.core.ExperimentalUseCaseGroup.class)
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
.addUseCase(previewUseCase)
.addUseCase(analysisUseCase)
.addUseCase(imageCaptureUseCase)
.setViewPort(viewPort)
.build();
camera = cameraProvider.bindToLifecycle(/* lifecycleOwner= */ this, cameraSelector, useCaseGroup);
LiveData<Integer> torchStateObserver = camera.getCameraInfo().getTorchState();
torchStateObserver.observe(this, new Observer<Integer>() {
#Override
public void onChanged(Integer state) {
torchState = state;
}
});
} else {
Toast.makeText(this, "VIEWPORT is NULL", Toast.LENGTH_SHORT).show();
}
}
}
}
Working on media browser type of application where I read files from USB with custom drivers. File expose by NanoHTTPServer. Not important point here but just for information.
The following code logic works pretty well but it looks broken and start struggling with fast scrolling.
Is there any way to load videos frame faster and avoid hanging and delay in frames. Is there any flaws in this code? As I am using rxjava.
#Override
public void onBindViewHolder(BasicHolder holder,final int position) {
...
if(multiMedia.isImage()){
Glide.with(context)
.load(multiMedia.getUrl())
.crossFade()
.into(holder.imageThumbnail);
}else{
Bitmap bitmap = mThumbnailsCache.get(multiMedia.getUrl()+THUMBNAIL_AT_PERCENT);
if(bitmap == null || multiMedia.getLength() == 0){
//check for already running task
if(holder.imageThumbnail.getTag(R.string.tag_for_video) == null){
loadVideoThumbnail(holder,multiMedia,position);
}
}else {
holder.imageThumbnail.setImageBitmap(bitmap);
}
}
....
//this method will trigger rxjava task on io and set back results emitted by
private void loadVideoThumbnail(final BasicHolder holder, final MultiMedia multiMedia,final int position){
final SoftReference<BasicHolder> mHolderRef = new SoftReference<>(holder);
final Subscription sub = extractFrameAndDuration(multiMedia.getUrl(), THUMBNAIL_AT_PERCENT)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(emittedObject -> {
if (emittedObject == null || mHolderRef.get() == null) return;
if (mHolderRef.get().getLayoutPosition() != position) {
notifyItemChanged(position);
return;
}
if (emittedObject instanceof Bitmap) {
ImageView imageThumbnail = mHolderRef.get().imageThumbnail;
if (imageThumbnail.getTag(R.string.tag_for_video) != null) {
imageThumbnail.setTag(R.string.tag_for_video, null);
imageThumbnail.setImageBitmap((Bitmap) emittedObject);
}
} else {
multiMedia.setLength((long)emittedObject);
TextView textInfo = mHolderRef.get().textInfo;
if (textInfo.getTag() == Filetype.MOVIE) {
mHolderRef.get().viewHighlight.setData(multiMedia.getSavedClips(),multiMedia.getLength());
textInfo.setText(VideoUtils.getTotalClipDuration((long) emittedObject, App.context));
}
}
}, Throwable::printStackTrace);
if(reqSubQueue.size() > 8){
//cancel previous requests
reqSubQueue.pop().unsubscribe();
}
reqSubQueue.add(sub);
holder.imageThumbnail.setTag(R.string.tag_for_video,sub);
}
...
//this method will get thumbnail from video url
private Observable<Object> extractFrameAndDuration(String url, final long atPercent){
return Observable.create(subscriber -> {
final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
subscriber.add(new Subscription() {
#Override
public void unsubscribe() {
//this will actually cancel loading
Observable.fromCallable(() -> {
mediaMetadataRetriever.release();
return true;
}).subscribeOn(Schedulers.io())
.subscribe();
}
#Override
public boolean isUnsubscribed() {
return false;
}
});
try{
mediaMetadataRetriever.setDataSource(url, new HashMap<>());
String durationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
final long duration = Long.parseLong(TextUtils.isEmpty(durationString)? "0": durationString);
//cache duration
clipsDurations.put(url,duration);
subscriber.onNext(duration);
long timeUs = (long) (duration / 100.0 * atPercent);
if(timeUs > duration) timeUs = duration;
Bitmap bitmap = mThumbnailsCache.get(url+THUMBNAIL_AT_PERCENT);
if(bitmap == null){
//1 milli sec = 1000 microseconds
bitmap = MediaUtils.getFrameFromVideoAt(mediaMetadataRetriever, timeUs * 1000);
if(bitmap != null){
bitmap = MediaUtils.resize(bitmap,thumbSize,thumbSize);
mThumbnailsCache.put(url+THUMBNAIL_AT_PERCENT,bitmap);
subscriber.onNext(bitmap);
}
}else {
subscriber.onNext(bitmap);
}
}
catch (Exception e){
e.printStackTrace();
}
finally{
if (mediaMetadataRetriever != null){
mediaMetadataRetriever.release();
}
}
//remove item from queue
subscriber.onCompleted();
reqSubQueue.remove(subscriber);
});
}
All requests get canceled in Activity.onStop. reqSubQueue is ArrayDeque
Edit method description is added
I am sure some pro-developers will find out flaws.
I have just started exploring google-play-services-turnbased APIs. Till now I have been successful in creating a match. But from the documentation I haven't been able to figure out how to player's score after he completes his turn.
This is my onClickStartMatch method.
public void onStartMatchClicked() {
Intent intent =
Games.TurnBasedMultiplayer.getSelectOpponentsIntent(mHelper.getApiClient(), 1, 7, true);
startActivityForResult(intent, RC_SELECT_PLAYERS);
}
This is my onActivityResult method in my main activity class.
if (request == RC_SELECT_PLAYERS) {
if (response != RESULT_OK) {
// user canceled
return;
}
// Get the invitee list.
final ArrayList<String> invitees =
data.getStringArrayListExtra(Games.EXTRA_PLAYER_IDS);
// Get auto-match criteria.
Bundle autoMatchCriteria = null;
int minAutoMatchPlayers = data.getIntExtra(
Multiplayer.EXTRA_MIN_AUTOMATCH_PLAYERS, 0);
int maxAutoMatchPlayers = data.getIntExtra(
Multiplayer.EXTRA_MAX_AUTOMATCH_PLAYERS, 0);
if (minAutoMatchPlayers > 0) {
autoMatchCriteria = RoomConfig.createAutoMatchCriteria(
minAutoMatchPlayers, maxAutoMatchPlayers, 0);
} else {
autoMatchCriteria = null;
}
TurnBasedMatchConfig tbmc = TurnBasedMatchConfig.builder()
.addInvitedPlayers(invitees)
.setAutoMatchCriteria(autoMatchCriteria)
.build();
// Create and start the match.
Games.TurnBasedMultiplayer
.createMatch(mHelper.getApiClient(), tbmc)
.setResultCallback(new MatchInitiatedCallback());
}
This is my MatchInitiatedCallback class
public class MatchInitiatedCallback implements
ResultCallback<TurnBasedMultiplayer.InitiateMatchResult>,OnTurnBasedMatchUpdateReceivedListener {
#Override
public void onResult(TurnBasedMultiplayer.InitiateMatchResult result) {
// Check if the status code is not success.
Status status = result.getStatus();
if (status.isSuccess()) {
Log.d("turnbased","Turn Based Match Initiated successfully with result: "+status.getStatusMessage());
return;
}
TurnBasedMatch match = result.getMatch();
// If this player is not the first player in this match, continue.
if (match.getData() != null) {
showTurnUI(match);
return;
}
// Otherwise, this is the first player. Initialize the game state.
initGame(match);
// Let the player take the first turn
showTurnUI(match);
}
public void showTurnUI(TurnBasedMatch match){
if(match.getStatus() == TurnBasedMatch.MATCH_STATUS_ACTIVE){
if(match.getTurnStatus() == TurnBasedMatch.MATCH_TURN_STATUS_MY_TURN){
turnBasedMatchData = match.getData();
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(), "score:400".getBytes(Charset.forName("UTF-16")),null).setResultCallback(updateMatchResult());
}
}
}
public void initGame(TurnBasedMatch match){
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(),"score:605".getBytes(Charset.forName("UTF-16")),match.getParticipantId(Games.Players.getCurrentPlayerId(mHelper.getApiClient()))).setResultCallback(updateMatchResult());
}
public ResultCallback<TurnBasedMultiplayer.UpdateMatchResult> updateMatchResult(){
return null;
}
#Override
public void onTurnBasedMatchReceived(TurnBasedMatch turnBasedMatch) {
Log.d("turn-based","Player played his turn");
}
#Override
public void onTurnBasedMatchRemoved(String s) {
}
}
}
Also it would helpful if some can properly explain how to continue a game a game from start and when to submit score and how.
Figured it out. This is how you can do it.
public byte[] persist() {
JSONObject retVal = new JSONObject();
try {
retVal.put("turnCounter", 2);
retVal.put("score1",100);
retVal.put("score2",200);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String st = retVal.toString();
Log.d(TAG, "==== PERSISTING\n" + st);
return st.getBytes(Charset.forName("UTF-8"));
}
Games.TurnBasedMultiplayer.takeTurn(mHelper.getApiClient(),match.getMatchId(),persist(),null).setResultCallback(updateMatchResult());
My app turn on camera LED using FLASH_MODE_TORCH, but now some people say that FLASH_MODE_TORCH will not work on some Samsung devices correctly.
So should I use FLASH_MODE_ON for all devices to work?(especially for Samsung devices)
may be this will help you
Parameters params = null;
if(mCamera != null) {
params = mCamera.getParameters();
if(params != null) {
List<String> supportedFlashModes = params.getSupportedFlashModes();
if(supportedFlashModes != null) {
if(supportedFlashModes.contains(Parameters.FLASH_MODE_TORCH)) {
params.setFlashMode( Parameters.FLASH_MODE_TORCH );
} else if(supportedFlashModes.contains(Parameters.FLASH_MODE_ON)) {
params.setFlashMode( Parameters.FLASH_MODE_ON );
} else mCamera = null;
} else Log.d(TAG, "Camera is null.");
if(mCamera != null) {
Log.d(TAG, "Flash disponibile (" + params.getFlashMode() + ")");
mCamera.setParameters( params );
mCamera.startPreview();
mCamera.autoFocus(null);
} else Log.d(TAG, "Camera is null.");
There is no single way to make sure the flash works on every device. You have to add a lot of code that is specific of the manufacturer and the device.
Dwhanik's answer is how I would handle the specific problem you are talking about. Check for FLASH_MODE_TORCH first and then try FLASH_MODE_ON. But this does not mean that you will get a flash on every device.
public void Initialize(){
if (this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)) {
camManager = (CameraManager) getSystemService(this.CAMERA_SERVICE);
try {
if (camManager.getCameraIdList().length > 0) {
strCameraID=camManager.getCameraIdList()[0];
}
} catch (Exception EX_CAMLIST) {
}
}
}
public void switchLED(String strCemeraID,boolean status){
if(camManager==null || strCemeraID==null)return;
try {
if (strCemeraID.length() > 0) {
camManager.setTorchMode(strCemeraID, status);
}
} catch (Exception EX_CAMERA_TORCH) {
}
}
Is there an onFinished listener of some sort? Or do we have to compare the current stream position against the duration of the track?
It's not pretty but you can make this call:
if (mRemoteMediaPlayer.getMediaStatus().getPlayerState() == MediaStatus.PLAYER_STATE_IDLE
&& mRemoteMediaPlayer.getMediaStatus().getIdleReason() == MediaStatus.IDLE_REASON_FINISHED) {
...
}
Prem,
There is currently no callback to register for such event. One alternative (and-not-so-pretty) approach is the following: on the receiver, listen for "ended" event of the media element and send an event back to the sender through a private channel. Another approach is what you suggested: check position against duration. When SDK graduates to general availability, better and cleaner approaches will be available to accomplish what you want.
Here is solution:
You just need to take one more variable mIdleReason.
1) Initialize mIdleReason as
public int mIdleReason=MediaStatus.IDLE_REASON_NONE;
2) Update value at method loadMedia
public void loadMedia(String url, MediaMetadata movieMetadata, CastSession castSession, boolean autoPlay, long position) {
if (castSession == null || !castSession.isConnected()) {
return;
}
MediaInfo mediaInfo = new MediaInfo.Builder(url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("videos/m3u8")
.setMetadata(movieMetadata)
.build();
mRemoteMediaClient = castSession.getRemoteMediaClient();
mRemoteMediaClient.addListener(mRemoteMediaClientListener);
mRemoteMediaClient.load(mediaInfo, autoPlay, position);
mIdleReason = MediaStatus.IDLE_REASON_NONE;
}
3) Update value at onStatusUpdate:
private RemoteMediaClient.Listener mRemoteMediaClientListener = new RemoteMediaClient.Listener() {
#Override
public void onStatusUpdated() {
if (mRemoteMediaClient == null || mediaPlayerListener == null) {
return;
}
MediaStatus mediaStatus = mRemoteMediaClient.getMediaStatus();
if (mediaStatus != null) {
int playerStatus = mediaStatus.getPlayerState();
Log.d("PlayerState", "onStatusUpdated() called, progress= "+mSeekBar.getProgress() +", stream duration= "+ mRemoteMediaClient.getStreamDuration()+" mSeekBar.getProgress() == mRemoteMediaClient.getStreamDuration()="+(mSeekBar.getProgress() == mRemoteMediaClient.getStreamDuration()));
Log.d("PlayerState", "onStatusUpdated() called playerStatus="+playerStatus+", idleReason="+mediaStatus.getIdleReason());
if (playerStatus == MediaStatus.PLAYER_STATE_PLAYING) {
mediaPlayerListener.playing();
mIdleReason = MediaStatus.IDLE_REASON_FINISHED;
} else if (playerStatus == MediaStatus.PLAYER_STATE_BUFFERING) {
mediaPlayerListener.buffering();
mIdleReason = MediaStatus.IDLE_REASON_FINISHED;
} else if (playerStatus == MediaStatus.PLAYER_STATE_PAUSED) {
mediaPlayerListener.paused();
} else if (playerStatus == MediaStatus.IDLE_REASON_INTERRUPTED) {
mediaPlayerListener.error();
} else if (playerStatus == MediaStatus.IDLE_REASON_ERROR) {
mediaPlayerListener.error();
}else if(playerStatus == MediaStatus.PLAYER_STATE_IDLE && mediaStatus.getIdleReason() == MediaStatus.IDLE_REASON_FINISHED&& mIdleReason == MediaStatus.IDLE_REASON_FINISHED){
mediaPlayerListener.played();
}
}
}
#Override
public void onMetadataUpdated() {
Log.d("", "onMetadataUpdated: ");
}
#Override
public void onQueueStatusUpdated() {
Log.d("", "onQueueStatusUpdated: ");
}
#Override
public void onPreloadStatusUpdated() {
Log.d("", "onPreloadStatusUpdated: ");
}
#Override
public void onSendingRemoteMediaRequest() {
Log.d("", "onSendingRemoteMediaRequest: ");
}
#Override
public void onAdBreakStatusUpdated() {
Log.d("", "onAdBreakStatusUpdated: ");
}
};