Android устраняет ОШИБКУ черного экрана при съемке фотографий с помощью CameraView, наложенного более чем на 2 фильтра (2): Устраните ОШИБКУ

1. Введение

В этот период я ​​используюnatario1/CameraView для реализации фильтрованных 预览, a>, но автор не устранил эту проблему. много упоминаний об этом в является нормальным явлением При съемке фотографий с более чем одним фильтром предварительный просмотр будет обычным, но сделанные фотографии будут полностью черными. фильтров, наложение , наложения Специально для фотосъемки с использованием проникло в глубоководную зону, и постепенно становится ясно, что оно не может удовлетворить наши потребности. Однако по мере того, как проект продолжает углубляться, использование упаковка вполне на месте, она действительно сэкономила нам много времени на ранних стадиях проекта. Поскольку функция. 拍照, 录像
CameraView
CameraView
MultiFilter22
GithubissuesBUG

Вставьте сюда описание изображения

Предыдущая статья, мы воспроизвели это BUG,
и . В этой статье мы формально решим эту задачу предварительный просмотр и фотоэффекты несовместимы. объясняется, почему В другой статьеCameraView
BUG

2. Вариант 1

ПосколькуCameraView эффекты предварительного просмотра и фотосъемки несовместимы, первое, что приходит на ум, — это самостоятельно реализовать логику, связанную с фотосъемкой, чтобы избежать eglSurface всевозможные проблемы.вопрос.

2.1 Описание исходного кода

Прежде всего, мы знаем, что обработка фотографии фильтра находится в методе SnapshotGlPictureRecorder.take(), а Snapshot2PictureRecorder является реализацией < a i=3>. Наследуется от . Camera2SnapshotGlPictureRecorder

public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
    
    
	//...省略了代码...
}

Snapshot2PictureRecorder инициализируется в методеCamera2EngineonTakePictureSnapshot()

@EngineThread
@Override
protected void onTakePictureSnapshot(@NonNull final PictureResult.Stub stub,
                                     @NonNull final AspectRatio outputRatio,
                                     boolean doMetering) {
    
    
	stub.size = getUncroppedSnapshotSize(Reference.OUTPUT);
	stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
	mPictureRecorder = new Snapshot2PictureRecorder(stub, this,
	        (RendererCameraPreview) mPreview, outputRatio); 
	mPictureRecorder.take();
}

Итак, если мы хотим заменить логику фотосъемки фильтрами, мы можем просто заменить эти два класса.

2.2 Замена SnapshotGlPictureRecorder

Создаем новый классMySnapshotGlPictureRecorder для заменыSnapshotGlPictureRecorder

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class MySnapshotGlPictureRecorder extends SnapshotPictureRecorder {
    
    
    private RendererCameraPreview mPreview;
    private AspectRatio mOutputRatio;

    private Overlay mOverlay;
    private boolean mHasOverlay;

    public MySnapshotGlPictureRecorder(
            @NonNull PictureResult.Stub stub,
            @Nullable PictureResultListener listener,
            @NonNull RendererCameraPreview preview,
            @NonNull AspectRatio outputRatio,
            @Nullable Overlay overlay) {
    
    
        super(stub, listener);
        mPreview = preview;
        mOutputRatio = outputRatio;
        mOverlay = overlay;
        mHasOverlay = mOverlay != null && mOverlay.drawsOn(Overlay.Target.PICTURE_SNAPSHOT);
    }

    @Override
    public void take() {
    
    
        mPreview.addRendererFrameCallback(new RendererFrameCallback() {
    
    

            @RendererThread
            public void onRendererTextureCreated(int textureId) {
    
    
                MySnapshotGlPictureRecorder.this.onRendererTextureCreated(textureId);
            }

            @RendererThread
            @Override
            public void onRendererFilterChanged(@NonNull Filter filter) {
    
    
                MySnapshotGlPictureRecorder.this.onRendererFilterChanged(filter);
            }

            @RendererThread
            @Override
            public void onRendererFrame(@NonNull SurfaceTexture surfaceTexture,
                                        int rotation, float scaleX, float scaleY) {
    
    
                mPreview.removeRendererFrameCallback(this);
                MySnapshotGlPictureRecorder.this.onRendererFrame(surfaceTexture,
                        rotation, scaleX, scaleY);
            }

        });
    }

    private void onRendererTextureCreated(int textureId) {
    
    
        Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio);
        mResult.size = new Size(crop.width(), crop.height());
        /*if (CameraViewConstants.custom) { 
        	// 如果自定义标志位生效,那么使用1920*1080,此处
            mResult.size = new Size(1920, 1080);
        }*/
    }

    private void onRendererFilterChanged(Filter filter) {
    
    

    }

    private void onRendererFrame(SurfaceTexture surfaceTexture, int rotation, float scaleX, float scaleY) {
    
    
        //待实现...
    }
}

