Android中混流或编码后为视频帧烧录时间戳文本的实现方案
Android视频帧烧录时间戳方案(含原生API与FFmpeg实现)
不使用FFmpeg的实现方案
可以通过Android原生API实现,核心是在**原始像素帧(YUV/RGB)**上绘制时间戳,再进行编码混流——因为编码后的H.264压缩数据无法直接修改像素内容。
场景1:处理编码前的原始YUV帧(推荐,性能更高)
如果你的流程能拿到编码前的原始YUV帧(比如RTSP解码后的帧),直接对YUV数据做文本绘制:
- 高效方式:用RenderScript或Native层算法直接修改YUV分量(比如NV12格式的Y通道),计算文本区域的像素值并替换,避免Bitmap转换的性能开销。
- 简易方式:将YUV转成Bitmap,用Canvas绘制时间戳后再转回YUV,适合低分辨率或非实时场景。
场景2:处理已编码的H.264帧
如果你的frame.getData()是H.264编码后的NAL单元,需要先解码为YUV,绘制时间戳后重新编码:
- 用MediaCodec创建解码器,输入H.264数据解码得到YUV帧。
- 对YUV帧执行时间戳绘制(同上两种方式)。
- 用MediaCodec编码器将处理后的YUV重新编码为H.264,再送入混流逻辑。
使用FFmpeg的实现方案
FFmpeg的drawtext滤镜可以快速实现时间戳烧录,支持实时混流和离线处理两种模式。
方式1:实时混流阶段烧录
通过FFmpeg滤镜链直接在编码/混流过程中绘制时间戳:
- 命令行示例(适用于提前保存的H.264文件或RTSP拉流):
参数说明:ffmpeg -i input.h264 -vf "drawtext=fontfile=/system/fonts/Roboto-Regular.ttf:text='%{localtime\:%Y-%m-%d %H\\\:%M\\\:%S}':x=10:y=10:fontsize=24:fontcolor=white:shadowcolor=black:shadowx=2:shadowy=2" -c:v libx264 output.mp4fontfile:指定Android系统字体路径(如/system/fonts/Roboto-Regular.ttf)text:时间戳格式,%{localtime:...}表示用本地时间,也可替换为自定义时间字符串x/y:文本绘制坐标,fontsize/fontcolor控制字体样式,shadow*添加阴影提升可读性
- 代码集成:通过FFmpeg API初始化滤镜图,将
drawtext加入视频处理链,读取输入帧后经滤镜处理再编码混流。
方式2:编码完成后离线烧录
如果已保存无时间戳的MP4和时间戳文件(每行对应一帧的时间字符串),可通过textfile参数读取时间戳:
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/system/fonts/Roboto-Regular.ttf:textfile=timestamps.txt:x=10:y=10:fontsize=24:fontcolor=white" -c:a copy output.mp4
针对你现有代码的修改建议
你的当前代码直接处理编码后的帧,要实现时间戳烧录,需在编码前插入帧处理逻辑:
private void muxFrame(FrameQueue.Frame frame, int encodeIndex, boolean isVideoEOS) { // 1. 先处理原始帧,绘制时间戳 byte[] processedFrameData = drawTimestampOnFrame(frame.getData(), frame.getTimestamp()); long presentationTimeUs = computePresentationTimeNsec(encodeIndex); int inputBufferIndex = codec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); inputBuffer.put(processedFrameData); // 送入处理后的帧数据 codec.queueInputBuffer(inputBufferIndex, frame.getOffset(), processedFrameData.length, frame.getTimestamp(), 0); } // 后续混流逻辑保持不变... } // 简易实现:YUV转Bitmap绘制时间戳(注意:实时场景建议用RenderScript优化) private byte[] drawTimestampOnFrame(byte[] yuvData, long timestampUs) { int width = 1280; // 替换为你的视频实际宽度 int height = 720; // 替换为你的视频实际高度 // NV21转Bitmap YuvImage yuvImage = new YuvImage(yuvData, ImageFormat.NV21, width, height, null); ByteArrayOutputStream jpegOut = new ByteArrayOutputStream(); yuvImage.compressToJpeg(new Rect(0, 0, width, height), 100, jpegOut); Bitmap bitmap = BitmapFactory.decodeByteArray(jpegOut.toByteArray(), 0, jpegOut.size()); // 绘制时间戳 Canvas canvas = new Canvas(bitmap); Paint paint = new Paint(); paint.setColor(Color.WHITE); paint.setTextSize(48); paint.setShadowLayer(2, 2, 2, Color.BLACK); String timeText = formatTimestamp(timestampUs); canvas.drawText(timeText, 20, 60, paint); // Bitmap转回NV21(示例,实际需避免JPEG压缩损失) ByteArrayOutputStream yuvOut = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, yuvOut); bitmap.recycle(); return yuvOut.toByteArray(); } private String formatTimestamp(long timestampUs) { long timestampMs = timestampUs / 1000; SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault()); return sdf.format(new Date(timestampMs)); }
内容的提问来源于stack exchange,提问作者Charles Semaan
相关产品推荐
相关产品推荐

