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.2 SnapshotGlPictureRecorder を置き換える

新しいクラスを作成しますMySnapshotGlPictureRecorderSnapshotGlPictureRecorder

@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中取出widthheight

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 を呼び出し、最後に に配布します。 >< a 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 に置き換えられました

Camera2EngineonTakePictureSnapshot() で、Snapshot2PictureRecorder の初期化を に置き換えます。MySnapshot2PictureRecorder

@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問題が解決されたことがわかります。
満足していた矢先、別の問題があることに気付きました。フィラーを再割り当てした後は毎回、プレビューが黒く点滅し、その後通常に戻ります< /span> >

binding.cameraView.filter = multiFilter

これを聞いてとても落ち込んでいます。混乱しすぎていませんか? しばらくアイデアがありませんでした。プロセスを何度も整理しても、まだ MultiFilter から始めることしかできません。 。
幸いなことに、2 番目の解決策を見つけることができたのは、ひらめきなのか偶然なのかはわかりません。

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()移动到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. プログラムを実行する

プログラムを再実行すると、2 つのバグ CameraView叠加2个以上滤镜拍照黑屏的BUG每次重新赋值fitler之后,预览都会黑屏闪一下,才回复正常 が解決されていることがわかります。

4. その他

4.1 CameraView ソースコード解析シリーズ

Android カメラ ライブラリ CameraView ソース コード分析 (1): プレビュー - CSDN ブログ
Android カメラ ライブラリ CameraView ソース コード分析 (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 は、2 つ以上のフィルターを重ねた CameraView で写真を撮ると黒い画面になるバグを解決します (1): 再発するバグ-CSDN ブログ
Android は黒い画面のバグを解決しますCameraView に 2 つ以上のフィルターを重ねて写真を撮影する場合 (2): BUG の解決 - CSDN ブログ
カメラ ライブラリ CameraView のプレビューと写真の効果に一貫性がないのはなぜですか? - CSDN ブログ

おすすめ

転載: blog.csdn.net/EthanCo/article/details/134310244