1 はじめに
この期間中、natario1/CameraView を使用してフィルタリングされた 预览
、 を実装しています。 a> について多くの言及がありますが、作成者はこの問題を修正していません。 では、この の を重ね合わせるのが一般的です。複数のフィルターを使用して写真を撮影すると、プレビューは正常になりますが、撮影された写真は真っ黒になります。 フィルターを重ねて写真を撮る場合は、 を使用して 特に の使用は深海域に入り、徐々にニーズを満たせないようになってきました。 しかし、プロジェクトの深化が進むにつれて、 パッケージ化が適切に行われているため、プロジェクトの初期段階で実際に多くの時間を節約できました。 関数。 拍照
、录像
CameraView
CameraView
MultiFilter
2
2
Github
issues
BUG
前の記事、BUG
、
、。 この記事では、この問題を正式に解決します について説明しています。 では、プレビューと写真の効果が一致しない理由を再録しました。別の記事CameraView
BUG
2. オプション 1
プレビューと写真撮影の効果には一貫性がないためCameraView
、最初に思いつくのは、写真撮影に関連するロジックを自分で再実装して、 eglSurface
あらゆる種類の問題、質問。
2.1 ソースコードの説明
まず、フィルター写真の処理は SnapshotGlPictureRecorder.take()
メソッドにあり、 Snapshot2PictureRecorder
は < a i=3>。 から継承されます。 Camera2
SnapshotGlPictureRecorder
public class Snapshot2PictureRecorder extends SnapshotGlPictureRecorder {
//...省略了代码...
}
Snapshot2PictureRecorder
はCamera2Engine
のonTakePictureSnapshot()
メソッド で初期化されます。
@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 を置き換える
新しいクラスを作成します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
フレーム バッファから取り出して保存します。 にあります。 形式なので、 は として送信されます。 OpenGL
buffer
RGBA
capacity
width * 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> のコールバック リスト。 CameraView
mListeners
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()
で、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 ソースコード解析
MultiFilter
のonCreate()
メソッドは空ですが、次のようなコメント行があります。
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 ブログ