2

我正在尝试从 trafficview.org 抓取视频帧,但似乎无法弄清楚如何解码数据。

我根据这个websocket_client上的教程编写了几行代码来访问实时流 websocket 并直接接收消息。

我已经监控了通过 Chrome 上的网络选项卡传入的消息,并且还深入研究了下面代码的输出,并且相当确定数据正在以分段 MP4 的形式流入。以下是前 100 个左右的字节/消息:

b'\xfa\x00\x02\x86\xf1B\xc0\x1e\x00\x00\x00\x18ftypiso5\x00\x00\x02\x00iso6mp41\x00\x00\x02jmoov\x00\x00\x00lmvhd\x00\x00\x00 \x00\xdb\x7f\xeb\xb2\xdb\x7f\xeb\xb2\x00\x00\x03\xe8\x00\x00\x00\x00\x00\x01\x00\x00\x01\x00\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

在整个输出中,有很多 moof 和 mdat 对。假设我让这段代码运行了 30 秒,如何将这个原始字节字符串转换为 mp4 文件?

import json

from websocket import create_connection

url = 'wss://cctv.trafficview.org:8420/DDOT_CAPTOP_13.vod?progressive'

headers = json.dumps({
    'Accept-Encoding': 'gzip, deflate, br',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cache-Control': 'no-cache',
    'Connection': 'Upgrade',
    'Host': 'cctv.trafficview.org:8420',
    'Origin': 'https://trafficview.org',
    'Pragma': 'no-cache',
    'Sec-WebSocket-Extensions': 'permessage-deflate; client_max_window_bits',
    'Sec-WebSocket-Key': 'FzWbrsoHFsJWzvWGJ04ffw==',
    'Sec-WebSocket-Version': '13',
    'Upgrade': 'websocket',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36',
})

ws = create_connection(url, headers=headers)

# Then send a message through the tunnel
ws.send('ping')

# Here you will view the message return from the tunnel
flag = 3000
output = b''
while flag > 0:
    output += ws.recv()
    flag -= 1

更新:我已经修改了一些关于堆栈溢出的代码,据说可以通过管道输入 fmp4 数据并将其转换为帧。为了到达那里,我注意到 websocket 输出的前 16 个字节与我检查过的其他 mp4 文件不一致。所以我首先修剪前 16 个字节。我也不知道其中一个文件应该如何结束,所以我修剪到文件的最后一个moof。

下面的代码可以很好地读取 mp4 标头(也在下面),但无法解码任何字节。

output = output[8:]

import re
moof_locs = [m.start() for m in re.finditer(b'moof', output)]

output = output[:moof_locs[-1]-1]

import subprocess as sp
import shlex

width, height = 640, 480

# FFmpeg input PIPE: WebM encoded data as stream of bytes.
# FFmpeg output PIPE: decoded video frames in BGR format.
process = sp.Popen(shlex.split('/usr/bin/ffmpeg -i pipe: -f hls -hls_segment_type fmp4 -c h264 -an -sn pipe:'), stdin=sp.PIPE, stdout=sp.PIPE, bufsize=10**8)
process.stdin.write(output)
process.stdin.close()
in_bytes = process.stdout.read(width * height * 3)
in_frame = (np.frombuffer(in_bytes, np.uint8).reshape([height, width, 3]))

ffmpeg 的输出:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x994600] Could not find codec parameters for stream 0 (Video: h264 (avc1 / 0x31637661), none, 640x480): unspecified pixel format
Consider increasing the value for the 'analyzeduration' and 'probesize' options
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'pipe:':
  Metadata:
    major_brand     : iso5
    minor_version   : 512
    compatible_brands: iso6mp41
    creation_time   : 2020-09-11T13:40:21.000000Z
  Duration: N/A, bitrate: N/A
    Stream #0:0(und): Video: h264 (avc1 / 0x31637661), none, 640x480, 1k tbr, 1k tbn, 2k tbc (default)
    Metadata:
      creation_time   : 2020-09-11T13:40:21.000000Z
      encoder         : EvoStream Media Server
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))
Finishing stream 0:0 without any data written to it.
Nothing was written into output file 0 (pipe:), because at least one of its streams received no packets.
frame=    0 fps=0.0 q=0.0 Lsize=       0kB time=-577014:32:22.77 bitrate=  -0.0kbits/s speed=N/A    
video:0kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown
Output file is empty, nothing was encoded (check -ss / -t / -frames parameters if used)

更新 2:

在检查来自 websocket 的流后,我意识到每条消息都以一个特定的整数开头,该整数是在 trafficview 的 javascript 代码中定义的。这些代码的顺序总是相同的,它们如下所示:

Header MOOV (250)
    PBT Begin (249)
        Video Buffer (252)
        Header MOOF (251)
        Header MOOF (251)
        Header MOOF (251)
        Header MDAT (254)
    PBT End (255)

    PBT Begin (249)
    Continues Forever

其中一些标签始终相同,例如 249 条消息始终为 f900 0000,而 255 条消息始终为 ff00 0000。

我猜测 249 和 255 消息通常不在分段的 mp4 或 hls 流中,因此我认为我需要使用此标记信息从头开始构建正确的文件格式。

4

1 回答 1

1
ws = create_connection(url, headers=headers)
# Then send a message through the tunnel
ws.send('ping')

start = timeit.default_timer()
flag = True
output = []
while flag:
    output.append(ws.recv())
    if timeit.default_timer() - start > 90:
        flag = False

result = output[0][8:]

for msg in output[1:]:
    if msg[0] == 249:
        moofmdat = b''
        moof = b''
        continue

    if msg[0] == 252:
        vidbuf = msg[4:]

    if msg[0] == 251:
        moof += msg[4:]

    if msg[0] == 254:
        mdat = msg[4:]

    if msg[0] == 255:
        moofmdat += moof
        moofmdat += mdat
        moofmdat += vidbuf
        result += moofmdat

with open('test.mp4', 'wb') as file:
    file.write(result)

弄清楚了。MOOV 标头有 8 个字节的不必要信息,必须删除。每条附加消息(除了 PBT_Begin 和 PBT_End)都有 4 个字节的播放器特定数据。只需要清理每条消息并按正确的顺序放置。然后将原始字节保存为 mp4 和瞧,在 vlc 中播放的视频。

于 2020-09-15T22:07:48.037 回答