webrtc-mediasoup 学习- 调试相关(二)

官方文档

v3 Documentation

node 相关语法

node 进程相关

Node child_process的fork,spawn,exec我有话要说

node 通过 child_process.spawn 完成多个子线程创建。

spawn英 [spɔːn] 美 [spɔːn]  v.产卵;引发;引起;导致;造成

protoo.websocket 基本API:info, accept, reject

protoo.websocket 基本API讲解与使用(六

gulp 使用

gulp是一个常见的自动化构建工具,主要用来设定程序自动处理静态资源的工作。简单的说,gulp就是用来打包项目的。

node 调试

三种启动方式

node的3种运行方式

  • node xx.js
  • node start
  • npm start

服务端调试

  1. 启动
node --inspect-brk service.js
  1. 打开浏览器
chrome://inspect
dump 工具
  • 方法一:设置环境变量
export INTERACTIVE=1; node service.js
  • 方法二:另外打开终端执行 node connect.js
node connect.js
mediasoup-worker 应用程序
cd mediasoup-3/worker //进入目录
make  //根据 makefile 文件执行 make
  • 方法三:Chrome 自带的 WebRTC 分析工具
chrome://webrtc-internals/

根据 mediasoup/node/lib/Worker.js定义,找到当前编译生成二进制文件:

// If env MEDIASOUP_WORKER_BIN is given, use it as worker binary.
// Otherwise if env MEDIASOUP_BUILDTYPE is 'Debug' use the Debug binary.
// Otherwise use the Release binary.
const workerBin = process.env.MEDIASOUP_WORKER_BIN
    ? process.env.MEDIASOUP_WORKER_BIN
    : process.env.MEDIASOUP_BUILDTYPE === 'Debug'
        ? path.join(__dirname, '..', '..', 'worker', 'out', 'Debug', 'mediasoup-worker')
        : path.join(__dirname, '..', '..', 'worker', 'out', 'Release', 'mediasoup-worker');

mediasoup-demo/server/node_modules/mediasoup/worker/out/Release/目录下生成 release 版二进制文件 mediasoup-worker,被 nodejs 开启相应数目子进程。

客户端调试

mediasoup-demo 设置

默认视频编码 VP8 修改为 H264

修改配置项,把 H264 配置移到 vp8,vp9 之前

ffmpeg 推流 h264 脚本修改

ffmpeg推流到mediasoup

#!/usr/bin/env bash

function show_usage()
{
	echo
	echo "USAGE"
	echo "-----"
	echo
	echo "  SERVER_URL=https://my.mediasoup-demo.org:4443 ROOM_ID=test MEDIA_FILE=./test.mp4 ./ffmpeg.sh"
	echo
	echo "  where:"
	echo "  - SERVER_URL is the URL of the mediasoup-demo API server"
	echo "  - ROOM_ID is the id of the mediasoup-demo room (it must exist in advance)"
	echo "  - MEDIA_FILE is the path to a audio+video file (such as a .mp4 file)"
	echo
	echo "REQUIREMENTS"
	echo "------------"
	echo
	echo "  - ffmpeg: stream audio and video (https://www.ffmpeg.org)"
	echo "  - httpie: command line HTTP client (https://httpie.org)"
	echo "  - jq: command-line JSON processor (https://stedolan.github.io/jq)"
	echo
}

echo

if [ -z "${SERVER_URL}" ] ; then
	>&2 echo "ERROR: missing SERVER_URL environment variable"
	show_usage
	exit 1
fi

if [ -z "${ROOM_ID}" ] ; then
	>&2 echo "ERROR: missing ROOM_ID environment variable"
	show_usage
	exit 1
fi

if [ -z "${MEDIA_FILE}" ] ; then
	>&2 echo "ERROR: missing MEDIA_FILE environment variable"
	show_usage
	exit 1
fi

if [ "$(command -v ffmpeg)" == "" ] ; then
	>&2 echo "ERROR: ffmpeg command not found, must install FFmpeg"
	show_usage
	exit 1
fi

if [ "$(command -v http)" == "" ] ; then
	>&2 echo "ERROR: http command not found, must install httpie"
	show_usage
	exit 1
fi

if [ "$(command -v jq)" == "" ] ; then
	>&2 echo "ERROR: jq command not found, must install jq"
	show_usage
	exit 1
fi

set -e

BROADCASTER_ID=$(LC_CTYPE=C tr -dc A-Za-z0-9 < /dev/urandom | fold -w ${1:-32} | head -n 1)
HTTPIE_COMMAND="http --check-status --verify false"
AUDIO_SSRC=1111
AUDIO_PT=100
VIDEO_SSRC=2222
VIDEO_PT=101

#
# Verify that a room with id ROOM_ID does exist by sending a simlpe HTTP GET. If
# not abort since we are not allowed to initiate a room..
#
echo ">>> verifying that room '${ROOM_ID}' exists..."

${HTTPIE_COMMAND} \
	GET ${SERVER_URL}/rooms/${ROOM_ID} > /dev/null

#
# Create a Broadcaster entity in the server by sending a POST with our metadata.
# Note that this is not related to mediasoup at all, but will become just a JS
# object in the Node.js application to hold our metadata and mediasoup Transports
# and Producers.
#
echo ">>> creating Broadcaster..."

${HTTPIE_COMMAND} \
	POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters \
	id="${BROADCASTER_ID}" \
	displayName="Broadcaster" \
	device:='{"name": "FFmpeg"}' \
	> /dev/null

#
# Upon script termination delete the Broadcaster in the server by sending a
# HTTP DELETE.
#
trap 'echo ">>> script exited with status code $?"; ${HTTPIE_COMMAND} DELETE ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID} > /dev/null' EXIT

#
# Create a PlainTransport in the mediasoup to send our audio using plain RTP
# over UDP. Do it via HTTP post specifying type:"plain" and comedia:true and
# rtcpMux:false.
#
echo ">>> creating mediasoup PlainTransport for producing audio..."

res=$(${HTTPIE_COMMAND} \
	POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports \
	type="plain" \
	comedia:=true \
	rtcpMux:=false \
	2> /dev/null)

#
# Parse JSON response into Shell variables and extract the PlainTransport id,
# IP, port and RTCP port.
#
eval "$(echo ${res} | jq -r '@sh "audioTransportId=\(.id) audioTransportIp=\(.ip) audioTransportPort=\(.port) audioTransportRtcpPort=\(.rtcpPort)"')"

#
# Create a PlainTransport in the mediasoup to send our video using plain RTP
# over UDP. Do it via HTTP post specifying type:"plain" and comedia:true and
# rtcpMux:false.
#
echo ">>> creating mediasoup PlainTransport for producing video..."

res=$(${HTTPIE_COMMAND} \
	POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports \
	type="plain" \
	comedia:=true \
	rtcpMux:=false \
	2> /dev/null)

#
# Parse JSON response into Shell variables and extract the PlainTransport id,
# IP, port and RTCP port.
#
eval "$(echo ${res} | jq -r '@sh "videoTransportId=\(.id) videoTransportIp=\(.ip) videoTransportPort=\(.port) videoTransportRtcpPort=\(.rtcpPort)"')"

#
# Create a mediasoup Producer to send audio by sending our RTP parameters via a
# HTTP POST.
#
echo ">>> creating mediasoup audio Producer..."

${HTTPIE_COMMAND} -v \
	POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports/${audioTransportId}/producers \
	kind="audio" \
	rtpParameters:="{ \"codecs\": [{ \"mimeType\":\"audio/opus\", \"payloadType\":${AUDIO_PT}, \"clockRate\":48000, \"channels\":2, \"parameters\":{ \"sprop-stereo\":1 } }], \"encodings\": [{ \"ssrc\":${AUDIO_SSRC} }] }" \
	> /dev/null

#
# Create a mediasoup Producer to send video by sending our RTP parameters via a
# HTTP POST.
#
echo ">>> creating mediasoup video Producer..."

${HTTPIE_COMMAND} -v \
	POST ${SERVER_URL}/rooms/${ROOM_ID}/broadcasters/${BROADCASTER_ID}/transports/${videoTransportId}/producers \
	kind="video" \
	rtpParameters:="{ \"codecs\": [{ \"mimeType\":\"video/h264\", \"payloadType\":${VIDEO_PT}, \"clockRate\":90000 , \"parameters\": {\"packetization-mode\": 1, \"level-asymmetry-allowed\": 1, \"profile-level-id\": \"42e01f\"}}], \"encodings\": [{ \"ssrc\":${VIDEO_SSRC} }] }" \
	> /dev/null

#
# Run ffmpeg command and make it send audio and video RTP with codec payload and
# SSRC values matching those that we have previously signaled in the Producers
# creation above. Also, tell ffmpeg to send the RTP to the mediasoup
# PlainTransports' ip and port.
#
echo ">>> running ffmpeg..."

#
# NOTES:
# - We can add ?pkt_size=1200 to each rtp:// URI to limit the max packet size
#   to 1200 bytes.
#
ffmpeg \
	-re \
	-v info \
	-stream_loop -1 \
	-i ${MEDIA_FILE} \
	-map 0:a:0 \
	-acodec libopus -ab 128k -ac 2 -ar 48000 \
	-map 0:v:0 \
	-pix_fmt yuv420p -c:v libx264 -b:v 1000k -deadline realtime -cpu-used 4 \
	-f tee \
	"[select=a:f=rtp:ssrc=${AUDIO_SSRC}:payload_type=${AUDIO_PT}]rtp://${audioTransportIp}:${audioTransportPort}?rtcpport=${audioTransportRtcpPort}|[select=v:f=rtp:ssrc=${VIDEO_SSRC}:payload_type=${VIDEO_PT}]rtp://${videoTransportIp}:${videoTransportPort}?rtcpport=${videoTransportRtcpPort}"

/home/brody/workspace/software/mediasoup-demo/broadcasters
brody@brody:~/workspace/software/mediasoup-demo/broadcasters$ ll
total 24
drwxrwxr-x 2 brody brody 4096 9月  27 09:11 ./
drwxrwxr-x 8 brody brody 4096 9月  27 09:11 ../
-rwxrwxr-x 1 brody brody 5904 9月  27 09:11 ffmpeg.sh*
-rwxrwxr-x 1 brody brody 6217 9月  27 09:11 gstreamer.sh*


SERVER_URL=https://(服务器 IP):4443 ROOM_ID=(房间号) MEDIA_FILE=(FFMPEG 输入文件路径) ./ffmpeg.sh
SERVER_URL=https://192.168.110.129:4443 ROOM_ID=brody MEDIA_FILE=source.200kbps.768x320.flv ./ffmpeg.sh

wireshark 解析数据

配置 wireshark 解密 chrom 的加密数据

Ubuntu使用wireshark抓取https数据:设置导出pre-master-secret log,然后wireshark设置pre-master-secret log路径,这样就可以解密,新版没有 SSL protocal,统一集成在 TLS protocal 中。

导出 RTP 打包的 H264 H265 码流

rtp_h264_export.lua
-- Dump RTP h.264 payload to raw h.264 file (*.264)  
-- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it  
-- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,  
-- STAP-A and FU-A format RTP payload for H.264.  
-- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"  
-- Author: Huang Qiangxiong (qiangxiong.huang@gmail.com)  
-- change log:  
--      2012-03-13  
--          Just can play  
--      2012-04-28  
--          Add local to local function, and add [local bit = require("bit")] to prevent  
--          bit recleared in previous file.  
--      2013-07-11  
--          Add sort RTP and drop uncompleted frame option.  
--      2013-07-19  
--          Do nothing when tap is triggered other than button event.  
--          Add check for first or last packs lost of one frame.  
--      2014-10-23  
--          Fixed bug about print a frame.nalu_type error.  
--      2014-11-07  
--          Add support for Lua 5.2(>1.10.1) and 5.1(<=1.10.1).   
--          Change range:string() to range:raw().  
--          Change h264_f.value to h264_f.range:bytes() because of wireshark lua bug.  
--      2015-06-03  
--          Fixed bug that if ipv6 address is using the file will not generated.(we replace ':' to '.')  
------------------------------------------------------------------------------------------------  
do  
    --local bit = require("bit") -- only work before 1.10.1  
    --local bit = require("bit32") -- only work after 1.10.1 (only support in Lua 5.2)  
    local version_str = string.match(_VERSION, "%d+[.]%d*")  
    local version_num = version_str and tonumber(version_str) or 5.1  
    local bit = (version_num >= 5.2) and require("bit32") or require("bit")  
  
    -- for geting h264 data (the field's value is type of ByteArray)  
    local f_h264 = Field.new("h264")   
    local f_rtp = Field.new("rtp")   
    local f_rtp_seq = Field.new("rtp.seq")  
    local f_rtp_timestamp = Field.new("rtp.timestamp")  
    local nalu_type_list = {  
        [0] = "Unspecified",  
        [1] = "P/B_slice",  
        [2] = "P/B_A",  
        [3] = "P/B_B",  
        [4] = "P/B_C",  
        [5] = "I_slice",  
        [6] = "SEI",  
        [7] = "SPS",  
        [8] = "PPS",  
        [9] = "AUD",  
    }  
      
    local function get_enum_name(list, index)  
        local value = list[index]  
        return value and value or "Unknown"  
    end  
  
    -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function  
    local function export_h264_to_file()  
        -- window for showing information  
        local tw = TextWindow.new("Export H264 to File Info Win")  
        --local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")  
        local pgtw;  
          
        -- add message to information window  
        function twappend(str)  
            tw:append(str)  
            tw:append("\n")  
        end  
          
        -- running first time for counting and finding sps+pps, second time for real saving  
        local first_run = true   
        -- variable for storing rtp stream and dumping parameters  
        local stream_infos = nil  
        -- drop_uncompleted_frame  
        local drop_uncompleted_frame = false  
        -- max frame buffer size  
        local MAX_FRAME_NUM = 3  
  
        -- trigered by all h264 packats  
        local my_h264_tap = Listener.new(tap, "h264")  
          
        -- get rtp stream info by src and dst address  
        function get_stream_info(pinfo)  
            local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "to" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port) .. (drop_uncompleted_frame and "_dropped" or "_all")  
            key = key:gsub(":", ".")  
            local stream_info = stream_infos[key]  
            if not stream_info then -- if not exists, create one  
                stream_info = { }  
                stream_info.filename = key.. ".264"  
                stream_info.file = io.open(stream_info.filename, "wb")  
                stream_info.counter = 0 -- counting h264 total NALUs  
                stream_info.counter2 = 0 -- for second time running  
                stream_infos[key] = stream_info  
                twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port)   
                         .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:\n         [" .. stream_info.filename .. "] ...\n")  
            end  
            return stream_info  
        end  
          
        -- write a NALU or part of NALU to file.  
        local function real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)  
            if first_run then  
                stream_info.counter = stream_info.counter + 1  
                  
                if begin_with_nalu_hdr then  
                    -- save SPS or PPS  
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)  
                    if not stream_info.sps and nalu_type == 7 then  
                        stream_info.sps = str_bytes  
                    elseif not stream_info.pps and nalu_type == 8 then  
                        stream_info.pps = str_bytes  
                    end  
                end  
                  
            else -- second time running  
                --[[  
                if begin_with_nalu_hdr then  
                    -- drop AUD  
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)  
                    if nalu_type == 9 then  
                        return;  
                    end  
                end  
                ]]  
                  
                if stream_info.counter2 == 0 then  
                    -- write SPS and PPS to file header first  
                    if stream_info.sps then  
                        stream_info.file:write("\00\00\00\01")  
                        stream_info.file:write(stream_info.sps)  
                    else  
                        twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")  
                    end  
                    if stream_info.pps then  
                        stream_info.file:write("\00\00\00\01")  
                        stream_info.file:write(stream_info.pps)  
                    else  
                        twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")  
                    end  
                end  
              
                if begin_with_nalu_hdr then  
                    -- *.264 raw file format seams that every nalu start with 0x00000001  
                    stream_info.file:write("\00\00\00\01")  
                end  
                stream_info.file:write(str_bytes)  
                stream_info.counter2 = stream_info.counter2 + 1  
  
                -- update progress window's progress bar  
                if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then pgtw:update(stream_info.counter2 / stream_info.counter) end  
            end  
        end  
          
        local function comp_pack(p1, p2)  
            if math.abs(p2.seq - p1.seq) < 1000 then  
                return p1.seq < p2.seq  
            else -- seqeunce is over 2^16, so the small one is much big  
                return p1.seq > p2.seq  
            end  
        end  
          
        local function print_seq_error(stream_info, str)  
            if stream_info.seq_error_counter == nil then  
                stream_info.seq_error_counter = 0  
            end  
            stream_info.seq_error_counter = stream_info.seq_error_counter + 1  
            twappend(str .. " SeqErrCounts=" .. stream_info.seq_error_counter)  
        end  
          
        local function sort_and_write(stream_info, frame)  
            table.sort(frame.packs, comp_pack)  
              
            -- check if it is uncompleted frame  
            local completed = true  
            for i = 1, #frame.packs - 1, 1 do  
                local seq1 = frame.packs[i].seq  
                local seq2 = frame.packs[i+1].seq  
                if bit.band(seq1+1, 0xFFFF) ~= seq2 then  
                    print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq between " .. seq1 .. " and " .. seq2)  
                    completed = false  
                end  
            end  
              
            if not frame.packs[1].nalu_begin then  
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq before " .. frame.packs[1].seq)  
                completed = false  
            end  
              
            if not frame.packs[#frame.packs].nalu_end then  
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq after " .. frame.packs[#frame.packs].seq)  
                completed = false  
            end  
              
            if completed then  
                for i = 1, #frame.packs, 1 do  
                    real_write_to_file(stream_info, frame.packs[i].data, frame.packs[i].nalu_begin)  
                end  
            else  
                twappend("   We drop one uncompleted frame: rtp.timestamp=" .. frame.timestamp   
                         .. " nalu_type=" .. (frame.nalu_type and frame.nalu_type .."(" .. get_enum_name(nalu_type_list, frame.nalu_type) .. ")" or "unknown") )  
            end  
        end  
          
        local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, timestamp, seq, end_of_nalu)  
            if drop_uncompleted_frame and not first_run then -- sort and drop uncompleted frame  
                if stream_info.frame_buffer_size == nil then  
                    stream_info.frame_buffer_size = 0  
                end  
                  
                if timestamp < 0 or seq < 0 then  
                    twappend(" Invalid rtp timestamp (".. timestamp .. ") or seq (".. seq .. ")! We have to write it to file directly!")  
                    real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)  
                    return;  
                end  
                  
                -- check if this frame has existed  
                local p = stream_info.frame_buffer  
                while p do  
                    if p.timestamp == timestamp then  
                        break;  
                    else  
                        p = p.next  
                    end  
                end  
                  
                if p then  -- add this pack to frame  
                    if begin_with_nalu_hdr then  
                        p.nalu_type = bit.band(str_bytes:byte(1), 0x1F)  
                    end  
                    table.insert(p.packs, { ["seq"] = seq, ["data"] = str_bytes , ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu })  
                    return  
                end  
                  
                if stream_info.frame_buffer_size >= MAX_FRAME_NUM then  
                    -- write the most early frame to file  
                    sort_and_write(stream_info, stream_info.frame_buffer)  
                    stream_info.frame_buffer = stream_info.frame_buffer.next  
                    stream_info.frame_buffer_size = stream_info.frame_buffer_size - 1  
                end  
                  
                -- create a new frame buffer for new frame (timestamp)  
                local frame = {}  
                frame.timestamp = timestamp  
                if begin_with_nalu_hdr then  
                    frame.nalu_type = bit.band(str_bytes:byte(1), 0x1F)  
                end  
                frame.packs = {{ ["seq"] = seq, ["data"] = str_bytes, ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu}}  -- put pack to index 1 pos  
                frame.next = nil  
                  
                if stream_info.frame_buffer_size == 0 then  -- first frame  
                    stream_info.frame_buffer = frame  
                else  
                    p = stream_info.frame_buffer  
                    while p.next do  
                        p = p.next  
                    end  
                    p.next = frame  
                end  
                stream_info.frame_buffer_size = stream_info.frame_buffer_size + 1  
                  
            else -- write data direct to file without sort or frame drop  
                real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)  
            end  
        end  
          
        -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp  
        -- single NALU: one rtp payload contains only NALU  
        local function process_single_nalu(stream_info, h264, timestamp, seq)  
            --write_to_file(stream_info, h264:tvb()():string(), true, timestamp, seq, true)  
            write_to_file(stream_info, ((version_num >= 5.2) and h264:tvb():raw() or h264:tvb()():string()), true, timestamp, seq, true)  
        end  
          
        -- STAP-A: one rtp payload contains more than one NALUs  
        local function process_stap_a(stream_info, h264, timestamp, seq)  
            local h264tvb = h264:tvb()  
            local offset = 1  
            local i = 1  
            repeat  
                local size = h264tvb(offset,2):uint()  
                --write_to_file(stream_info, h264tvb(offset+2, size):string(), true, timestamp, i, true)  
                write_to_file(stream_info, ((version_num >= 5.2) and h264tvb:raw(offset+2, size) or h264tvb(offset+2, size):string()), true, timestamp, i, true)  
                offset = offset + 2 + size  
                i = i + 1  
            until offset >= h264tvb:len()  
        end  
          
        -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)  
        local function process_fu_a(stream_info, h264, timestamp, seq)  
            local h264tvb = h264:tvb()  
            local fu_idr = h264:get_index(0)  
            local fu_hdr = h264:get_index(1)  
            local end_of_nalu =  (bit.band(fu_hdr, 0x40) ~= 0)  
            if bit.band(fu_hdr, 0x80) ~= 0 then  
                -- start bit is set then save nalu header and body  
                local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))  
                --write_to_file(stream_info, string.char(nalu_hdr) .. h264tvb(2):string(), true, timestamp, seq, end_of_nalu)  
                write_to_file(stream_info, string.char(nalu_hdr) .. ((version_num >= 5.2) and h264tvb:raw(2) or h264tvb(2):string()), true, timestamp, seq, end_of_nalu)  
            else  
                -- start bit not set, just write part of nalu body  
                --write_to_file(stream_info, h264tvb(2):string(), false, timestamp, seq, end_of_nalu)  
                write_to_file(stream_info, ((version_num >= 5.2) and h264tvb:raw(2) or h264tvb(2):string()), false, timestamp, seq, end_of_nalu)  
            end  
        end  
          
        -- call this function if a packet contains h264 payload  
        function my_h264_tap.packet(pinfo,tvb)  
            if stream_infos == nil then  
                -- not triggered by button event, so do nothing.  
                return  
            end  
            local h264s = { f_h264() } -- using table because one packet may contains more than one RTP  
            local rtps = { f_rtp() }  
            local rtp_seqs = { f_rtp_seq() }  
            local rtp_timestamps = { f_rtp_timestamp() }  
              
            for i,h264_f in ipairs(h264s) do  
                if h264_f.len < 2 then  
                    return  
                end  
                --local h264 = h264_f.value   -- is ByteArray, it only works for 1.10.1 or early version  
                --local h264 = h264_f.range:bytes()   -- according to user-guide.chm, there is a bug of fieldInfo.value, so we have to convert it to TVB range first  
                local h264 = (version_num >= 5.2) and h264_f.range:bytes() or h264_f.value   
                local hdr_type = bit.band(h264:get_index(0), 0x1F)  
                local stream_info = get_stream_info(pinfo)  
