Android MediaCodec getOutputImage返回只读图片,如何修改帧像素?
解决MediaCodec只读帧修改像素的方案
因为getOutputImage()返回的是只读Image对象,无法直接修改像素,这里提供三种可行的处理思路:
1. 用OpenGL ES做实时GPU处理(推荐高性能场景)
这是视频帧处理的标准方案,性能开销低,适合高帧率/分辨率场景:
- 不要直接把MediaCodec输出到目标Surface,先输出到一个离屏Surface(比如基于TextureView的SurfaceTexture,或者OpenGL的Framebuffer)
- 用OpenGL把这个Surface的数据绑定成纹理,通过自定义Shader编写像素修改逻辑(比如调整颜色、替换特定像素值)
- 处理完成后,再把纹理渲染到最终的播放Surface上
这种方式全程在GPU上操作,避免了CPU和GPU之间的数据拷贝,性能最优。
2. 复制帧数据到可写缓冲区处理(适合低性能要求场景)
如果你的处理逻辑不适合用GPU实现,可以把只读Image的数据复制到自己的可写缓冲区,修改后再渲染:
- 从Image的Planes中读取原始像素数据,复制到byte数组或ByteBuffer
- 在可写缓冲区里执行自定义像素修改
- 修改完成后,通过Canvas或OpenGL把处理后的数据绘制到目标Surface
代码示例(以YUV420格式为例):
Image image = codec.getOutputImage(outputBufferIndex); if (image != null) { int width = image.getWidth(); int height = image.getHeight(); Image.Plane[] planes = image.getPlanes(); // 计算YUV420格式的总数据大小 int ySize = width * height; int uvSize = ySize / 2; byte[] pixelData = new byte[ySize + uvSize]; // 复制Y平面数据 ByteBuffer yBuffer = planes[0].getBuffer(); yBuffer.get(pixelData, 0, ySize); // 复制UV平面数据 ByteBuffer uvBuffer = planes[1].getBuffer(); uvBuffer.get(pixelData, ySize, uvSize); // 执行自定义像素修改逻辑 applyCustomProcessingToByteArray(pixelData, width, height); // 将修改后的数据渲染到Surface(这里用Canvas示例,需确保Surface可用) Surface targetSurface = ((SurfaceRenderer)renderer).getSurface(); Canvas canvas = targetSurface.lockCanvas(null); if (canvas != null) { // 把YUV数据转成Bitmap再绘制(注意格式转换) YuvImage yuvImage = new YuvImage(pixelData, ImageFormat.NV21, width, height, null); ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, outputStream); Bitmap bitmap = BitmapFactory.decodeByteArray(outputStream.toByteArray(), 0, outputStream.size()); canvas.drawBitmap(bitmap, 0, 0, null); targetSurface.unlockCanvasAndPost(canvas); bitmap.recycle(); outputStream.close(); } image.close(); // 注意:这里不再调用原来的surfaceRenderer.render(),因为已经自己处理了渲染 }
⚠️ 注意:这种方式涉及多次数据拷贝和格式转换,性能开销大,不适合高帧率视频。
3. 用ImageReader获取可写帧(API 23+)
如果你的设备系统版本在Android 6.0及以上,可以用ImageReader来获取可写的帧:
- 创建ImageReader,指定和视频匹配的尺寸、格式
- 把MediaCodec的输出Surface设置为ImageReader的Surface
- 从ImageReader获取的Image对象是可写的,直接修改后再渲染到目标Surface
代码示例:
// 初始化阶段创建ImageReader ImageReader imageReader = ImageReader.newInstance(videoWidth, videoHeight, ImageFormat.YUV_420_888, 2); // 把MediaCodec的输出Surface设置为imageReader.getSurface() codec.configure(format, imageReader.getSurface(), null, 0); // 设置ImageReader的回调处理帧 imageReader.setOnImageAvailableListener(reader -> { Image image = reader.acquireLatestImage(); if (image != null) { // 直接修改可写Image的像素数据 applyCustomProcessingToWritableImage(image); // 把修改后的帧渲染到目标Surface renderImageToTargetSurface(image, ((SurfaceRenderer)renderer).getSurface()); image.close(); } }, new Handler(Looper.getMainLooper()));
内容的提问来源于stack exchange,提问作者Mary H
相关产品推荐
相关产品推荐

