wireshark抓rtp包,提取出H265裸流数

调试rtsp收发流时,经常会需要抓包以确认是网络问题还是程序问题还是其它问题。通过tcpdump或者wireshark抓到的包通常是rtp流,保存为.pcap格式文件后中,可通过wireshark进行解析,得出h264裸流,并保存为文件。

1.wireshark配置

从gitee上可以直接下载的

WiresharkPlugin: The H265 H264 PS PCM AMR SILK plugin for Wireshark Lua (gitee.com)

1.1 下载rtp_h365_extractor.lua

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.2 rtp_h365_extractor.lua放在 Wireshark 安装目录中

1.3 修改init.lua配置文件

2. 流分析

2.1. 解码为RTP数据包

使用wireshark抓包工具抓取码流包(如下图),基于UDP传输。

选中其中一个数据包(包要选择正确,可根据protocol的类型选择),右键选择解码为(如下图)。

新增解码规则,选择解码为RTP流(如下图)。

解码后,可看到数据包解码成了RTP包(如下图)。

导出h265视频流

2.2. vlc播放视频流

相关推荐
dashizhi201537 分钟前
共享文件禁止拖动本地磁盘、共享文件禁止另存为、禁止打印共享文件、禁止复制共享文件的方法
运维·服务器·网络·安全·电脑
网教盟人才服务平台43 分钟前
AI 全面重塑网络攻防生态,智能安全进入深度对抗时代
网络·人工智能·安全
头铁的伦4 小时前
QNX 网络模型
linux·网络·车载系统
小贾要学习4 小时前
【Linux】TCP网络通信编程
linux·服务器·网络·c++·网络协议·tcp/ip
vortex54 小时前
构建可审计、可分层、可扩展的SSH身份管理体系
网络·ssh·php
Hello_Embed5 小时前
嵌入式上位机开发入门(十九):Socket 状态检测与断线重连
网络·单片机·网络协议·tcp/ip·嵌入式
cheems95275 小时前
[SpringMVC]Cookie 和Session 从无状态协议到状态保存实务
网络·http
Bruce_Liuxiaowei5 小时前
2026年4月第2周网络安全形势周报(3)
网络·安全·web安全
zl_dfq5 小时前
计算机网络 之 【IP协议】(IPv4报文格式、IP地址、公网IP VS 私网IP、路由VS转发)
网络·计算机网络·ip
大数据新鸟5 小时前
NIO 三大核心组件
服务器·网络·nio