--twappend(string.format("hdr_type=%X %d", hdr_type, hdr_type))   
--twappend("bytearray=" .. tostring(h264))  
--twappend("byterange=" .. tostring(h264_f.range):upper())  
                -- search the RTP timestamp and sequence of this H264  
                local timestamp = -1  
                local seq = -1  
                -- debug begin  
                local rtplen = -1  
                local preh264_foffset = -1  
                local prertp_foffset = -1  
                local preh264len = -1  
                -- debug end  
                if drop_uncompleted_frame then  
                    local matchx = 0;  
                    for j,rtp_f in ipairs(rtps) do  
                        if h264_f.offset > rtp_f.offset and h264_f.offset - rtp_f.offset <= 16 and h264_f.offset+h264_f.len <= rtp_f.offset+rtp_f.len then  
                        -- debug begin  
                        --if h264_f.offset > rtp_f.offset and h264_f.offset < rtp_f.offset+rtp_f.len then  
                    matchx = matchx + 1  
                    if matchx > 1 then  
                        print_seq_error(stream_info, "ASS seq=" .. seq .. " timestamp=" .. timestamp .. " rtplen=" .. rtplen .. " rtpoff=" .. prertp_foffset .. " h264off=" .. preh264_foffset .. " h264len=" .. preh264len .. "  |matched=" .. matchx .. "  New seq=" .. rtp_seqs[j].value .. " timestamp=" .. rtp_timestamps[j].value .. " rtplen=" .. rtp_f.len .." rtpoff=" .. rtp_f.offset .. " h264off=" .. h264_f.offset .. " h264.len=" .. h264_f.len)  
                    end        
                    -- debug end  
                            seq = rtp_seqs[j].value  
                            timestamp = rtp_timestamps[j].value  
                            -- debug begin  
                            rtplen = rtp_f.len  
                            preh264_foffset = h264_f.offset  
                            prertp_foffset = rtp_f.offset  
                            preh264len = h264_f.len  
                            -- debug end  
                            break  
                        end  
                    end  
  
                end  
                  
                if hdr_type > 0 and hdr_type < 24 then  
                    -- Single NALU  
                    process_single_nalu(stream_info, h264, timestamp, seq)  
                elseif hdr_type == 24 then  
                    -- STAP-A Single-time aggregation  
                    process_stap_a(stream_info, h264, timestamp, seq)  
                elseif hdr_type == 28 then  
                    -- FU-A  
                    process_fu_a(stream_info, h264, timestamp, seq)  
                else  
                    twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")  
                end  
            end  
        end  
          
        -- close all open files  
        local function close_all_files()  
            if stream_infos then  
                local no_streams = true  
                for id,stream in pairs(stream_infos) do  
                    if stream and stream.file then  
                        if stream.frame_buffer then  
                            local p = stream.frame_buffer  
                            while p do  
                                sort_and_write(stream, p)  
                                p = p.next  
                            end  
                            stream.frame_buffer = nil  
                            stream.frame_buffer_size = 0  
                        end  
                        stream.file:flush()  
                        stream.file:close()  
                        twappend("File [" .. stream.filename .. "] generated OK!\n")  
                        stream.file = nil  
                        no_streams = false  
                    end  
                end  
                  
                if no_streams then  
                    twappend("Not found any H.264 over RTP streams!")  
                end  
            end  
        end  
          
        function my_h264_tap.reset()  
            -- do nothing now  
        end  
          
        local function remove()  
            my_h264_tap:remove()  
        end  
          
        tw:set_atclose(remove)  
          
        local function export_h264(drop_frame)  
            pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")  
            first_run = true  
            drop_uncompleted_frame = drop_frame  
            stream_infos = {}  
            -- first time it runs for counting h.264 packets and finding SPS and PPS  
            retap_packets()  
            first_run = false  
            -- second time it runs for saving h264 data to target file.  
            retap_packets()  
            close_all_files()  
            -- close progress window  
            pgtw:close()  
            stream_infos = nil  
        end  
          
        local function export_all()  
            export_h264(false)  
        end  
          
        local function export_completed_frames()  
            export_h264(true)  
        end  
          
        tw:add_button("Export All", export_all)  
        tw:add_button("Export Completed Frames (Drop uncompleted frames)", export_completed_frames)  
    end  
      
    -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""  
    register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)  
