#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ H264文件检查工具 检查生成的test.h264文件是否符合播放要求 """ import os import sys def check_h264_file(file_path): """检查H264文件是否符合播放要求""" print(f"检查文件: {file_path}\n") # 1. 检查文件是否存在 if not os.path.exists(file_path): print("❌ 文件不存在!") print(f" 预期路径: {file_path}") print("\n提示:") print(" - 如果是Android设备,需要先通过adb pull下载文件") print(f" - 或者将文件复制到当前目录") return False # 2. 检查文件大小 file_size = os.path.getsize(file_path) print(f"✓ 文件大小: {file_size} 字节 ({file_size / 1024:.2f} KB)") if file_size == 0: print("❌ 文件为空!") return False if file_size < 100: print("⚠️ 警告: 文件太小,可能只有SPS/PPS或1帧数据") # 3. 读取文件内容并分析 try: with open(file_path, 'rb') as f: data = f.read() except Exception as e: print(f"❌ 读取文件失败: {e}") return False # 检查NALU单元 nal_start_code_3 = bytes([0x00, 0x00, 0x01]) nal_start_code_4 = bytes([0x00, 0x00, 0x00, 0x01]) nalu_list = [] i = 0 # 查找所有NALU单元 while i < len(data): # 查找起始码 found = False # 查找4字节起始码 if i + 4 <= len(data) and data[i:i+4] == nal_start_code_4: start_pos = i + 4 found = True # 查找3字节起始码 elif i + 3 <= len(data) and data[i:i+3] == nal_start_code_3: # 确保前面不是0x00 (避免误判) if i == 0 or data[i-1] != 0x00: start_pos = i + 3 found = True if found: # 查找下一个起始码 next_start = -1 # 先找4字节起始码 for j in range(start_pos, len(data) - 3): if j + 4 <= len(data) and data[j:j+4] == nal_start_code_4: next_start = j break # 如果没找到4字节的,找3字节的 if next_start == -1: for j in range(start_pos, len(data) - 2): if j + 3 <= len(data) and data[j:j+3] == nal_start_code_3: # 确保前面不是0x00 if j == 0 or data[j-1] != 0x00: next_start = j break # 提取NALU数据 if next_start == -1: nalu_data = data[start_pos:] else: nalu_data = data[start_pos:next_start] if len(nalu_data) > 0: # 获取NALU类型 (第一个字节的低5位) nal_type = nalu_data[0] & 0x1F nalu_list.append({ 'type': nal_type, 'size': len(nalu_data), 'name': get_nalu_type_name(nal_type) }) i = next_start if next_start != -1 else len(data) else: i += 1 # 4. 分析NALU单元 print(f"\n✓ 找到 {len(nalu_list)} 个NALU单元\n") if len(nalu_list) == 0: print("❌ 未找到任何NALU单元!文件格式可能不正确") return False # 统计不同类型的NALU nal_type_count = {} for nalu in nalu_list: nal_type = nalu['type'] if nal_type not in nal_type_count: nal_type_count[nal_type] = 0 nal_type_count[nal_type] += 1 print("NALU类型统计:") for nal_type in sorted(nal_type_count.keys()): count = nal_type_count[nal_type] name = get_nalu_type_name(nal_type) print(f" {name} (类型{nal_type}): {count} 个") # 5. 检查关键要求 print("\n检查项:") has_sps = 7 in nal_type_count has_pps = 8 in nal_type_count has_idr = 5 in nal_type_count has_non_idr = 1 in nal_type_count # SPS/PPS检查 if has_sps: print(f" ✓ 包含SPS (序列参数集) - {nal_type_count[7]} 个") else: print(" ❌ 缺少SPS (序列参数集) - 必需!") if has_pps: print(f" ✓ 包含PPS (图像参数集) - {nal_type_count[8]} 个") else: print(" ❌ 缺少PPS (图像参数集) - 必需!") # 关键帧检查 if has_idr: print(f" ✓ 包含IDR关键帧 - {nal_type_count[5]} 个") else: print(" ❌ 缺少IDR关键帧") # 非关键帧检查 if has_non_idr: print(f" ✓ 包含非IDR帧 - {nal_type_count[1]} 个") else: print(" ⚠️ 没有非IDR帧(只有关键帧)") # 6. 总体评估 print("\n" + "="*50) can_play = has_sps and has_pps and has_idr if can_play: if len(nalu_list) >= 3: print("✅ 文件应该可以播放!") print(f" 包含完整的SPS/PPS和 {len(nalu_list)} 个NALU单元") else: print("⚠️ 文件结构完整,但帧数较少") print(" 建议录制更长时间以获得更多帧") else: print("❌ 文件可能无法播放") if not has_sps or not has_pps: print(" 原因: 缺少SPS/PPS参数集") if not has_idr: print(" 原因: 缺少IDR关键帧") print("="*50) return can_play def get_nalu_type_name(nal_type): """获取NALU类型名称""" nal_names = { 1: "非IDR编码片", 2: "编码片数据分区A", 3: "编码片数据分区B", 4: "编码片数据分区C", 5: "IDR图像编码片", 6: "SEI (补充增强信息)", 7: "SPS (序列参数集)", 8: "PPS (图像参数集)", 9: "访问单元分隔符", 10: "序列结束", 11: "流结束", 12: "填充数据" } return nal_names.get(nal_type, f"未知类型{nal_type}") if __name__ == "__main__": # 默认文件路径 default_path = "test.h264" # 如果提供了命令行参数,使用参数作为文件路径 file_path = sys.argv[1] if len(sys.argv) > 1 else default_path print("H264文件检查工具") print("="*50) success = check_h264_file(file_path) sys.exit(0 if success else 1)