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