新增hls测试
This commit is contained in:
196
hls_downloader.py
Normal file
196
hls_downloader.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import requests
|
||||||
|
import m3u8
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
from urllib.parse import urljoin
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class RawHLSDownloader:
|
||||||
|
"""直接下载HLS分片,保留原始时间戳"""
|
||||||
|
|
||||||
|
def __init__(self, m3u8_url, output_dir="segments"):
|
||||||
|
self.m3u8_url = m3u8_url
|
||||||
|
self.output_dir = output_dir
|
||||||
|
self.session = requests.Session()
|
||||||
|
self.session.headers.update({
|
||||||
|
'User-Agent': 'Mozilla/5.0'
|
||||||
|
})
|
||||||
|
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
|
def download_playlist(self):
|
||||||
|
"""下载并解析m3u8播放列表"""
|
||||||
|
response = self.session.get(self.m3u8_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 解析播放列表
|
||||||
|
playlist = m3u8.loads(response.text, uri=self.m3u8_url)
|
||||||
|
|
||||||
|
# 如果是主播放列表(多码率),选择第一个
|
||||||
|
if playlist.is_variant:
|
||||||
|
print(f"发现多码率流,选择: {playlist.playlists[0].stream_info}")
|
||||||
|
playlist_url = playlist.playlists[0].absolute_uri
|
||||||
|
response = self.session.get(playlist_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
playlist = m3u8.loads(response.text, uri=playlist_url)
|
||||||
|
|
||||||
|
return playlist
|
||||||
|
|
||||||
|
def download_segment_raw(self, segment_url, segment_filename):
|
||||||
|
"""直接下载TS分片,不进行任何处理"""
|
||||||
|
print(f"下载: {segment_url}")
|
||||||
|
|
||||||
|
response = self.session.get(segment_url, stream=True)
|
||||||
|
response.raise_for_status()
|
||||||
|
|
||||||
|
# 直接保存原始字节
|
||||||
|
filepath = os.path.join(self.output_dir, segment_filename)
|
||||||
|
with open(filepath, 'wb') as f:
|
||||||
|
for chunk in response.iter_content(chunk_size=8192):
|
||||||
|
f.write(chunk)
|
||||||
|
|
||||||
|
# 获取下载时间戳
|
||||||
|
download_time = datetime.now()
|
||||||
|
|
||||||
|
# 获取文件大小
|
||||||
|
file_size = os.path.getsize(filepath)
|
||||||
|
|
||||||
|
print(f" 保存到: {segment_filename} ({file_size} 字节)")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'filename': segment_filename,
|
||||||
|
'url': segment_url,
|
||||||
|
'size': file_size,
|
||||||
|
'download_time': download_time,
|
||||||
|
'local_path': filepath
|
||||||
|
}
|
||||||
|
|
||||||
|
def analyze_raw_ts(self, ts_file):
|
||||||
|
"""分析原始TS文件的时间戳(不通过FFmpeg)"""
|
||||||
|
import struct
|
||||||
|
|
||||||
|
filepath = os.path.join(self.output_dir, ts_file)
|
||||||
|
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
# 读取TS包(每个188字节)
|
||||||
|
packet_size = 188
|
||||||
|
packets = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
packet = f.read(packet_size)
|
||||||
|
if len(packet) < packet_size:
|
||||||
|
break
|
||||||
|
|
||||||
|
# 解析TS包头
|
||||||
|
sync_byte = packet[0]
|
||||||
|
if sync_byte != 0x47: # TS同步字节
|
||||||
|
print(f"警告: 无效的TS包同步字节: {sync_byte:02x}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 解析PID
|
||||||
|
pid = ((packet[1] & 0x1F) << 8) | packet[2]
|
||||||
|
|
||||||
|
# 检查适配字段是否存在
|
||||||
|
adaptation_field_control = (packet[3] >> 4) & 0x03
|
||||||
|
|
||||||
|
# 如果有适配字段,可能包含PCR
|
||||||
|
if adaptation_field_control in [2, 3]:
|
||||||
|
adaptation_field_length = packet[4]
|
||||||
|
if adaptation_field_length > 0:
|
||||||
|
pcr_flag = (packet[5] >> 4) & 0x01
|
||||||
|
if pcr_flag and adaptation_field_length >= 6:
|
||||||
|
# 提取PCR
|
||||||
|
pcr_bytes = packet[6:12]
|
||||||
|
pcr_base = (
|
||||||
|
(pcr_bytes[0] << 25) |
|
||||||
|
(pcr_bytes[1] << 17) |
|
||||||
|
(pcr_bytes[2] << 9) |
|
||||||
|
(pcr_bytes[3] << 1) |
|
||||||
|
(pcr_bytes[4] >> 7)
|
||||||
|
)
|
||||||
|
pcr_extension = ((pcr_bytes[4] & 0x01) << 8) | pcr_bytes[5]
|
||||||
|
pcr_value = pcr_base * 300 + pcr_extension
|
||||||
|
pcr_ms = pcr_value / 27000.0 # 转换为毫秒
|
||||||
|
|
||||||
|
packets.append({
|
||||||
|
'pid': pid,
|
||||||
|
'has_pcr': True,
|
||||||
|
'pcr_ms': pcr_ms
|
||||||
|
})
|
||||||
|
continue
|
||||||
|
|
||||||
|
packets.append({
|
||||||
|
'pid': pid,
|
||||||
|
'has_pcr': False
|
||||||
|
})
|
||||||
|
|
||||||
|
# 统计PCR信息
|
||||||
|
pcr_packets = [p for p in packets if p['has_pcr']]
|
||||||
|
|
||||||
|
return {
|
||||||
|
'total_packets': len(packets),
|
||||||
|
'pcr_packets': len(pcr_packets),
|
||||||
|
'pcr_values': [p['pcr_ms'] for p in pcr_packets] if pcr_packets else [],
|
||||||
|
'file_size': os.path.getsize(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
def monitor_and_download(self, max_segments=None):
|
||||||
|
"""监控并下载新的分片"""
|
||||||
|
downloaded_segments = set()
|
||||||
|
segment_counter = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 获取最新播放列表
|
||||||
|
playlist = self.download_playlist()
|
||||||
|
|
||||||
|
# 检查播放列表中的分片
|
||||||
|
for segment in playlist.segments:
|
||||||
|
segment_url = segment.absolute_uri
|
||||||
|
segment_filename = os.path.basename(segment_url)
|
||||||
|
|
||||||
|
# 如果还没下载过
|
||||||
|
if segment_filename not in downloaded_segments:
|
||||||
|
# 下载原始分片
|
||||||
|
result = self.download_segment_raw(segment_url, segment_filename)
|
||||||
|
|
||||||
|
# 分析原始TS文件
|
||||||
|
analysis = self.analyze_raw_ts(segment_filename)
|
||||||
|
|
||||||
|
print(f" 分析结果: {analysis['total_packets']}包, "
|
||||||
|
f"PCR包: {analysis['pcr_packets']}")
|
||||||
|
|
||||||
|
if analysis['pcr_values']:
|
||||||
|
print(f" PCR范围: {min(analysis['pcr_values']):.1f}ms - "
|
||||||
|
f"{max(analysis['pcr_values']):.1f}ms")
|
||||||
|
|
||||||
|
downloaded_segments.add(segment_filename)
|
||||||
|
segment_counter += 1
|
||||||
|
|
||||||
|
# 检查是否达到最大数量
|
||||||
|
if max_segments and segment_counter >= max_segments:
|
||||||
|
print(f"达到最大分片数: {max_segments}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# 等待下一轮
|
||||||
|
sleep_time = playlist.target_duration or 2
|
||||||
|
print(f"等待 {sleep_time} 秒...")
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("用户中断")
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"错误: {e}")
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
downloader = RawHLSDownloader(
|
||||||
|
m3u8_url="http://192.168.110.139:8080/stream.m3u8",
|
||||||
|
output_dir="raw_segments"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 下载5个分片进行测试
|
||||||
|
downloader.monitor_and_download(max_segments=5)
|
||||||
32
hls_test.py
Normal file
32
hls_test.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 使用PyAV(FFmpeg的Python绑定)
|
||||||
|
import av
|
||||||
|
|
||||||
|
|
||||||
|
def extract_timestamps_from_ts(ts_file):
|
||||||
|
container = av.open(ts_file)
|
||||||
|
video_stream = container.streams.video[0]
|
||||||
|
|
||||||
|
frame_index = 0
|
||||||
|
for packet in container.demux(video_stream):
|
||||||
|
for frame in packet.decode():
|
||||||
|
# 获取精确时间信息
|
||||||
|
pts_ms = frame.pts * video_stream.time_base * 1000
|
||||||
|
dts_ms = frame.dts * video_stream.time_base * 1000 if frame.dts else None
|
||||||
|
|
||||||
|
print(f"帧 {frame_index}:")
|
||||||
|
print(f" PTS: {pts_ms:.3f} ms")
|
||||||
|
print(f" DTS: {dts_ms:.3f} ms" if dts_ms else " DTS: None")
|
||||||
|
print(f" 类型: {frame.pict_type}") # I, P, B
|
||||||
|
print(f" 关键帧: {frame.key_frame}")
|
||||||
|
|
||||||
|
frame_index += 1
|
||||||
|
|
||||||
|
# 精度:可以达到微秒级
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# extract_timestamps_from_ts("D:\\ProjectDoc\\Police\\data\\hls\\segment_00000001.ts")
|
||||||
|
extract_timestamps_from_ts("raw_segments\\segment_00001619.ts")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user