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("\n")
 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:\n         [" .. stream_info.filename .. "] ...\n")
 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("\00\00\00\01")
 70                         stream_info.file:write(stream_info.sps)
 71                     else
 72                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")
 73                     end
 74                     if stream_info.pps then
 75                         stream_info.file:write("\00\00\00\01")
 76                         stream_info.file:write(stream_info.pps)
 77                     else
 78                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")
 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("\00\00\00\01")
 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!\n")
 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("\n")
 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:\n         [" .. stream_info.filename .. "] ...\n")
 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("\00\00\00\01")
115                         stream_info.file:write(stream_info.sps)
116                     else
117                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")
118                     end
119                     if stream_info.pps then
120                         stream_info.file:write("\00\00\00\01")
121                         stream_info.file:write(stream_info.pps)
122                     else
123                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")
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("\00\00\00\01")
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!\n")
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("\n")
 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:\n         [" .. stream_info.filename .. "] ...\n")
 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("\00\00\00\01")
128                         stream_info.file:write(stream_info.sps)
129                     else
130                         twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")
131                     end
132                     if stream_info.pps then
133                         stream_info.file:write("\00\00\00\01")
134                         stream_info.file:write(stream_info.pps)
135                     else
136                         twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")
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("\00\00\00\01")
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!\n")
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