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

TS转MP4后帧数量异常:原30帧转后仅28/27帧的问题求助

帧数量不一致问题分析

问题背景

将TS视频转为MP4后,帧数量出现明显差异:

  • 原TS视频共30帧,OpenCV可完整读取
  • 转换后的MP4经ffprobe检测仅28帧,OpenCV读取仅27帧

转换命令

ffmpeg -i test.ts -fflags +genpts -c copy -y test.mp4

帧数量检测命令及结果

  • 检测TS帧数量:
ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 tmp.ts 

返回结果:30

  • 检测MP4帧数量:
ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 tmp.mp4

返回结果:28

OpenCV读取代码及结果

cap = cv2.VideoCapture(tmp_path)
readMat = []
while cap.isOpened():
        ret, frame = cap.read()
        if not ret:
            break
        readMat.append(frame)
  • TS文件读取30帧,MP4仅读取27帧

转换日志

ffmpeg version N-111746-gd53acf452f Copyright (c) 2000-2023 the FFmpeg developers
  built with gcc 11.3.0 (GCC)
  configuration: --ld=g++ --bindir=/bin --extra-libs='-lpthread -lm' --pkg-config-flags=--static --enable-static --enable-gpl --enable-libaom --enable-libass --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libsvtav1 --enable-libdav1d --enable-libvorbis --enable-libvpx --enable-libx264 --enable-libx265 --enable-nonfree --enable-cuda-nvcc --enable-cuvid --enable-nvenc --enable-libnpp 
  libavutil      58. 16.101 / 58. 16.101
  libavcodec     60. 23.100 / 60. 23.100
  libavformat    60. 10.100 / 60. 10.100
  libavdevice    60.  2.101 / 60.  2.101
  libavfilter     9. 10.100 /  9. 10.100
  libswscale      7.  3.100 /  7.  3.100
  libswresample   4. 11.100 /  4. 11.100
  libpostproc    57.  2.100 / 57.  2.100
[mpegts @ 0x4237240] DTS discontinuity in stream 0: packet 5 with DTS 306003, packet 6 with DTS 396001
Input #0, mpegts, from 'tmp.ts':
  Duration: 00:00:21.33, start: 3.400000, bitrate: 15 kb/s
  Program 1 
    Metadata:
      service_name    : Service01
      service_provider: FFmpeg
  Stream #0:0[0x100]: Video: h264 (High) ([27][0][0][0] / 0x001B), yuv420p(progressive), 300x300, 1 fps, 3 tbr, 90k tbn
Output #0, mp4, to 'test.mp4':
  Metadata:
    encoder         : Lavf60.10.100
  Stream #0:0: Video: h264 (High) (avc1 / 0x31637661), yuv420p(progressive), 300x300, q=2-31, 1 fps, 3 tbr, 90k tbn
Stream mapping:
  Stream #0:0 -> #0:0 (copy)
