APP录制IP摄像头视频异常:倍速播放且时长缺失求助
IP摄像头录制视频倍速、时长缺失问题解决方案
问题根源分析
你当前的实现只是把摄像头帧的原始字节数据直接拼接成文件,没有做视频容器封装和时序信息处理,导致播放器无法正确识别视频的帧率和时长:
- 播放器会默认用固定帧率(比如30fps)解码播放,而你的摄像头实际输出帧率可能是15fps,所以播放速度变成2倍
- 录制15秒但实际保存的帧数不足,要么是帧回调过程中丢帧,要么是没有给每帧添加时间戳,播放器无法正确计算真实时长
具体解决方案
1. 改用MediaCodec+MediaMuxer做标准视频封装
Android原生的MediaMuxer可以将原始视频帧封装成MP4等标准格式,同时记录帧率、时间戳等关键信息,让播放器正确解析。替换原来的ByteArrayOutputStream方案,核心实现如下:
private MediaMuxer mMediaMuxer; private int mVideoTrackIndex = -1; private long mStartTimeUs; private int mFrameCount = 0; private static final int TARGET_FPS = 15; private static final int RECORD_DURATION_SEC = 15; private static final int TOTAL_FRAMES = TARGET_FPS * RECORD_DURATION_SEC; // 启动录制时调用 private void startRecording() { try { String outputPath = getOutputVideoPath(); mMediaMuxer = new MediaMuxer(outputPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4); // 根据摄像头实际输出配置MediaFormat(分辨率、格式按需调整) MediaFormat format = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720); format.setInteger(MediaFormat.KEY_BIT_RATE, 1000000); format.setInteger(MediaFormat.KEY_FRAME_RATE, TARGET_FPS); format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1); format.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible); mVideoTrackIndex = mMediaMuxer.addTrack(format); mMediaMuxer.start(); mStartTimeUs = System.nanoTime() / 1000; mCameraRecordingInProgress = true; mFrameCount = 0; } catch (IOException e) { Log.e(TAG, "初始化录制失败: " + e.getMessage()); } } @Override public void onCameraFrame(CameraFrame frame) { if (!mCameraRecordingInProgress) return; if (mFrameCount >= TOTAL_FRAMES) { endRecording(); return; } try { byte[] frameData = frame.getData(); // 计算当前帧的时间戳(确保时序正确) long timestampUs = (System.nanoTime() / 1000) - mStartTimeUs; // 构造帧信息(关键帧标记按需设置) MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); bufferInfo.offset = 0; bufferInfo.size = frameData.length; bufferInfo.presentationTimeUs = timestampUs; bufferInfo.flags = (mFrameCount % TARGET_FPS == 0) ? MediaCodec.BUFFER_FLAG_KEY_FRAME : 0; // 写入到MP4容器 mMediaMuxer.writeSampleData(mVideoTrackIndex, frameData, bufferInfo); mFrameCount++; } catch (Exception e) { Log.e(TAG, "写入帧失败: " + e.getMessage()); } } public void endRecording() { if (!mCameraRecordingInProgress) return; mCameraRecordingInProgress = false; try { if (mMediaMuxer != null) { mMediaMuxer.stop(); mMediaMuxer.release(); mMediaMuxer = null; } // 通知系统扫描文件,确保相册能识别 MediaScannerConnection.scanFile(this, new String[]{getOutputVideoPath()}, null, null); } catch (Exception e) { Log.e(TAG, "结束录制失败: " + e.getMessage()); } } // 复用你原有的文件路径生成逻辑 private String getOutputVideoPath() { SimpleDateFormat sdf = new SimpleDateFormat(SEREDUtils.DATE_FORMAT_FILE_NAME); String fileName = new StringBuilder() .append(mTextViewCamName.getText().toString()) .append(SEREDUtils.PREFIX_DATE_INITIAL_FILE) .append(sdf.format(recordingInitDate)) .append(SEREDUtils.PREFIX_DATE_END_FILE) .append(sdf.format(new Date())) .append(SEREDUtils.SUFFIX_TYPE_VIDEO) .toString(); return new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES), fileName).getAbsolutePath(); }
2. 解决丢帧问题
- 确认摄像头库的帧回调是否在子线程执行,避免主线程阻塞导致帧丢失
- 检查摄像头输出帧率是否和你设置的
TARGET_FPS一致,若回调频率不足,需调整摄像头参数 - 帧回调中只做必要的时间戳计算和写入操作,耗时逻辑(如格式转换)放到单独的子线程处理
3. 修正录制时长逻辑
不再依赖固定定时器结束录制,而是通过统计捕获的帧数来控制时长(如示例中TOTAL_FRAMES = 15fps × 15秒),确保实际录制的帧数符合预期。
关键注意事项
- 不同IP摄像头输出的帧格式可能不同(如NV21、YUYV),需对应调整
MediaFormat中的颜色格式参数 - 若摄像头输出的是压缩帧(如H.264),可直接跳过编码步骤,将帧数据写入
MediaMuxer - 需处理Android 10+的存储权限适配,确保文件能正常写入公共目录
内容的提问来源于stack exchange,提问作者Pipo Karet
相关产品推荐
相关产品推荐

