• Wireshark Lua: 一个从RTP抓包里导出H.264 Payload,变成264裸码流文件(xxx.264)的Wireshark插件


    抓取一个包含H.264 Payload RTP包的SIP会话或RTSP会话后,用Wireshark的Play功能只能播放声音,不能播放视频。把RTP payload直接导出成文件后也是不能直接播放的,因为H.264 over RTP封包是符合RFC3984规范的,必须按照该规范把H.264数据取出来后,组成NALU,放到avi/mp4或裸码流文件等容器里后才能播放。

         本人写了一个wireshark插件,可以在打开包含H.264码流的抓包后,选菜单“Tools->Export H264 to file [HQX's plugins]”后,把抓包文件里的H.264码流自动导出到抓包文件所在目录(工作目录)里,名为from_<RTP流源ip>_<RTP流源端口>_to_<RTP流目的ip>_<RTP流目的端口>.264的264裸码流文件里。(文件格式为每个NALU前加0x00000001分隔符)。

          本程序可以识别RFC3984里提到的三种H.264 over RTP封装,分别是Single NALU(一个RTP含一个NALU)、STAP-A(一个RTP包含多个NALU)、FU-A(一个NALU分布到多个RTP包)三种封装格式,且会自动把SPS和PPS放到裸码流文件头部。

    Lua脚本如下:

      1 -- Dump RTP h.264 payload to raw h.264 file (*.264)
      2 -- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
      3 -- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
      4 -- STAP-A and FU-A format RTP payload for H.264.
      5 -- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
      6 -- Author: Huang Qiangxiong (qiangxiong.huang@gmail.com)
      7 -- change log:
      8 --      2012-03-13
      9 --          Just can play
     10 ------------------------------------------------------------------------------------------------
     11 do
     12     -- for geting h264 data (the field's value is type of ByteArray)
     13     local f_h264 = Field.new("h264") 
     14     -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function
     15     local function export_h264_to_file()
     16         -- window for showing information
     17         local tw = TextWindow.new("Export H264 to File Info Win")
     18         local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
     19         
     20         -- add message to information window
     21         function twappend(str)
     22             tw:append(str)
     23             tw:append("
    ")
     24         end
     25         
     26         -- running first time for counting and finding sps+pps, second time for real saving
     27         local first_run = true 
     28         -- variable for storing rtp stream and dumping parameters
     29         local stream_infos = {}
     30         -- trigered by all h264 packats
     31         local my_h264_tap = Listener.new(tap, "h264")
     32         
     33         -- get rtp stream info by src and dst address
     34         function get_stream_info(pinfo)
     35             local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "to" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port)
     36             local stream_info = stream_infos[key]
     37             if not stream_info then -- if not exists, create one
     38                 stream_info = { }
     39                 stream_info.filename = key.. ".264"
     40                 stream_info.file = io.open(stream_info.filename, "wb")
     41                 stream_info.counter = 0 -- counting h264 total NALUs
     42                 stream_info.counter2 = 0 -- for second time running
     43                 stream_infos[key] = stream_info
     44                 twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
     45                          .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:
             [" .. stream_info.filename .. "] ...
    ")
     46             end
     47             return stream_info
     48         end
     49         
     50         -- write a NALU or part of NALU to file.
     51         function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
     52             if first_run then
     53                 stream_info.counter = stream_info.counter + 1
     54                 
     55                 if begin_with_nalu_hdr then
     56                     -- save SPS or PPS
     57                     local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
     58                     if not stream_info.sps and nalu_type == 7 then
     59                         stream_info.sps = str_bytes
     60                     elseif not stream_info.pps and nalu_type == 8 then
     61                         stream_info.pps = str_bytes
     62                     end
     63                 end
     64                 
     65             else -- second time running
     66                 if stream_info.counter2 == 0 then
     67                     -- write SPS and PPS to file header first
     68                     if stream_info.sps then
     69                         stream_info.file:write("0001")
     70                         stream_info.file:write(stream_info.sps)
     71                     else
     72                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!
    ")
     73                     end
     74                     if stream_info.pps then
     75                         stream_info.file:write("0001")
     76                         stream_info.file:write(stream_info.pps)
     77                     else
     78                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!
    ")
     79                     end
     80                 end
     81             
     82                 if begin_with_nalu_hdr then
     83                     -- *.264 raw file format seams that every nalu start with 0x00000001
     84                     stream_info.file:write("0001")
     85                 end
     86                 stream_info.file:write(str_bytes)
     87                 stream_info.counter2 = stream_info.counter2 + 1
     88                 
     89                 if stream_info.counter2 == stream_info.counter then
     90                     stream_info.file:flush()
     91                     twappend("File [" .. stream_info.filename .. "] generated OK!
    ")
     92                 end
     93                 -- update progress window's progress bar
     94                 if stream_info.counter > 0 then pgtw:update(stream_info.counter2 / stream_info.counter) end
     95             end
     96         end
     97         
     98         -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp
     99         -- single NALU: one rtp payload contains only NALU
    100         function process_single_nalu(stream_info, h264)
    101             write_to_file(stream_info, h264:tvb()():string(), true)
    102         end
    103         
    104         -- STAP-A: one rtp payload contains more than one NALUs
    105         function process_stap_a(stream_info, h264)
    106             local h264tvb = h264:tvb()
    107             local offset = 1
    108             repeat
    109                 local size = h264tvb(offset,2):uint()
    110                 write_to_file(stream_info, h264tvb(offset+2, size):string(), true)
    111                 offset = offset + 2 + size
    112             until offset >= h264tvb:len()
    113         end
    114         
    115         -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
    116         function process_fu_a(stream_info, h264)
    117             local h264tvb = h264:tvb()
    118             local fu_idr = h264:get_index(0)
    119             local fu_hdr = h264:get_index(1)
    120             if bit.band(fu_hdr, 0x80) ~= 0 then
    121                 -- start bit is set then save nalu header and body
    122                 local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))
    123                 write_to_file(stream_info, string.char(nalu_hdr), true)
    124             else
    125                 -- start bit not set, just write part of nalu body
    126             end
    127             write_to_file(stream_info, h264tvb(2):string(), false)
    128         end
    129         
    130         -- call this function if a packet contains h264 payload
    131         function my_h264_tap.packet(pinfo,tvb)
    132             local h264s = { f_h264() } -- using table because one packet may contains more than one RTP
    133             for i,h264_f in ipairs(h264s) do
    134                 if h264_f.len < 2 then
    135                     return
    136                 end
    137                 local h264 = h264_f.value   -- is ByteArray
    138                 local hdr_type = bit.band(h264:get_index(0), 0x1F)
    139                 local stream_info = get_stream_info(pinfo)
    140                 
    141                 if hdr_type > 0 and hdr_type < 24 then
    142                     -- Single NALU
    143                     process_single_nalu(stream_info, h264)
    144                 elseif hdr_type == 24 then
    145                     -- STAP-A Single-time aggregation
    146                     process_stap_a(stream_info, h264)
    147                 elseif hdr_type == 28 then
    148                     -- FU-A
    149                     process_fu_a(stream_info, h264)
    150                 else
    151                     twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")
    152                 end
    153             end
    154         end
    155         
    156         -- close all open files
    157         function close_all_files()
    158             if stream_infos then
    159                 for id,stream in pairs(stream_infos) do
    160                     if stream and stream.file then
    161                         stream.file:close()
    162                         stream.file = nil
    163                     end
    164                 end
    165             end
    166         end
    167         
    168         function my_h264_tap.reset()
    169             -- do nothing now
    170         end
    171         
    172         function remove()
    173             close_all_files()
    174             my_h264_tap:remove()
    175         end
    176         
    177         tw:set_atclose(remove)
    178         
    179         -- first time it runs for counting h.264 packets and finding SPS and PPS
    180         retap_packets()
    181         first_run = false
    182         -- second time it runs for saving h264 data to target file.
    183         retap_packets()
    184         -- close progress window
    185         pgtw:close()
    186     end
    187     
    188     -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""
    189     register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)
    190 end

    把代码保存成h264_export.lua文件,放到wireshark安装目录下,然后修改wireshark安装目录下的init.lua文件:

    (1)若有disable_lua = true这样的行,则注释掉;

    (2)在文件末加入dofile("h264_export.lua")

    重新打开wirekshark就能使用该功能了。

    另外,264裸码流文件一般播放器不一定能播放,推荐使用ffmpeg的ffplay播放,或用ffmpeg转成通用文件格式播放。

    2014年升级版,支持排序、丢弃不完整帧,注意生成的文件from...在抓拍文件相同的目录:

      1 -- Dump RTP h.264 payload to raw h.264 file (*.264)
      2 -- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
      3 -- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
      4 -- STAP-A and FU-A format RTP payload for H.264.
      5 -- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
      6 -- Author: Huang Qiangxiong (qiangxiong.huang@gmail.com)
      7 -- change log:
      8 --      2012-03-13
      9 --          Just can play
     10 --      2012-04-28
     11 --          Add local to local function, and add [local bit = require("bit")] to prevent
     12 --          bit recleared in previous file.
     13 --      2013-07-11
     14 --          Add sort RTP and drop uncompleted frame option.
     15 --      2013-07-19
     16 --          Do nothing when tap is triggered other than button event.
     17 --          Add check for first or last packs lost of one frame.
     18 ------------------------------------------------------------------------------------------------
     19 do
     20     local bit = require("bit")
     21  
     22     -- for geting h264 data (the field's value is type of ByteArray)
     23     local f_h264 = Field.new("h264") 
     24     local f_rtp = Field.new("rtp") 
     25     local f_rtp_seq = Field.new("rtp.seq")
     26     local f_rtp_timestamp = Field.new("rtp.timestamp")
     27     local nalu_type_list = {
     28         [0] = "Unspecified",
     29         [1] = "P/B_slice",
     30         [2] = "P/B_A",
     31         [3] = "P/B_B",
     32         [4] = "P/B_C",
     33         [5] = "I_slice",
     34         [6] = "SEI",
     35         [7] = "SPS",
     36         [8] = "PPS",
     37         [9] = "AUD",
     38     }
     39     
     40     local function get_enum_name(list, index)
     41         local value = list[index]
     42         return value and value or "Unknown"
     43     end
     44     -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function
     45     local function export_h264_to_file()
     46         -- window for showing information
     47         local tw = TextWindow.new("Export H264 to File Info Win")
     48         --local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
     49         local pgtw;
     50         
     51         -- add message to information window
     52         function twappend(str)
     53             tw:append(str)
     54             tw:append("
    ")
     55         end
     56         
     57         -- running first time for counting and finding sps+pps, second time for real saving
     58         local first_run = true 
     59         -- variable for storing rtp stream and dumping parameters
     60         local stream_infos = nil
     61         -- drop_uncompleted_frame
     62         local drop_uncompleted_frame = false
     63         -- max frame buffer size
     64         local MAX_FRAME_NUM = 3
     65         -- trigered by all h264 packats
     66         local my_h264_tap = Listener.new(tap, "h264")
     67         
     68         -- get rtp stream info by src and dst address
     69         function get_stream_info(pinfo)
     70             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")
     71             local stream_info = stream_infos[key]
     72             if not stream_info then -- if not exists, create one
     73                 stream_info = { }
     74                 stream_info.filename = key.. ".264"
     75                 stream_info.file = io.open(stream_info.filename, "wb")
     76                 stream_info.counter = 0 -- counting h264 total NALUs
     77                 stream_info.counter2 = 0 -- for second time running
     78                 stream_infos[key] = stream_info
     79                 twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
     80                          .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:
             [" .. stream_info.filename .. "] ...
    ")
     81             end
     82             return stream_info
     83         end
     84         
     85         -- write a NALU or part of NALU to file.
     86         local function real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
     87             if first_run then
     88                 stream_info.counter = stream_info.counter + 1
     89                 
     90                 if begin_with_nalu_hdr then
     91                     -- save SPS or PPS
     92                     local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
     93                     if not stream_info.sps and nalu_type == 7 then
     94                         stream_info.sps = str_bytes
     95                     elseif not stream_info.pps and nalu_type == 8 then
     96                         stream_info.pps = str_bytes
     97                     end
     98                 end
     99                 
    100             else -- second time running
    101                 --[[
    102                 if begin_with_nalu_hdr then
    103                     -- drop AUD
    104                     local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
    105                     if nalu_type == 9 then
    106                         return;
    107                     end
    108                 end
    109                 ]]
    110                 
    111                 if stream_info.counter2 == 0 then
    112                     -- write SPS and PPS to file header first
    113                     if stream_info.sps then
    114                         stream_info.file:write("0001")
    115                         stream_info.file:write(stream_info.sps)
    116                     else
    117                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!
    ")
    118                     end
    119                     if stream_info.pps then
    120                         stream_info.file:write("0001")
    121                         stream_info.file:write(stream_info.pps)
    122                     else
    123                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!
    ")
    124                     end
    125                 end
    126             
    127                 if begin_with_nalu_hdr then
    128                     -- *.264 raw file format seams that every nalu start with 0x00000001
    129                     stream_info.file:write("0001")
    130                 end
    131                 stream_info.file:write(str_bytes)
    132                 stream_info.counter2 = stream_info.counter2 + 1
    133                 -- update progress window's progress bar
    134                 if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then pgtw:update(stream_info.counter2 / stream_info.counter) end
    135             end
    136         end
    137         
    138         local function comp_pack(p1, p2)
    139             if math.abs(p2.seq - p1.seq) < 1000 then
    140                 return p1.seq < p2.seq
    141             else -- seqeunce is over 2^16, so the small one is much big
    142                 return p1.seq > p2.seq
    143             end
    144         end
    145         
    146         local function print_seq_error(stream_info, str)
    147             if stream_info.seq_error_counter == nil then
    148                 stream_info.seq_error_counter = 0
    149             end
    150             stream_info.seq_error_counter = stream_info.seq_error_counter + 1
    151             twappend(str .. " SeqErrCounts=" .. stream_info.seq_error_counter)
    152         end
    153         
    154         local function sort_and_write(stream_info, frame)
    155             table.sort(frame.packs, comp_pack)
    156             
    157             -- check if it is uncompleted frame
    158             local completed = true
    159             for i = 1, #frame.packs - 1, 1 do
    160                 local seq1 = frame.packs[i].seq
    161                 local seq2 = frame.packs[i+1].seq
    162                 if bit.band(seq1+1, 0xFFFF) ~= seq2 then
    163                     print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq between " .. seq1 .. " and " .. seq2)
    164                     completed = false
    165                 end
    166             end
    167             
    168             if not frame.packs[1].nalu_begin then
    169                 print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq before " .. frame.packs[1].seq)
    170                 completed = false
    171             end
    172             
    173             if not frame.packs[#frame.packs].nalu_end then
    174                 print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq after " .. frame.packs[#frame.packs].seq)
    175                 completed = false
    176             end
    177             
    178             if completed then
    179                 for i = 1, #frame.packs, 1 do
    180                     real_write_to_file(stream_info, frame.packs[i].data, frame.packs[i].nalu_begin)
    181                 end
    182             else
    183                 twappend("   We drop one uncompleted frame: rtp.timestamp=" .. frame.timestamp 
    184                          .. " nalu_type=" .. frame.nalu_type .."(" .. get_enum_name(nalu_type_list, frame.nalu_type) .. ")")
    185             end
    186         end
    187         
    188         local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, timestamp, seq, end_of_nalu)
    189             if drop_uncompleted_frame and not first_run then -- sort and drop uncompleted frame
    190                 if stream_info.frame_buffer_size == nil then
    191                     stream_info.frame_buffer_size = 0
    192                 end
    193                 
    194                 if timestamp < 0 or seq < 0 then
    195                     twappend(" Invalid rtp timestamp (".. timestamp .. ") or seq (".. seq .. ")! We have to write it to file directly!")
    196                     real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
    197                     return;
    198                 end
    199                 
    200                 -- check if this frame has existed
    201                 local p = stream_info.frame_buffer
    202                 while p do
    203                     if p.timestamp == timestamp then
    204                         break;
    205                     else
    206                         p = p.next
    207                     end
    208                 end
    209                 
    210                 if p then  -- add this pack to frame
    211                     if begin_with_nalu_hdr then
    212                         p.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
    213                     end
    214                     table.insert(p.packs, { ["seq"] = seq, ["data"] = str_bytes , ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu })
    215                     return
    216                 end
    217                 
    218                 if stream_info.frame_buffer_size >= MAX_FRAME_NUM then
    219                     -- write the most early frame to file
    220                     sort_and_write(stream_info, stream_info.frame_buffer)
    221                     stream_info.frame_buffer = stream_info.frame_buffer.next
    222                     stream_info.frame_buffer_size = stream_info.frame_buffer_size - 1
    223                 end
    224                 
    225                 -- create a new frame buffer for new frame (timestamp)
    226                 local frame = {}
    227                 frame.timestamp = timestamp
    228                 if begin_with_nalu_hdr then
    229                     frame.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
    230                 end
    231                 frame.packs = {{ ["seq"] = seq, ["data"] = str_bytes, ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu}}  -- put pack to index 1 pos
    232                 frame.next = nil
    233                 
    234                 if stream_info.frame_buffer_size == 0 then  -- first frame
    235                     stream_info.frame_buffer = frame
    236                 else
    237                     p = stream_info.frame_buffer
    238                     while p.next do
    239                         p = p.next
    240                     end
    241                     p.next = frame
    242                 end
    243                 stream_info.frame_buffer_size = stream_info.frame_buffer_size + 1
    244                 
    245             else -- write data direct to file without sort or frame drop
    246                 real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
    247             end
    248         end
    249         
    250         -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp
    251         -- single NALU: one rtp payload contains only NALU
    252         local function process_single_nalu(stream_info, h264, timestamp, seq)
    253             write_to_file(stream_info, h264:tvb()():string(), true, timestamp, seq, true)
    254         end
    255         
    256         -- STAP-A: one rtp payload contains more than one NALUs
    257         local function process_stap_a(stream_info, h264, timestamp, seq)
    258             local h264tvb = h264:tvb()
    259             local offset = 1
    260             local i = 1
    261             repeat
    262                 local size = h264tvb(offset,2):uint()
    263                 write_to_file(stream_info, h264tvb(offset+2, size):string(), true, timestamp, i, true)
    264                 offset = offset + 2 + size
    265                 i = i + 1
    266             until offset >= h264tvb:len()
    267         end
    268         
    269         -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
    270         local function process_fu_a(stream_info, h264, timestamp, seq)
    271             local h264tvb = h264:tvb()
    272             local fu_idr = h264:get_index(0)
    273             local fu_hdr = h264:get_index(1)
    274             local end_of_nalu =  (bit.band(fu_hdr, 0x40) ~= 0)
    275             if bit.band(fu_hdr, 0x80) ~= 0 then
    276                 -- start bit is set then save nalu header and body
    277                 local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))
    278                 write_to_file(stream_info, string.char(nalu_hdr) .. h264tvb(2):string(), true, timestamp, seq, end_of_nalu)
    279             else
    280                 -- start bit not set, just write part of nalu body
    281                 write_to_file(stream_info, h264tvb(2):string(), false, timestamp, seq, end_of_nalu)
    282             end
    283         end
    284         
    285         -- call this function if a packet contains h264 payload
    286         function my_h264_tap.packet(pinfo,tvb)
    287             if stream_infos == nil then
    288                 -- not triggered by button event, so do nothing.
    289                 return
    290             end
    291             local h264s = { f_h264() } -- using table because one packet may contains more than one RTP
    292             local rtps = { f_rtp() }
    293             local rtp_seqs = { f_rtp_seq() }
    294             local rtp_timestamps = { f_rtp_timestamp() }
    295             
    296             for i,h264_f in ipairs(h264s) do
    297                 if h264_f.len < 2 then
    298                     return
    299                 end
    300                 local h264 = h264_f.value   -- is ByteArray
    301                 local hdr_type = bit.band(h264:get_index(0), 0x1F)
    302                 local stream_info = get_stream_info(pinfo)
    303                 
    304                 -- search the RTP timestamp and sequence of this H264
    305                 local timestamp = -1
    306                 local seq = -1
    307                 if drop_uncompleted_frame then
    308                     for j,rtp_f in ipairs(rtps) do
    309                         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
    310                             seq = rtp_seqs[j].value
    311                             timestamp = rtp_timestamps[j].value
    312                 break
    313                         end
    314                     end
    315                 end
    316                 
    317                 if hdr_type > 0 and hdr_type < 24 then
    318                     -- Single NALU
    319                     process_single_nalu(stream_info, h264, timestamp, seq)
    320                 elseif hdr_type == 24 then
    321                     -- STAP-A Single-time aggregation
    322                     process_stap_a(stream_info, h264, timestamp, seq)
    323                 elseif hdr_type == 28 then
    324                     -- FU-A
    325                     process_fu_a(stream_info, h264, timestamp, seq)
    326                 else
    327                     twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")
    328                 end
    329             end
    330         end
    331         
    332         -- close all open files
    333         local function close_all_files()
    334             if stream_infos then
    335                 local no_streams = true
    336                 for id,stream in pairs(stream_infos) do
    337                     if stream and stream.file then
    338                         if stream.frame_buffer then
    339                             local p = stream.frame_buffer
    340                             while p do
    341                                 sort_and_write(stream, p)
    342                                 p = p.next
    343                             end
    344                             stream.frame_buffer = nil
    345                             stream.frame_buffer_size = 0
    346                         end
    347                         stream.file:flush()
    348                         stream.file:close()
    349                         twappend("File [" .. stream.filename .. "] generated OK!
    ")
    350                         stream.file = nil
    351                         no_streams = false
    352                     end
    353                 end
    354                 
    355                 if no_streams then
    356                     twappend("Not found any H.264 over RTP streams!")
    357                 end
    358             end
    359         end
    360         
    361         function my_h264_tap.reset()
    362             -- do nothing now
    363         end
    364         
    365         local function remove()
    366             my_h264_tap:remove()
    367         end
    368         
    369         tw:set_atclose(remove)
    370         
    371         local function export_h264(drop_frame)
    372             pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
    373             first_run = true
    374             drop_uncompleted_frame = drop_frame
    375             stream_infos = {}
    376             -- first time it runs for counting h.264 packets and finding SPS and PPS
    377             retap_packets()
    378             first_run = false
    379             -- second time it runs for saving h264 data to target file.
    380             retap_packets()
    381             close_all_files()
    382             -- close progress window
    383             pgtw:close()
    384             stream_infos = nil
    385         end
    386         
    387         local function export_all()
    388             export_h264(false)
    389         end
    390         
    391         local function export_completed_frames()
    392             export_h264(true)
    393         end
    394         
    395         tw:add_button("Export All", export_all)
    396         tw:add_button("Export Completed Frames (Drop uncompleted frames)", export_completed_frames)
    397     end
    398     
    399     -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""
    400     register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)
    401 end

    2015年升级版:

      1 -- Dump RTP h.264 payload to raw h.264 file (*.264)
      2 -- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
      3 -- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
      4 -- STAP-A and FU-A format RTP payload for H.264.
      5 -- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
      6 -- Author: Huang Qiangxiong (qiangxiong.huang@gmail.com)
      7 -- change log:
      8 --      2012-03-13
      9 --          Just can play
     10 --      2012-04-28
     11 --          Add local to local function, and add [local bit = require("bit")] to prevent
     12 --          bit recleared in previous file.
     13 --      2013-07-11
     14 --          Add sort RTP and drop uncompleted frame option.
     15 --      2013-07-19
     16 --          Do nothing when tap is triggered other than button event.
     17 --          Add check for first or last packs lost of one frame.
     18 --      2014-10-23
     19 --          Fixed bug about print a frame.nalu_type error.
     20 --      2014-11-07
     21 --          Add support for Lua 5.2(>1.10.1) and 5.1(<=1.10.1). 
     22 --          Change range:string() to range:raw().
     23 --          Change h264_f.value to h264_f.range:bytes() because of wireshark lua bug.
     24 --      2015-06-03
     25 --          Fixed bug that if ipv6 address is using the file will not generated.(we replace ':' to '.')
     26 ------------------------------------------------------------------------------------------------
     27 do
     28     --local bit = require("bit") -- only work before 1.10.1
     29     --local bit = require("bit32") -- only work after 1.10.1 (only support in Lua 5.2)
     30     local version_str = string.match(_VERSION, "%d+[.]%d*")
     31     local version_num = version_str and tonumber(version_str) or 5.1
     32     local bit = (version_num >= 5.2) and require("bit32") or require("bit")
     33  
     34     -- for geting h264 data (the field's value is type of ByteArray)
     35     local f_h264 = Field.new("h264") 
     36     local f_rtp = Field.new("rtp") 
     37     local f_rtp_seq = Field.new("rtp.seq")
     38     local f_rtp_timestamp = Field.new("rtp.timestamp")
     39     local nalu_type_list = {
     40         [0] = "Unspecified",
     41         [1] = "P/B_slice",
     42         [2] = "P/B_A",
     43         [3] = "P/B_B",
     44         [4] = "P/B_C",
     45         [5] = "I_slice",
     46         [6] = "SEI",
     47         [7] = "SPS",
     48         [8] = "PPS",
     49         [9] = "AUD",
     50     }
     51     
     52     local function get_enum_name(list, index)
     53         local value = list[index]
     54         return value and value or "Unknown"
     55     end
     56     -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function
     57     local function export_h264_to_file()
     58         -- window for showing information
     59         local tw = TextWindow.new("Export H264 to File Info Win")
     60         --local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
     61         local pgtw;
     62         
     63         -- add message to information window
     64         function twappend(str)
     65             tw:append(str)
     66             tw:append("
    ")
     67         end
     68         
     69         -- running first time for counting and finding sps+pps, second time for real saving
     70         local first_run = true 
     71         -- variable for storing rtp stream and dumping parameters
     72         local stream_infos = nil
     73         -- drop_uncompleted_frame
     74         local drop_uncompleted_frame = false
     75         -- max frame buffer size
     76         local MAX_FRAME_NUM = 3
     77         -- trigered by all h264 packats
     78         local my_h264_tap = Listener.new(tap, "h264")
     79         
     80         -- get rtp stream info by src and dst address
     81         function get_stream_info(pinfo)
     82             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")
     83             key = key:gsub(":", ".")
     84             local stream_info = stream_infos[key]
     85             if not stream_info then -- if not exists, create one
     86                 stream_info = { }
     87                 stream_info.filename = key.. ".264"
     88                 stream_info.file = io.open(stream_info.filename, "wb")
     89                 stream_info.counter = 0 -- counting h264 total NALUs
     90                 stream_info.counter2 = 0 -- for second time running
     91                 stream_infos[key] = stream_info
     92                 twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
     93                          .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:
             [" .. stream_info.filename .. "] ...
    ")
     94             end
     95             return stream_info
     96         end
     97         
     98         -- write a NALU or part of NALU to file.
     99         local function real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
    100             if first_run then
    101                 stream_info.counter = stream_info.counter + 1
    102                 
    103                 if begin_with_nalu_hdr then
    104                     -- save SPS or PPS
    105                     local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
    106                     if not stream_info.sps and nalu_type == 7 then
    107                         stream_info.sps = str_bytes
    108                     elseif not stream_info.pps and nalu_type == 8 then
    109                         stream_info.pps = str_bytes
    110                     end
    111                 end
    112                 
    113             else -- second time running
    114                 --[[
    115                 if begin_with_nalu_hdr then
    116                     -- drop AUD
    117                     local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
    118                     if nalu_type == 9 then
    119                         return;
    120                     end
    121                 end
    122                 ]]
    123                 
    124                 if stream_info.counter2 == 0 then
    125                     -- write SPS and PPS to file header first
    126                     if stream_info.sps then
    127                         stream_info.file:write("0001")
    128                         stream_info.file:write(stream_info.sps)
    129                     else
    130                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!
    ")
    131                     end
    132                     if stream_info.pps then
    133                         stream_info.file:write("0001")
    134                         stream_info.file:write(stream_info.pps)
    135                     else
    136                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!
    ")
    137                     end
    138                 end
    139             
    140                 if begin_with_nalu_hdr then
    141                     -- *.264 raw file format seams that every nalu start with 0x00000001
    142                     stream_info.file:write("0001")
    143                 end
    144                 stream_info.file:write(str_bytes)
    145                 stream_info.counter2 = stream_info.counter2 + 1
    146                 -- update progress window's progress bar
    147                 if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then pgtw:update(stream_info.counter2 / stream_info.counter) end
    148             end
    149         end
    150         
    151         local function comp_pack(p1, p2)
    152             if math.abs(p2.seq - p1.seq) < 1000 then
    153                 return p1.seq < p2.seq
    154             else -- seqeunce is over 2^16, so the small one is much big
    155                 return p1.seq > p2.seq
    156             end
    157         end
    158         
    159         local function print_seq_error(stream_info, str)
    160             if stream_info.seq_error_counter == nil then
    161                 stream_info.seq_error_counter = 0
    162             end
    163             stream_info.seq_error_counter = stream_info.seq_error_counter + 1
    164             twappend(str .. " SeqErrCounts=" .. stream_info.seq_error_counter)
    165         end
    166         
    167         local function sort_and_write(stream_info, frame)
    168             table.sort(frame.packs, comp_pack)
    169             
    170             -- check if it is uncompleted frame
    171             local completed = true
    172             for i = 1, #frame.packs - 1, 1 do
    173                 local seq1 = frame.packs[i].seq
    174                 local seq2 = frame.packs[i+1].seq
    175                 if bit.band(seq1+1, 0xFFFF) ~= seq2 then
    176                     print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq between " .. seq1 .. " and " .. seq2)
    177                     completed = false
    178                 end
    179             end
    180             
    181             if not frame.packs[1].nalu_begin then
    182                 print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq before " .. frame.packs[1].seq)
    183                 completed = false
    184             end
    185             
    186             if not frame.packs[#frame.packs].nalu_end then
    187                 print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq after " .. frame.packs[#frame.packs].seq)
    188                 completed = false
    189             end
    190             
    191             if completed then
    192                 for i = 1, #frame.packs, 1 do
    193                     real_write_to_file(stream_info, frame.packs[i].data, frame.packs[i].nalu_begin)
    194                 end
    195             else
    196                 twappend("   We drop one uncompleted frame: rtp.timestamp=" .. frame.timestamp 
    197                          .. " nalu_type=" .. (frame.nalu_type and frame.nalu_type .."(" .. get_enum_name(nalu_type_list, frame.nalu_type) .. ")" or "unknown") )
    198             end
    199         end
    200         
    201         local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, timestamp, seq, end_of_nalu)
    202             if drop_uncompleted_frame and not first_run then -- sort and drop uncompleted frame
    203                 if stream_info.frame_buffer_size == nil then
    204                     stream_info.frame_buffer_size = 0
    205                 end
    206                 
    207                 if timestamp < 0 or seq < 0 then
    208                     twappend(" Invalid rtp timestamp (".. timestamp .. ") or seq (".. seq .. ")! We have to write it to file directly!")
    209                     real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
    210                     return;
    211                 end
    212                 
    213                 -- check if this frame has existed
    214                 local p = stream_info.frame_buffer
    215                 while p do
    216                     if p.timestamp == timestamp then
    217                         break;
    218                     else
    219                         p = p.next
    220                     end
    221                 end
    222                 
    223                 if p then  -- add this pack to frame
    224                     if begin_with_nalu_hdr then
    225                         p.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
    226                     end
    227                     table.insert(p.packs, { ["seq"] = seq, ["data"] = str_bytes , ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu })
    228                     return
    229                 end
    230                 
    231                 if stream_info.frame_buffer_size >= MAX_FRAME_NUM then
    232                     -- write the most early frame to file
    233                     sort_and_write(stream_info, stream_info.frame_buffer)
    234                     stream_info.frame_buffer = stream_info.frame_buffer.next
    235                     stream_info.frame_buffer_size = stream_info.frame_buffer_size - 1
    236                 end
    237                 
    238                 -- create a new frame buffer for new frame (timestamp)
    239                 local frame = {}
    240                 frame.timestamp = timestamp
    241                 if begin_with_nalu_hdr then
    242                     frame.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
    243                 end
    244                 frame.packs = {{ ["seq"] = seq, ["data"] = str_bytes, ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu}}  -- put pack to index 1 pos
    245                 frame.next = nil
    246                 
    247                 if stream_info.frame_buffer_size == 0 then  -- first frame
    248                     stream_info.frame_buffer = frame
    249                 else
    250                     p = stream_info.frame_buffer
    251                     while p.next do
    252                         p = p.next
    253                     end
    254                     p.next = frame
    255                 end
    256                 stream_info.frame_buffer_size = stream_info.frame_buffer_size + 1
    257                 
    258             else -- write data direct to file without sort or frame drop
    259                 real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
    260             end
    261         end
    262         
    263         -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp
    264         -- single NALU: one rtp payload contains only NALU
    265         local function process_single_nalu(stream_info, h264, timestamp, seq)
    266             --write_to_file(stream_info, h264:tvb()():string(), true, timestamp, seq, true)
    267             write_to_file(stream_info, ((version_num >= 5.2) and h264:tvb():raw() or h264:tvb()():string()), true, timestamp, seq, true)
    268         end
    269         
    270         -- STAP-A: one rtp payload contains more than one NALUs
    271         local function process_stap_a(stream_info, h264, timestamp, seq)
    272             local h264tvb = h264:tvb()
    273             local offset = 1
    274             local i = 1
    275             repeat
    276                 local size = h264tvb(offset,2):uint()
    277                 --write_to_file(stream_info, h264tvb(offset+2, size):string(), true, timestamp, i, true)
    278                 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)
    279                 offset = offset + 2 + size
    280                 i = i + 1
    281             until offset >= h264tvb:len()
    282         end
    283         
    284         -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
    285         local function process_fu_a(stream_info, h264, timestamp, seq)
    286             local h264tvb = h264:tvb()
    287             local fu_idr = h264:get_index(0)
    288             local fu_hdr = h264:get_index(1)
    289             local end_of_nalu =  (bit.band(fu_hdr, 0x40) ~= 0)
    290             if bit.band(fu_hdr, 0x80) ~= 0 then
    291                 -- start bit is set then save nalu header and body
    292                 local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))
    293                 --write_to_file(stream_info, string.char(nalu_hdr) .. h264tvb(2):string(), true, timestamp, seq, end_of_nalu)
    294                 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)
    295             else
    296                 -- start bit not set, just write part of nalu body
    297                 --write_to_file(stream_info, h264tvb(2):string(), false, timestamp, seq, end_of_nalu)
    298                 write_to_file(stream_info, ((version_num >= 5.2) and h264tvb:raw(2) or h264tvb(2):string()), false, timestamp, seq, end_of_nalu)
    299             end
    300         end
    301         
    302         -- call this function if a packet contains h264 payload
    303         function my_h264_tap.packet(pinfo,tvb)
    304             if stream_infos == nil then
    305                 -- not triggered by button event, so do nothing.
    306                 return
    307             end
    308             local h264s = { f_h264() } -- using table because one packet may contains more than one RTP
    309             local rtps = { f_rtp() }
    310             local rtp_seqs = { f_rtp_seq() }
    311             local rtp_timestamps = { f_rtp_timestamp() }
    312             
    313             for i,h264_f in ipairs(h264s) do
    314                 if h264_f.len < 2 then
    315                     return
    316                 end
    317                 --local h264 = h264_f.value   -- is ByteArray, it only works for 1.10.1 or early version
    318                 --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
    319                 local h264 = (version_num >= 5.2) and h264_f.range:bytes() or h264_f.value 
    320                 local hdr_type = bit.band(h264:get_index(0), 0x1F)
    321                 local stream_info = get_stream_info(pinfo)
    322 --twappend(string.format("hdr_type=%X %d", hdr_type, hdr_type)) 
    323 --twappend("bytearray=" .. tostring(h264))
    324 --twappend("byterange=" .. tostring(h264_f.range):upper())
    325                 -- search the RTP timestamp and sequence of this H264
    326                 local timestamp = -1
    327                 local seq = -1
    328                 -- debug begin
    329                 local rtplen = -1
    330                 local preh264_foffset = -1
    331                 local prertp_foffset = -1
    332                 local preh264len = -1
    333                 -- debug end
    334                 if drop_uncompleted_frame then
    335                     local matchx = 0;
    336                     for j,rtp_f in ipairs(rtps) do
    337                         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
    338                         -- debug begin
    339                         --if h264_f.offset > rtp_f.offset and h264_f.offset < rtp_f.offset+rtp_f.len then
    340                     matchx = matchx + 1
    341                     if matchx > 1 then
    342                         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)
    343                     end         
    344                     -- debug end
    345                             seq = rtp_seqs[j].value
    346                             timestamp = rtp_timestamps[j].value
    347                             -- debug begin
    348                             rtplen = rtp_f.len
    349                             preh264_foffset = h264_f.offset
    350                             prertp_foffset = rtp_f.offset
    351                             preh264len = h264_f.len
    352                             -- debug end
    353                             break
    354                         end
    355                     end
    356                 end
    357                 
    358                 if hdr_type > 0 and hdr_type < 24 then
    359                     -- Single NALU
    360                     process_single_nalu(stream_info, h264, timestamp, seq)
    361                 elseif hdr_type == 24 then
    362                     -- STAP-A Single-time aggregation
    363                     process_stap_a(stream_info, h264, timestamp, seq)
    364                 elseif hdr_type == 28 then
    365                     -- FU-A
    366                     process_fu_a(stream_info, h264, timestamp, seq)
    367                 else
    368                     twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")
    369                 end
    370             end
    371         end
    372         
    373         -- close all open files
    374         local function close_all_files()
    375             if stream_infos then
    376                 local no_streams = true
    377                 for id,stream in pairs(stream_infos) do
    378                     if stream and stream.file then
    379                         if stream.frame_buffer then
    380                             local p = stream.frame_buffer
    381                             while p do
    382                                 sort_and_write(stream, p)
    383                                 p = p.next
    384                             end
    385                             stream.frame_buffer = nil
    386                             stream.frame_buffer_size = 0
    387                         end
    388                         stream.file:flush()
    389                         stream.file:close()
    390                         twappend("File [" .. stream.filename .. "] generated OK!
    ")
    391                         stream.file = nil
    392                         no_streams = false
    393                     end
    394                 end
    395                 
    396                 if no_streams then
    397                     twappend("Not found any H.264 over RTP streams!")
    398                 end
    399             end
    400         end
    401         
    402         function my_h264_tap.reset()
    403             -- do nothing now
    404         end
    405         
    406         local function remove()
    407             my_h264_tap:remove()
    408         end
    409         
    410         tw:set_atclose(remove)
    411         
    412         local function export_h264(drop_frame)
    413             pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
    414             first_run = true
    415             drop_uncompleted_frame = drop_frame
    416             stream_infos = {}
    417             -- first time it runs for counting h.264 packets and finding SPS and PPS
    418             retap_packets()
    419             first_run = false
    420             -- second time it runs for saving h264 data to target file.
    421             retap_packets()
    422             close_all_files()
    423             -- close progress window
    424             pgtw:close()
    425             stream_infos = nil
    426         end
    427         
    428         local function export_all()
    429             export_h264(false)
    430         end
    431         
    432         local function export_completed_frames()
    433             export_h264(true)
    434         end
    435         
    436         tw:add_button("Export All", export_all)
    437         tw:add_button("Export Completed Frames (Drop uncompleted frames)", export_completed_frames)
    438     end
    439     
    440     -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""
    441     register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)
    442 end

    ——wsj 注:目前wireshark测试发现只有2015版可用

    转自:https://blog.csdn.net/jasonhwang/article/details/7359095

  • 相关阅读:
    跨境电商ERP中的自动化 5.平台订单自动打印面单
    小特工具箱新增功能:文档转换、代码转换和AI写诗词
    跨境电商ERP中的自动化 4.平台订单自动取运单号
    C#.NET 使用HttpWebRequest发送JSON
    .net core .net5 asp.net core mvc 与quartz.net 3.3.3 新版本调用方式
    .NET CORE .NET5 控制台程序使用EF连接MYSQL
    C#.NET 国密SM3withSM2签名与验签 和JAVA互通
    JAVA RSA 私钥签名 公钥验证签名 公钥验签
    java 读取控制台输入
    C#.NET RSA 私钥签名 公钥验证签名
  • 原文地址:https://www.cnblogs.com/liushui-sky/p/13671360.html
Copyright © 2020-2023  润新知