You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 18:15:18