使用Android Java CameraX API无法录制带水印的视频
解决CameraX预览水印无法录制到视频的问题
直接在PreviewView上添加的水印属于UI层绘制,和CameraX的视频捕获管线是分离的。VideoCapture组件直接从相机传感器获取帧数据,不会包含UI层的叠加内容,所以录制的视频里看不到水印。
方案一:使用VideoFrameProcessor处理视频帧(推荐)
CameraX 1.1.0及以上版本支持通过VideoFrameProcessor在视频编码前修改每一帧,直接在帧数据上添加水印。
步骤:
- 确保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" }
- 实现水印处理的
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); } }; }
- 在初始化VideoCapture时绑定处理器:
//Video Capture videoCapture = new VideoCapture.Builder() .setVideoFrameRate(30) .setBitRate(1000000) .setTargetResolution(new Size(640, 480)) .build(); // 添加水印处理器 videoCapture.setVideoFrameProcessor(createWatermarkProcessor());
方案二:使用TextureView录制带水印的预览画面
如果你的水印已经绘制在预览界面上,可以改用TextureView承载预览,直接录制TextureView的内容。
步骤:
- 布局中替换PreviewView为自定义TextureView:
<com.yourpackage.WatermarkTextureView android:id="@+id/texture_view" android:layout_width="match_parent" android:layout_height="match_parent" />
- 实现带水印的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); } }
- 使用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
相关产品推荐
相关产品推荐

