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 Замена 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
для распространения и, наконец, передает его < список обратных вызовов в 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
проблема решена.
Когда я был счастлив, я обнаружил, что есть еще одна проблема: Каждый раз после переназначения фитлера предварительный просмотр мигал черным, а затем возвращался в нормальное состояние< /а> >
binding.cameraView.filter = multiFilter
Это меня очень расстраивает. Это слишком сбивает с толку? Некоторое время у меня не было никаких идей. После того, как я много раз разобрался с процессом, я все еще могу начать только с MultiFilter
.
К счастью, я не знаю, была ли вспышка вдохновения или совпадение позволило мне найти второе решение
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()
переход достигнут 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