ЗдесьonRendererTextureCreated определяется размер изображения. Затем в onRendererFrame настало время выполнить обработку фильтром при фотосъемке.

2.2.1 Получить размер изображения

Продвинутые талантыmResult.sizeСозданиеwidthяпонского языкаheight

int width = mResult.size.getWidth();
int height = mResult.size.getHeight();
2.2.2 Чтение данных изображения

Затем вызовитеGLES20.glReadPixels(), чтобы извлечь данные пикселей, которые были обработаны, из кадрового буфера GPU и сохранить это в . Поскольку формат , передается как . OpenGLbuffer
RGBAcapacitywidth * height * 4

ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 4);
buffer.order(ByteOrder.nativeOrder());
GLES20.glReadPixels(
        0,
        0,
        width,
        height,
        GLES20.GL_RGBA,
        GLES20.GL_UNSIGNED_BYTE,
        buffer
);
2.2.3 Создание битампа

ОбщиеbufferТрансформацияBitmap

buffer.rewind();
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
2.2.4 Использование матриц для обработки вращения

ИспользуяMatrix матрицу можно зеркально отображать, вращать и т. д. Bitmap. Здесь вы можете выполнить соответствующую обработку в соответствии с фактическим направлением аппаратного обеспечения камеры.

Matrix matrix = new Matrix();
matrix.preRotate(180,width/2F,height/2F);
Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false);
2.2.5 Преобразование растрового изображения в массив байтов

ОбщиеBitmapОтслеживаниеByteКомбинация чисел

ByteArrayOutputStream stream = new ByteArrayOutputStream();
newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
byte[] byteArray = stream.toByteArray();
try {
    
    
    stream.close();
} catch (IOException e) {
    
    
    throw new RuntimeException(e);
}
bitmap.recycle();
newBitmap.recycle();
2.2.6 Данные о распределении

назначает массив Byte data, вызывает dispatchResult для распространения и, наконец, передает его < список обратных вызовов в i=4>. CameraViewmListeners

mResult.data = byteArray;
dispatchResult();

Мы установили обратный вызов addCameraListener, чтобы мы могли получить соответствующие данные после вызова takePictureSnapshot() для фотографирования.

binding.cameraView.addCameraListener(object : CameraListener() {
    
    
    override fun onPictureTaken(result: PictureResult) {
    
    
        super.onPictureTaken(result)
        //拍照回调
        val bitmap = BitmapFactory.decodeByteArray(result.data, 0, result.data.size)
        bitmap?.also {
    
    
            Toast.makeText(this@Test2Activity, "拍照成功", Toast.LENGTH_SHORT).show()
            //将Bitmap设置到ImageView上
            binding.img.setImageBitmap(it)
            
            val file = getNewImageFile()
            //保存图片到指定目录
            ImageUtils.save(it, file, Bitmap.CompressFormat.JPEG)
        }
    }
})

2.3 Создайте новый MySnapshot2PictureRecorder

НовыйMySnapshot2PictureRecorderУнаследовано отMySnapshotGlPictureRecorder

public class MySnapshot2PictureRecorder extends MySnapshotGlPictureRecorder {
    
    

    public MySnapshot2PictureRecorder(@NonNull PictureResult.Stub stub,
                                      @NonNull Camera2Engine engine,
                                      @NonNull RendererCameraPreview preview,
                                      @NonNull AspectRatio outputRatio) {
    
    
        super(stub, engine, preview, outputRatio, engine.getOverlay());
    }

    @Override
    public void take() {
    
    
        super.take();
    }

    @Override
    protected void dispatchResult() {
    
    
        super.dispatchResult();
    }
}

2.4 Заменено на MySnapshot2PictureRecorder.

В Camera2Engine замените инициализацию на onTakePictureSnapshot()Snapshot2PictureRecorderMySnapshot2PictureRecorder

@EngineThread
@Override
protected void onTakePictureSnapshot(@NonNull final PictureResult.Stub stub,
                                     @NonNull final AspectRatio outputRatio,
                                     boolean doMetering) {
    
    
    stub.size = getUncroppedSnapshotSize(Reference.OUTPUT);
    stub.rotation = getAngles().offset(Reference.VIEW, Reference.OUTPUT, Axis.ABSOLUTE);
    //Snapshot2PictureRecorder 替换为了MySnapshot2PictureRecorder
    mPictureRecorder = new MySnapshot2PictureRecorder(stub, this,
             (RendererCameraPreview) mPreview, outputRatio); 
     mPictureRecorder.take();
}

2.5 Запустите программу

Перезапустите программу и вызовите фильтр для съемки, вы обнаружите, чтоCameraView叠加2个以上滤镜拍照黑屏的BUG проблема решена.
Когда я был счастлив, я обнаружил, что есть еще одна проблема: Каждый раз после переназначения фитлера предварительный просмотр мигал черным, а затем возвращался в нормальное состояние< /а> >