Press [q] to stop, [?] for help
[out#0/mp4 @ 0x423e280] video:25kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 4.192123%
frame=   30 fps=0.0 q=-1.0 Lsize=      26kB time=00:00:21.00 bitrate=  10.3kbits/s speed=1e+04x 

TS视频生成方式

该TS视频由以下Python类的update方法拼接静态图片生成(补充了原代码缺失的必要导入):

import cv2
import os
import subprocess
import shutil
import logging
import numpy as np
from tempfile import NamedTemporaryFile
from typing import Optional

LOGGER = logging.getLogger(__name__)

class VideoUpdater:
    def __init__(
        self, video_path: str, framerate: int, timePerFrame: Optional[int] = None
    ):
        """
        Video updater takes in a video path, and updates it using a supplied frame, based on a given framerate.
        Args:
            video_path: str: Specify the path to the video file
            framerate: int: Set the frame rate of the video
        """
        if not video_path.endswith(".mp4"):
            LOGGER.warning(
                f"File type {os.path.splitext(video_path)[1]} not supported for streaming, switching to ts"
            )
            video_path = os.path.splitext(video_path)[0] + ".mp4"

        self._ps = None
        self.env = {}
        self.ffmpeg = "/usr/bin/ffmpeg "

        self.video_path = video_path
        self.ts_path = video_path.replace(".mp4", ".ts")
        self.tfile = None
        self.framerate = framerate
        self._video = None
        self.last_frame = None
        self.curr_frame = None


    def update(self, frame: np.ndarray):
        if len(frame.shape) == 2:
            frame = cv2.cvtColor(frame, cv2.COLOR_GRAY2BGR)
        else:
            frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)
        self.writeFrame(frame)

    def writeFrame(self, frame: np.ndarray):
        """
        The writeFrame function takes a frame and writes it to the video file.
        Args:
            frame: np.ndarray: Write the frame to a temporary file
        """

        tImLFrame = NamedTemporaryFile(suffix=".png")
        tVidLFrame = NamedTemporaryFile(suffix=".ts")

        cv2.imwrite(tImLFrame.name, frame)
        ps = subprocess.Popen(
            self.ffmpeg
            + rf"-loop 1 -r {self.framerate} -i {tImLFrame.name} -t {self.framerate} -vcodec libx264 -pix_fmt yuv420p -y {tVidLFrame.name}",
            env=self.env,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        ps.communicate()
        if os.path.isfile(self.ts_path):
            # this does not work to watch, as timestamps are not updated
            ps = subprocess.Popen(
                self.ffmpeg
                + rf'-i "concat:{self.ts_path}|{tVidLFrame.name}" -c copy -y {self.ts_path.replace(".ts", ".bak.ts")}',
                env=self.env,
                shell=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
            )
            ps.communicate()
            shutil.move(self.ts_path.replace(".ts", ".bak.ts"), self.ts_path)

        else:
            shutil.copyfile(tVidLFrame.name, self.ts_path)
        # fixing timestamps, we dont have to wait for this operation
        ps = subprocess.Popen(
            self.ffmpeg
            + rf"-i {self.ts_path} -fflags +genpts -c copy -y {self.video_path}",
            env=self.env,
            shell=True,
            # stdout=subprocess.PIPE,
            # stderr=subprocess.PIPE,
        )
        tImLFrame.close()
        tVidLFrame.close()

核心原因分析

1. TS文件存在DTS时间戳不连续

从转换日志的警告[mpegts @ 0x4237240] DTS discontinuity in stream 0可知,原TS文件内部解码时间戳(DTS)存在跳变。TS是流媒体容器,对时间戳容错性高,播放器/读取库可通过逻辑跳过异常;但MP4作为本地容器,ffmpeg在流拷贝时会过滤不符合MP4时间戳规范的帧,导致帧数量减少。

2. TS片段拼接时的时间戳冲突

VideoUpdater生成单帧TS片段的命令参数不合理:-loop 1 -r {self.framerate} -t {self.framerate}会生成时长等于帧率的视频(比如帧率为1时生成1秒1帧,帧率为3时生成3秒3帧),而非预期的单帧。拼接时直接用concat协议拷贝流,每个片段的时间戳独立(如第二个片段DTS从0开始),导致拼接后时间戳混乱,即使后续用+genpts生成PTS,也无法修复DTS本身的不连续问题。

3. OpenCV读取MP4的额外丢失

OpenCV的VideoCapture依赖ffmpeg后端,但它的读取逻辑偏向按时间戳顺序采样,而非严格读取所有帧。当MP4内部存在时间戳异常时,OpenCV可能跳过帧或提前终止读取,导致比ffprobe检测的帧数量更少。

4. 异步时间戳修复的隐患

原代码中修复时间戳的ffmpeg进程是异步启动的(未调用ps.communicate()等待完成),可能导致TS文件未完成修复就被读取,时间戳异常未被修正。


解决建议

  1. 修复单帧TS生成命令:改为每次生成1帧的TS片段,参数调整为:
ffmpeg -loop 1 -r 1 -i {tImLFrame.name} -t 1 -vcodec libx264 -pix_fmt yuv420p -y {tVidLFrame.name}
  1. 拼接时重新编码修复时间戳:避免流拷贝,重新编码让ffmpeg生成连续时间戳:
ffmpeg -i "concat:{self.ts_path}|{tVidLFrame.name}" -vcodec libx264 -pix_fmt yuv420p -y {self.ts_path.replace(".ts", ".bak.ts")}
  1. 转MP4时强制重置时间戳:添加+igndts忽略原有异常DTS,强制生成连续PTS:
ffmpeg -i test.ts -fflags +igndts +genpts -c copy -y test.mp4
  1. 同步执行时间戳修复:将修复时间戳的ffmpeg进程改为同步执行,确保操作完成后再进行后续步骤。

内容的提问来源于stack exchange,提问作者Vasilis Lemonidis

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 20:34:49