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

使用Android Java CameraX API无法录制带水印的视频

解决CameraX预览水印无法录制到视频的问题

直接在PreviewView上添加的水印属于UI层绘制,和CameraX的视频捕获管线是分离的。VideoCapture组件直接从相机传感器获取帧数据,不会包含UI层的叠加内容,所以录制的视频里看不到水印。


方案一:使用VideoFrameProcessor处理视频帧(推荐)

CameraX 1.1.0及以上版本支持通过VideoFrameProcessor在视频编码前修改每一帧,直接在帧数据上添加水印。

步骤:

  1. 确保CameraX依赖版本不低于1.1.0:
dependencies {
    implementation "androidx.camera:camera-core:1.1.0"
    implementation "androidx.camera:camera-video:1.1.0"
    implementation "androidx.camera:camera-lifecycle:1.1.0"
}
  1. 实现水印处理的VideoFrameProcessor:
private VideoFrameProcessor createWatermarkProcessor() {
    return new VideoFrameProcessor() {
        private final Paint watermarkPaint = new Paint();

        {
            // 配置水印样式
            watermarkPaint.setColor(Color.WHITE);
            watermarkPaint.setTextSize(60);
            watermarkPaint.setAntiAlias(true);
            watermarkPaint.setShadowLayer(2, 0, 0, Color.BLACK); // 添加阴影提升可读性
        }

        @Override
        public void process(@NonNull VideoFrame videoFrame, @NonNull Surface outputSurface) {
            Image image = videoFrame.getImage();
            if (image == null) {
                videoFrame.close();
                return;
            }

            // 将相机帧转换为Bitmap
            Bitmap frameBitmap = convertImageToBitmap(image);
            image.close();

            // 在Bitmap上绘制水印
            Canvas canvas = new Canvas(frameBitmap);
            // 水印位置可根据需求调整
            canvas.drawText("自定义水印", 50, 100, watermarkPaint);

            // 将处理后的Bitmap渲染到输出Surface(供编码使用)
            renderBitmapToSurface(frameBitmap, outputSurface, videoFrame.getRotationDegrees());

            // 释放资源
            frameBitmap.recycle();
            videoFrame.close();
        }

        // 将Image转换为Bitmap
        private Bitmap convertImageToBitmap(Image image) {
            Image.Plane[] planes = image.getPlanes();
            ByteBuffer buffer = planes[0].getBuffer();
            int pixelStride = planes[0].getPixelStride();
            int rowStride = planes[0].getRowStride();
            int rowPadding = rowStride - pixelStride * image.getWidth();

            Bitmap bitmap = Bitmap.createBitmap(
                    image.getWidth() + rowPadding / pixelStride,
                    image.getHeight(),
                    Bitmap.Config.ARGB_8888
            );
            bitmap.copyPixelsFromBuffer(buffer);
            // 裁剪掉多余的padding
            return Bitmap.createBitmap(bitmap, 0, 0, image.getWidth(), image.getHeight());
        }

        // 将Bitmap渲染到Surface
        private void renderBitmapToSurface(Bitmap bitmap, Surface surface, int rotation) {
            Canvas canvas = surface.lockCanvas(null);
            if (canvas == null) {
                return;
            }

            // 根据相机旋转角度调整画布
            canvas.save();
            canvas.rotate(rotation, canvas.getWidth() / 2f, canvas.getHeight() / 2f);
            // 绘制Bitmap到画布
            canvas.drawBitmap(bitmap, 0, 0, null);
            canvas.restore();

            surface.unlockCanvasAndPost(canvas);
        }
    };
}
  1. 在初始化VideoCapture时绑定处理器:
//Video Capture
videoCapture = new VideoCapture.Builder()
        .setVideoFrameRate(30)
        .setBitRate(1000000)
        .setTargetResolution(new Size(640, 480))
        .build();

// 添加水印处理器
videoCapture.setVideoFrameProcessor(createWatermarkProcessor());

方案二:使用TextureView录制带水印的预览画面

如果你的水印已经绘制在预览界面上,可以改用TextureView承载预览,直接录制TextureView的内容。

步骤:

  1. 布局中替换PreviewView为自定义TextureView:
<com.yourpackage.WatermarkTextureView
    android:id="@+id/texture_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
  1. 实现带水印的TextureView:
public class WatermarkTextureView extends TextureView {
    private final Paint watermarkPaint;

    public WatermarkTextureView(Context context) {
        super(context);
        watermarkPaint = initPaint();
    }

    public WatermarkTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        watermarkPaint = initPaint();
    }

    private Paint initPaint() {
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        paint.setTextSize(60);
        paint.setAntiAlias(true);
        return paint;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制水印
        canvas.drawText("自定义水印", 50, 100, watermarkPaint);
    }
}
  1. 使用MediaRecorder录制TextureView的画面:
// 初始化Preview绑定到TextureView
preview = new Preview.Builder().build();
preview.setSurfaceProvider(textureView.getSurfaceProvider());

// 配置MediaRecorder
MediaRecorder mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setOutputFile(getVideoOutputPath()); // 自定义输出路径
mediaRecorder.setVideoSize(640, 480);
mediaRecorder.setVideoFrameRate(30);
mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);

try {
    mediaRecorder.prepare();
} catch (IOException e) {
    e.printStackTrace();
}

// 将MediaRecorder的Surface绑定到CameraX
Surface recorderSurface = mediaRecorder.getSurface();
UseCaseGroup useCaseGroup = new UseCaseGroup.Builder()
        .addUseCase(preview)
        .addUseCase(new VideoCapture.Builder()
                .setTargetResolution(new Size(640, 480))
                .build()
                .setSurfaceProvider(SurfaceProvider.create(recorderSurface)))
        .build();

cameraProvider.bindToLifecycle(this, cameraSelector, useCaseGroup);

// 开始录制
mediaRecorder.start();

注意事项:

  • 方案一的帧处理可能会带来一定性能开销,若需优化可考虑使用OpenGL进行硬件加速渲染。
  • 确保相机权限和存储权限已正确申请。

内容的提问来源于stack exchange,提问作者Kunal Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 03:44:53