binding.cameraView.filter = multiFilter

Это меня очень расстраивает. Это слишком сбивает с толку? Некоторое время у меня не было никаких идей. После того, как я много раз разобрался с процессом, я все еще могу начать только с MultiFilter .
К счастью, я не знаю, была ли вспышка вдохновения или совпадение позволило мне найти второе решение

3. Вариант 2

Давайте откатим код до состояния перед изменением плана 1, а затем проанализируем исходный код.

3.1 Анализ исходного кода

MultiFilteronCreate()Метод в Мы создадим дочерние элементы в операции draw(), потому что некоторые могли быть добавлены после вызова onCreate(). пустой, но есть такая строка комментариев:

@Override
public void onCreate(int programHandle) {
    
    
    // We'll create children during the draw() op, since some of them
    // might have been added after this onCreate() is called.
}

Давайте еще раз посмотрим на метод draw(). Здесь мы просматриваем список фильтров filters и инициализируем каждый filters. Затем рисуем снова, и следующий фильтр рисуется на основе предыдущего фильтра, тем самым достигается эффект суперпозиции фильтров.

Существует проблема, а это означает, что каждый разdraw он будет повторно инициализироватьсяfitler, тогда мы можем сделать вывод: < Инициализация /span>. filter изначально должен быть помещен в onCreate()

@Override
public void draw(long timestampUs, @NonNull float[] transformMatrix) {
    
    
    synchronized (lock) {
    
    
        for (int i = 0; i < filters.size(); i++) {
    
    
            boolean isFirst = i == 0;
            boolean isLast = i == filters.size() - 1;
            Filter filter = filters.get(i);
            State state = states.get(filter);

            maybeSetSize(filter);
            maybeCreateProgram(filter, isFirst, isLast);
            maybeCreateFramebuffer(filter, isFirst, isLast);

            GLES20.glUseProgram(state.programHandle);

            if (!isLast) {
    
    
                state.outputFramebuffer.bind();
                GLES20.glClearColor(0, 0, 0, 0);
            } else {
    
    
                GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
            }

            if (isFirst) {
    
    
                filter.draw(timestampUs, transformMatrix);
            } else {
    
    
                filter.draw(timestampUs, Egloo.IDENTITY_MATRIX);
            }

            if (!isLast) {
    
    
                state.outputTexture.bind();
            } else {
    
    
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            }

            GLES20.glUseProgram(0);
        }
    }
}

3.2 Реализация решения

общий ondrawпромежуточныйmaybeSetSize(), maybeCreateProgram(), maybeCreateFramebuffer()переход достигнут a>onCreate()средний

public void onCreate(int programHandle) {
    
    
    synchronized (lock) {
    
    
        for (int i = 0; i < filters.size(); i++) {
    
    
            boolean isFirst = i == 0;
            boolean isLast = i == filters.size() - 1;
            Filter filter = filters.get(i);
            State state = states.get(filter);

            maybeSetSize(filter);
            maybeCreateProgram(filter, isFirst, isLast);
            maybeCreateFramebuffer(filter, isFirst, isLast);
        }
    }
}

Другие коды все еще используютсяCameraViewИсходный код, то есть все коды, измененные в плане 1, откатываются, и они все еще используютсяSnapshotGlPictureRecorderиSnapshot2PictureRecorder

3.3. Запустите программу

Перезапустите программу, и вы обнаружите, что две ошибки CameraView叠加2个以上滤镜拍照黑屏的BUG и 每次重新赋值fitler之后,预览都会黑屏闪一下,才回复正常 устранены!

4. Другие

4.1 Серия анализа исходного кода CameraView

Анализ исходного кода CameraView библиотеки камер Android (1): Предварительный просмотр — блог CSDN
Анализ исходного кода CameraView библиотеки камер Android (2): Съемка фотографий — блог CSDN< a i=2 >Анализ исходного кода библиотеки камер Android CameraView (3): описание класса, связанного с фильтром — блог CSDNАнализ исходного кода библиотеки камер Android CameraView (4): съемка фотографий с помощью фильтров — блог CSDN< /span>< a i=4>Библиотека камер Android. Анализ исходного кода CameraView (5): сохранение эффектов фильтра — блог CSDN


4.2 Устранение ошибки CameraViewBUG

Android устраняет ОШИБКУ черного экрана при съемке с помощью CameraView, наложенного более чем на 2 фильтра (1): повторяющаяся ошибка-блог CSDN
Android устраняет ОШИБКУ черного экрана при съемке с помощью CameraView с наложением более двух фильтров (2): Устранение ошибки — блог CSDN
Почему предварительный просмотр и фотоэффекты библиотеки камеры CameraView несовместимы? — блог CSDN

рекомендация

отblog.csdn.net/EthanCo/article/details/134310244