NoMetadataImageReader.java

/*
 * Copyright 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.camera.core.imagecapture;

import static androidx.core.util.Preconditions.checkState;

import android.os.Build;
import android.util.Pair;
import android.util.Size;
import android.view.Surface;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.camera.core.ImageProxy;
import androidx.camera.core.MetadataImageReader;
import androidx.camera.core.SettableImageProxy;
import androidx.camera.core.impl.CameraCaptureResult;
import androidx.camera.core.impl.ImageReaderProxy;
import androidx.camera.core.impl.TagBundle;
import androidx.camera.core.internal.CameraCaptureResultImageInfo;
import androidx.camera.core.streamsharing.StreamSharing;
import androidx.camera.core.streamsharing.VirtualCameraCaptureResult;

import java.util.concurrent.Executor;

/**
 * A {@link ImageReaderProxy} wrapper that does not rely on {@link CameraCaptureResult} to
 * deliver {@link ImageProxy}.
 *
 * <p>As opposed to {@link MetadataImageReader} which merges incoming {@link ImageProxy} with
 * {@link CameraCaptureResult}, this class delivers incoming {@link ImageProxy} with a
 * virtual {@link CameraCaptureResult}. This is useful when the {@link CameraCaptureResult}
 * cannot be merged. For example, for Extensions and {@link StreamSharing}, the incoming
 * {@link CameraCaptureResult} does not have matching timestamps with {@link ImageProxy}.
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class NoMetadataImageReader implements ImageReaderProxy {
    @NonNull
    private final ImageReaderProxy mWrappedImageReader;
    @Nullable
    private ProcessingRequest mPendingRequest;

    /**
     * Creates a new instance of {@link NoMetadataImageReader} by wrapping an existing
     * {@link ImageReaderProxy}.
     */
    NoMetadataImageReader(@NonNull ImageReaderProxy imageReaderProxy) {
        mWrappedImageReader = imageReaderProxy;
    }

    /**
     * Sets the {@link ProcessingRequest} that is pending to be processed.
     */
    void acceptProcessingRequest(@NonNull ProcessingRequest request) {
        checkState(mPendingRequest == null, "Pending request should be null");
        mPendingRequest = request;
    }

    @Nullable
    @Override
    public ImageProxy acquireLatestImage() {
        return createImageProxyWithEmptyMetadata(mWrappedImageReader.acquireLatestImage());
    }

    @Nullable
    @Override
    public ImageProxy acquireNextImage() {
        return createImageProxyWithEmptyMetadata(mWrappedImageReader.acquireNextImage());
    }

    @Override
    public void close() {
        mWrappedImageReader.close();
    }

    @Override
    public int getHeight() {
        return mWrappedImageReader.getHeight();
    }

    @Override
    public int getWidth() {
        return mWrappedImageReader.getWidth();
    }

    @Override
    public int getImageFormat() {
        return mWrappedImageReader.getImageFormat();
    }

    @Override
    public int getMaxImages() {
        return mWrappedImageReader.getMaxImages();
    }

    @Nullable
    @Override
    public Surface getSurface() {
        return mWrappedImageReader.getSurface();
    }

    @Override
    public void setOnImageAvailableListener(@NonNull OnImageAvailableListener listener,
            @NonNull Executor executor) {
        mWrappedImageReader.setOnImageAvailableListener(
                imageReader -> listener.onImageAvailable(NoMetadataImageReader.this), executor);
    }

    @Override
    public void clearOnImageAvailableListener() {
        mWrappedImageReader.clearOnImageAvailableListener();
    }

    @Nullable
    private ImageProxy createImageProxyWithEmptyMetadata(@Nullable ImageProxy originalImage) {
        if (originalImage == null) {
            return null;
        }
        checkState(mPendingRequest != null, "Pending request should not be null");
        TagBundle tagBundle = TagBundle.create(new Pair<>(mPendingRequest.getTagBundleKey(),
                mPendingRequest.getStageIds().get(0)));
        mPendingRequest = null;
        return new SettableImageProxy(originalImage,
                new Size(originalImage.getWidth(), originalImage.getHeight()),
                new CameraCaptureResultImageInfo(new VirtualCameraCaptureResult(
                        tagBundle, originalImage.getImageInfo().getTimestamp())));
    }
}