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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 21:55:27