一、效果




使用方法:在安装了anaconda和ffmpeg以后(安装对应的库也行)(ffmpeg需要加入系统路径)
命令: python <脚本名称> <档案名称>
二、代码
test1.py:
python
import subprocess
import pandas as pd
import matplotlib.pyplot as plt
def extract_packets_data(filename):
"""
使用 ffprobe 获取packet的多字段数据,csv格式输出。
- 解析字段包括:codec_type, pts, pts_time, pos, size
"""
cmd = [
'ffprobe',
'-show_packets',
filename,
'-show_entries', 'packet=codec_type,pts,pts_time,pos,size',
'-of', 'csv=p=0'
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"ffprobe error: {result.stderr}")
return result.stdout
def parse_ffprobe_output(raw_text):
"""
解析 ffprobe 输出的csv数据,返回 DataFrame
每行格式是 codec_type,pts,pts_time,pos,size
parts[0] = codec_type
parts[1] = pts
parts[2] = pts_time
parts[4] = pos (pkt_pos)
parts[3] = size (pkt_size)
"""
lines = raw_text.strip().splitlines()
data = []
for line in lines:
parts = line.split(',')
if len(parts) != 5:
continue
codec_type = parts[0].strip()
try:
pts = float(parts[1]) if parts[1] != 'N/A' else None
pts_time = float(parts[2]) if parts[2] != 'N/A' else None
pkt_pos = int(parts[4]) if parts[4] != 'N/A' else None
pkt_size = int(parts[3]) if parts[3] != 'N/A' else None
except (ValueError, TypeError):
continue
data.append({
'Type': codec_type,
'pts': pts,
'pts_time': pts_time,
'pkt_pos': pkt_pos,
'pkt_size': pkt_size,
})
df = pd.DataFrame(data)
df['seq'] = range(1, len(df) + 1)
return df
def save_csv(df, csv_filename):
df.to_csv(csv_filename, index=False)
def plot_multi(df, output_prefix):
"""
生成多副散点图,帮助分析:
- 横轴:seq (包序号)
- 纵轴分别是 pts, pts_time, pkt_pos, pkt_size
- 点颜色区分 audio/video/subtitle
"""
base_colors = {
'video': 'tab:blue',
'audio': 'tab:orange',
'subtitle': 'tab:green'
}
types_in_data = df['Type'].unique()
y_cols = ['pts', 'pts_time', 'pkt_pos', 'pkt_size']
y_labels = ['PTS', 'PTS (seconds)', 'Packet Position (file offset)', 'Packet Size (bytes)']
for y_col, ylabel in zip(y_cols, y_labels):
plt.figure(figsize=(12, 6))
for t in types_in_data:
sub = df[df['Type'] == t]
color = base_colors.get(t, 'gray')
plt.scatter(sub['seq'], sub[y_col], label=t, color=color, s=20, alpha=0.6)
plt.xlabel('Packet Sequence Number')
plt.ylabel(ylabel)
plt.title(f'{ylabel} vs Packet Sequence Number by Packet Type')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
filename = f"{output_prefix}_{y_col}.png"
# plt.savefig(filename, dpi=150)
print(f"Saved plot: {filename}")
plt.show()
def main(filename):
print("Extracting packet data with ffprobe...")
raw = extract_packets_data(filename)
print("Parsing packet data...")
df = parse_ffprobe_output(raw)
csv_file = filename + '_packets.csv'
print(f"Saving packet CSV to: {csv_file}")
save_csv(df, csv_file)
print("Generating analysis plots...")
plot_multi(df, filename + '_analysis')
print("All done.")
if __name__ == '__main__':
import sys
if len(sys.argv) < 2:
print("Usage: python script.py video_file")
exit(1)
video_file = sys.argv[1]
main(video_file)
test2.py
python
import subprocess
import json
import csv
import os
import matplotlib.pyplot as plt
import matplotlib
# 配置 matplotlib 中文字体,防止中文乱码
matplotlib.rcParams['font.sans-serif'] = ['SimHei'] # Windows常用黑体,如无请换成支持中文的字体
matplotlib.rcParams['axes.unicode_minus'] = False # 负号正常显示
def run_ffprobe_packets(file_path: str):
if not os.path.exists(file_path):
raise FileNotFoundError(f"文件不存在: {file_path}")
cmd = [
"ffprobe",
"-v", "error",
"-show_packets",
"-print_format", "json",
file_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"ffprobe 执行失败: {result.stderr}")
return json.loads(result.stdout)
def analyze_packets(packets):
threshold_pts_dts_sec = 5
size_min = 10
size_max = 5 * 1024 * 1024
last_pts = {}
last_dts = {}
analyzed = []
for p in packets:
err = []
si = p.get('stream_index')
pts_time = safe_float(p.get('pts_time'))
dts_time = safe_float(p.get('dts_time'))
size = int(p.get('size', 0))
if si in last_pts and pts_time >= 0:
if pts_time < last_pts[si]:
err.append("PTS逆序")
last_pts[si] = pts_time
if si in last_dts and dts_time >= 0:
if dts_time < last_dts[si]:
err.append("DTS逆序")
last_dts[si] = dts_time
if pts_time >= 0 and dts_time >= 0 and abs(pts_time - dts_time) > threshold_pts_dts_sec:
err.append("PTS-DTS差距异常")
if size < size_min:
err.append("包大小异常-过小")
elif size > size_max:
err.append("包大小异常-过大")
p['error_type'] = ",".join(err)
p['pts_time'] = pts_time
p['dts_time'] = dts_time
p['size'] = size
p['stream_index'] = si
analyzed.append(p)
return analyzed
def safe_float(x):
try:
return float(x)
except Exception:
return -1
def write_csv(packets, csv_path):
with open(csv_path, "w", encoding="utf-8-sig", newline='') as f:
writer = csv.writer(f)
writer.writerow(["packet_index", "stream_index", "pts_time", "dts_time", "size", "error_type"])
for i, p in enumerate(packets):
writer.writerow([
i,
p.get('stream_index'),
p.get('pts_time'),
p.get('dts_time'),
p.get('size'),
p.get('error_type')
])
def plot_packet_trends(packets, file_path):
# 识别视频和音频流索引(默认0是视频,1是音频)
video_streams = [0]
audio_streams = [1]
video_pts, video_dts, video_size, video_error_x = [], [], [], []
audio_pts, audio_dts, audio_size, audio_error_x = [], [], [], []
video_x, audio_x = [], []
video_count = 0
audio_count = 0
for p in packets:
si = p.get('stream_index')
pts = p.get('pts_time')
dts = p.get('dts_time')
size = p.get('size')
error = p.get('error_type')
if si in video_streams:
video_x.append(video_count)
video_pts.append(pts)
video_dts.append(dts)
video_size.append(size)
if error:
video_error_x.append(video_count)
video_count += 1
elif si in audio_streams:
audio_x.append(audio_count)
audio_pts.append(pts)
audio_dts.append(dts)
audio_size.append(size)
if error:
audio_error_x.append(audio_count)
audio_count += 1
plt.figure(figsize=(18, 16))
# 视频散点图 PTS 和 DTS (加微小y轴偏移避免点完全重叠)
plt.subplot(4, 1, 1)
plt.scatter(video_x, video_pts, label="PTS 视频", color='blue', s=16, alpha=0.7, marker='o', edgecolors='blue')
# 给 DTS 点加0.1秒微小偏移避免和PTS点重合覆盖
video_dts_offset = [x + 0.1 if x >= 0 else x for x in video_dts]
plt.scatter(video_x, video_dts_offset, label="DTS 视频", color='cyan', s=8, alpha=0.7, marker='^', edgecolors='cyan')
if video_error_x:
plt.scatter(video_error_x, [video_pts[x] for x in video_error_x], c='red', label="异常包", marker='x', s=50)
plt.title("视频流 PTS 和 DTS 趋势 (DTS加0.1秒偏移防止覆盖)")
plt.xlabel("视频流包序号")
plt.ylabel("时间 (秒)")
plt.legend()
# 音频散点图 PTS 和 DTS (同样加微小偏移)
plt.subplot(4, 1, 2)
plt.scatter(audio_x, audio_pts, label="PTS 音频", color='green', s=16, alpha=0.7, marker='o', edgecolors='blue')
audio_dts_offset = [x + 0.05 if x >= 0 else x for x in audio_dts]
plt.scatter(audio_x, audio_dts_offset, label="DTS 音频", color='lime', s=8, alpha=0.7, marker='^', edgecolors='cyan')
if audio_error_x:
plt.scatter(audio_error_x, [audio_pts[x] for x in audio_error_x], c='red', label="异常包", marker='x', s=50)
plt.title("音频流 PTS 和 DTS 趋势 (DTS加0.05秒偏移防止覆盖)")
plt.xlabel("音频流包序号")
plt.ylabel("时间 (秒)")
plt.legend()
# 包大小趋势
plt.subplot(4, 1, 3)
plt.plot(video_x, video_size, label="视频包大小", color='magenta')
plt.plot(audio_x, audio_size, label="音频包大小", color='orange')
plt.title("包大小趋势")
plt.xlabel("包序号")
plt.ylabel("大小 (字节)")
plt.legend()
# 新增图:视频PTS和音频PTS对比图
plt.subplot(4, 1, 4)
plt.scatter(video_x, video_pts, label="PTS 视频", color='blue', s=16, alpha=0.7, marker='o', edgecolors='green')
plt.scatter(audio_x, audio_pts, label="PTS 音频", color='green', s=8, alpha=0.7, marker='^', edgecolors='lime')
plt.title("视频PTS和音频PTS趋势对比")
plt.xlabel("包序号")
plt.ylabel("时间 (秒)")
plt.legend()
plt.tight_layout()
plt.suptitle(f"文件:{os.path.basename(file_path)} 的包数据趋势", y=1.03, fontsize=18)
plt.show()
def main(file_path):
print(f"开始检测: {file_path}")
try:
data = run_ffprobe_packets(file_path)
packets = data.get("packets", [])
if not packets:
print(f"{file_path} 无包数据,可能格式不支持或文件损坏")
return
analyzed = analyze_packets(packets)
csv_path = file_path + ".packet_analysis.csv"
write_csv(analyzed, csv_path)
print(f"包分析结果已保存至: {csv_path}")
plot_packet_trends(analyzed, file_path)
except Exception as e:
print(f"处理文件时出错: {e}")
if __name__ == "__main__":
import sys
if len(sys.argv) != 2:
print("用法: python detect_packets_trend.py 文件名")
else:
main(sys.argv[1])