end  

rtp_h265_export.lua
-- Dump RTP h.265 payload to raw h.265 file (*.265)
-- According to RFC7798 to dissector H265 payload of RTP to NALU, and write it
-- to from<sourceIp_sourcePort>to<dstIp_dstPort>.265 file. 
-- By now, we support Single NAL Unit Packets, Aggregation Packets (APs)
-- and Fragmentation Units (FUs) format RTP payload for H.265.
-- You can access this feature by menu "Tools"
-- Reference from Huang Qiangxiong (qiangxiong.huang@gmail.com)
-- Author: Yang Xing (hongch_911@126.com)
------------------------------------------------------------------------------------------------
do
    local version_str = string.match(_VERSION, "%d+[.]%d*")
    local version_num = version_str and tonumber(version_str) or 5.1
    local bit = (version_num >= 5.2) and require("bit32") or require("bit")

    function string.starts(String,Start)
        return string.sub(String,1,string.len(Start))==Start
     end
     
     function string.ends(String,End)
        return End=='' or string.sub(String,-string.len(End))==End
     end
    function get_temp_path()
        local tmp = nil
        if tmp == nil or tmp == '' then
            tmp = os.getenv('HOME')
            if tmp == nil or tmp == '' then
                tmp = os.getenv('USERPROFILE')
                if tmp == nil or tmp == '' then
                    tmp = persconffile_path('temp')
                else
                    tmp = tmp .. "/wireshark_temp"
                end
            else
                tmp = tmp .. "/wireshark_temp"
            end
        end
        return tmp
    end
    function get_ffmpeg_path()
        local tmp = nil
        if tmp == nil or tmp == '' then
            tmp = os.getenv('FFMPEG')
            if tmp == nil or tmp == '' then
                tmp = ""
            else
                if not string.ends(tmp, "/bin/") then
                    tmp = tmp .. "/bin/"
                end
            end
        end
        return tmp
    end

    -- for geting h265 data (the field's value is type of ByteArray)
    local f_h265 = Field.new("h265") 
    local f_rtp = Field.new("rtp") 
    local f_rtp_seq = Field.new("rtp.seq")
    local f_rtp_timestamp = Field.new("rtp.timestamp")

    local filter_string = nil

    -- menu action. When you click "Tools->Export H265 to file" will run this function
    local function export_h265_to_file()
        -- window for showing information
        local tw = TextWindow.new("Export H265 to File Info Win")
        local pgtw;
        
        -- add message to information window
        function twappend(str)
            tw:append(str)
            tw:append("\n")
        end
        
        local ffmpeg_path = get_ffmpeg_path()
        -- temp path
        local temp_path = get_temp_path()
        
        -- running first time for counting and finding sps+pps, second time for real saving
        local first_run = true 
        local writed_nalu_begin = false
        -- variable for storing rtp stream and dumping parameters
        local stream_infos = nil

        -- trigered by all h265 packats
        local list_filter = ''
        if filter_string == nil or filter_string == '' then
            list_filter = "h265"
        elseif string.find(filter_string,"h265")~=nil then
            list_filter = filter_string
        else
            list_filter = "h265 && "..filter_string
        end
        twappend("Listener filter: " .. list_filter .. "\n")
        local my_h265_tap = Listener.new("frame", list_filter)
        
        -- get rtp stream info by src and dst address
        function get_stream_info(pinfo)
            local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "_to_" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port)
            key = key:gsub(":", ".")
            local stream_info = stream_infos[key]
            if not stream_info then -- if not exists, create one
                stream_info = { }
                stream_info.filename = key.. ".265"
                -- stream_info.filepath = stream_info.filename
                -- stream_info.file,msg = io.open(stream_info.filename, "wb")
                if not Dir.exists(temp_path) then
                    Dir.make(temp_path)
                end
                stream_info.filepath = temp_path.."/"..stream_info.filename
                stream_info.file,msg = io.open(temp_path.."/"..stream_info.filename, "wb")
                if msg then
                    twappend("io.open "..stream_info.filepath..", error "..msg)
                end
                -- twappend("Output file path:" .. stream_info.filepath)
                stream_info.counter = 0 -- counting h265 total NALUs
                stream_info.counter2 = 0 -- for second time running
                stream_infos[key] = stream_info
                twappend("Ready to export H.265 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
                         .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " write to file:[" .. stream_info.filename .. "] ...")
            end
            return stream_info
        end
        
        -- write a NALU or part of NALU to file.
        local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, end_of_nalu)
            if first_run then
                stream_info.counter = stream_info.counter + 1
                
                if begin_with_nalu_hdr then
                    -- save VPS SPS PPS
                    local nalu_type = bit.rshift(bit.band(str_bytes:byte(0,1), 0x7e),1)
                    if not stream_info.vps and nalu_type == 32 then
                        stream_info.vps = str_bytes
                    elseif not stream_info.sps and nalu_type == 33 then
                        stream_info.sps = str_bytes
                    elseif not stream_info.pps and nalu_type == 34 then
                        stream_info.pps = str_bytes
                    end
                end
                
            else -- second time running

                if not writed_nalu_begin then
                    if begin_with_nalu_hdr then
                        writed_nalu_begin = true
                    else
                        return
                    end
                end
                
                if stream_info.counter2 == 0 then
                    local nalu_type = bit.rshift(bit.band(str_bytes:byte(0,1), 0x7e),1)
                    if nalu_type ~= 32 then
                        -- write VPS SPS and PPS to file header first
                        if stream_info.vps then
                            stream_info.file:write("\x00\x00\x00\x01")
                            stream_info.file:write(stream_info.vps)
                        else
                            twappend("Not found VPS for [" .. stream_info.filename .. "], it might not be played!")
                        end
                        if stream_info.sps then
                            stream_info.file:write("\x00\x00\x00\x01")
                            stream_info.file:write(stream_info.sps)
                        else
                            twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!")
                        end
                        if stream_info.pps then
                            stream_info.file:write("\x00\x00\x00\x01")
                            stream_info.file:write(stream_info.pps)
                        else
                            twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!")
                        end
                    end
                end
            
                if begin_with_nalu_hdr then
                    -- *.265 raw file format seams that every nalu start with 0x00000001
                    stream_info.file:write("\x00\x00\x00\x01")
                end
                stream_info.file:write(str_bytes)
                stream_info.counter2 = stream_info.counter2 + 1

                -- update progress window's progress bar
                if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then
                    pgtw:update(stream_info.counter2 / stream_info.counter)
                end
            end
        end
        
        -- read RFC3984 about single nalu/ap/fu H265 payload format of rtp
        -- single NALU: one rtp payload contains only NALU
        local function process_single_nalu(stream_info, h265)
            write_to_file(stream_info, h265:tvb():raw(), true, true)
        end
        
        -- APs: one rtp payload contains more than one NALUs
        local function process_ap(stream_info, h265)
            local h265tvb = h265:tvb()
            local offset = 2
            repeat
                local size = h265tvb(offset,2):uint()
                write_to_file(stream_info, h265tvb:raw(offset+2, size), true, true)
                offset = offset + 2 + size
            until offset >= h265tvb:len()
        end
        
        -- FUs: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
        local function process_fu(stream_info, h265)
            local h265tvb = h265:tvb()
            local start_of_nalu = (h265tvb:range(2, 1):bitfield(0,1) ~= 0)
            local end_of_nalu =  (h265tvb:range(2, 1):bitfield(1,1) ~= 0)
            if start_of_nalu then
                -- start bit is set then save nalu header and body
                local nalu_hdr_0 = bit.bor(bit.band(h265:get_index(0), 0x81), bit.lshift(bit.band(h265:get_index(2),0x3F), 1))
                local nalu_hdr_1 = h265:get_index(1)
                write_to_file(stream_info, string.char(nalu_hdr_0, nalu_hdr_1) .. h265tvb:raw(3), start_of_nalu, end_of_nalu)
            else
                -- start bit not set, just write part of nalu body
                write_to_file(stream_info, h265tvb:raw(3), start_of_nalu, end_of_nalu)
            end
        end
        
        -- call this function if a packet contains h265 payload
        function my_h265_tap.packet(pinfo,tvb)
            if stream_infos == nil then
                -- not triggered by button event, so do nothing.
                return
            end
            local h265s = { f_h265() } -- using table because one packet may contains more than one RTP
            
            for i,h265_f in ipairs(h265s) do
                if h265_f.len < 5 then
                    return
                end
                local h265 = h265_f.range:bytes() 
                local hdr_type = h265_f.range(0,1):bitfield(1,6)
                local stream_info = get_stream_info(pinfo)
                
                if hdr_type > 0 and hdr_type < 48 then
                    -- Single NALU
                    process_single_nalu(stream_info, h265)
                elseif hdr_type == 48 then
                    -- APs
                    process_ap(stream_info, h265)
                elseif hdr_type == 49 then
                    -- FUs
                    process_fu(stream_info, h265)
                else
                    twappend("Error: No.=" .. tostring(pinfo.number) .. " unknown type=" .. hdr_type .. " ; we only know 1-47(Single NALU),48(APs),49(FUs)!")
                end
            end
        end
        
        -- close all open files
        local function close_all_files()
            twappend("")
            local index = 0;
            if stream_infos then
                local no_streams = true
                for id,stream in pairs(stream_infos) do
                    if stream and stream.file then
                        stream.file:flush()
                        stream.file:close()
                        stream.file = nil
                        index = index + 1
                        twappend(index .. ": [" .. stream.filename .. "] generated OK!")
                        local anony_fuc = function ()
                            twappend("ffplay -x 640 -y 640 -autoexit "..stream.filename)
                            --copy_to_clipboard("ffplay -x 640 -y 640 -autoexit "..stream.filepath)
                            os.execute(ffmpeg_path.."ffplay -x 640 -y 640 -autoexit "..stream.filepath)
                        end
                        tw:add_button("Play "..index, anony_fuc)
                        no_streams = false
                    end
                end
                
                if no_streams then
                    twappend("Not found any H.265 over RTP streams!")
                else
                    tw:add_button("Browser", function () browser_open_data_file(temp_path) end)
                end
            end
        end
        
        function my_h265_tap.reset()
            -- do nothing now
        end
        
        tw:set_atclose(function ()
            my_h265_tap:remove()
            if Dir.exists(temp_path) then
                Dir.remove_all(temp_path)
            end
        end)
        
        local function export_h265()
            pgtw = ProgDlg.new("Export H265 to File Process", "Dumping H265 data to file...")
            first_run = true
            stream_infos = {}
            -- first time it runs for counting h.265 packets and finding SPS and PPS
            retap_packets()
            first_run = false
            -- second time it runs for saving h265 data to target file.
            retap_packets()
            close_all_files()
            -- close progress window
            pgtw:close()
            stream_infos = nil
        end
        
        tw:add_button("Export All", function ()
            export_h265()
        end)

        tw:add_button("Set Filter", function ()
            tw:close()
            dialog_menu()
        end)
    end

    local function dialog_func(str)
        filter_string = str
        export_h265_to_file()
    end

    function dialog_menu()
        new_dialog("Filter Dialog",dialog_func,"Filter")
    end

    local function dialog_default()
        filter_string = get_filter()
        export_h265_to_file()
    end
    
    -- Find this feature in menu "Tools"
    register_menu("Video/Export H265", dialog_default, MENU_TOOLS_UNSORTED)
end
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值