1. 程式人生 > >Wireshark Lua: 一個從RTP抓包裡匯出H.264 Payload,變成264裸碼流檔案(xxx.264)的Wireshark外掛

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指令碼如下:

-- Dump RTP h.264 payload to raw h.264 file (*.264)
-- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
-- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
-- STAP-A and FU-A format RTP payload for H.264.
-- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
-- Author: Huang Qiangxiong (
[email protected]
) -- change log: -- 2012-03-13 -- Just can play ------------------------------------------------------------------------------------------------ do -- for geting h264 data (the field's value is type of ByteArray) local f_h264 = Field.new("h264") -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function local function export_h264_to_file() -- window for showing information local tw = TextWindow.new("Export H264 to File Info Win") local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...") -- add message to information window function twappend(str) tw:append(str) tw:append("\n") end -- running first time for counting and finding sps+pps, second time for real saving local first_run = true -- variable for storing rtp stream and dumping parameters local stream_infos = {} -- trigered by all h264 packats local my_h264_tap = Listener.new(tap, "h264") -- get rtp stream info by src and dst address function get_stream_info(pinfo) local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "to" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port) local stream_info = stream_infos[key] if not stream_info then -- if not exists, create one stream_info = { } stream_info.filename = key.. ".264" stream_info.file = io.open(stream_info.filename, "wb") stream_info.counter = 0 -- counting h264 total NALUs stream_info.counter2 = 0 -- for second time running stream_infos[key] = stream_info twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:\n [" .. stream_info.filename .. "] ...\n") end return stream_info end -- write a NALU or part of NALU to file. function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr) if first_run then stream_info.counter = stream_info.counter + 1 if begin_with_nalu_hdr then -- save SPS or PPS local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F) if not stream_info.sps and nalu_type == 7 then stream_info.sps = str_bytes elseif not stream_info.pps and nalu_type == 8 then stream_info.pps = str_bytes end end else -- second time running if stream_info.counter2 == 0 then -- write SPS and PPS to file header first if stream_info.sps then stream_info.file:write("\00\00\00\01") stream_info.file:write(stream_info.sps) else twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n") end if stream_info.pps then stream_info.file:write("\00\00\00\01") stream_info.file:write(stream_info.pps) else twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n") end end if begin_with_nalu_hdr then -- *.264 raw file format seams that every nalu start with 0x00000001 stream_info.file:write("\00\00\00\01") end stream_info.file:write(str_bytes) stream_info.counter2 = stream_info.counter2 + 1 if stream_info.counter2 == stream_info.counter then stream_info.file:flush() twappend("File [" .. stream_info.filename .. "] generated OK!\n") end -- update progress window's progress bar if stream_info.counter > 0 then pgtw:update(stream_info.counter2 / stream_info.counter) end end end -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp -- single NALU: one rtp payload contains only NALU function process_single_nalu(stream_info, h264) write_to_file(stream_info, h264:tvb()():string(), true) end -- STAP-A: one rtp payload contains more than one NALUs function process_stap_a(stream_info, h264) local h264tvb = h264:tvb() local offset = 1 repeat local size = h264tvb(offset,2):uint() write_to_file(stream_info, h264tvb(offset+2, size):string(), true) offset = offset + 2 + size until offset >= h264tvb:len() end -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU) function process_fu_a(stream_info, h264) local h264tvb = h264:tvb() local fu_idr = h264:get_index(0) local fu_hdr = h264:get_index(1) if bit.band(fu_hdr, 0x80) ~= 0 then -- start bit is set then save nalu header and body local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F)) write_to_file(stream_info, string.char(nalu_hdr), true) else -- start bit not set, just write part of nalu body end write_to_file(stream_info, h264tvb(2):string(), false) end -- call this function if a packet contains h264 payload function my_h264_tap.packet(pinfo,tvb) local h264s = { f_h264() } -- using table because one packet may contains more than one RTP for i,h264_f in ipairs(h264s) do if h264_f.len < 2 then return end local h264 = h264_f.value -- is ByteArray local hdr_type = bit.band(h264:get_index(0), 0x1F) local stream_info = get_stream_info(pinfo) if hdr_type > 0 and hdr_type < 24 then -- Single NALU process_single_nalu(stream_info, h264) elseif hdr_type == 24 then -- STAP-A Single-time aggregation process_stap_a(stream_info, h264) elseif hdr_type == 28 then -- FU-A process_fu_a(stream_info, h264) else twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!") end end end -- close all open files function close_all_files() if stream_infos then for id,stream in pairs(stream_infos) do if stream and stream.file then stream.file:close() stream.file = nil end end end end function my_h264_tap.reset() -- do nothing now end function remove() close_all_files() my_h264_tap:remove() end tw:set_atclose(remove) -- first time it runs for counting h.264 packets and finding SPS and PPS retap_packets() first_run = false -- second time it runs for saving h264 data to target file. retap_packets() -- close progress window pgtw:close() end -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]"" register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED) end


把程式碼儲存成h264_export.lua檔案,放到wireshark安裝目錄下,然後修改wireshark安裝目錄下的init.lua檔案:

(1)若有disable_lua = true這樣的行,則註釋掉;

(2)在檔案末加入dofile("h264_export.lua")

重新開啟wirekshark就能使用該功能了。

另外,264裸碼流檔案一般播放器不一定能播放,推薦使用ffmpeg的ffplay播放,或用ffmpeg轉成通用檔案格式播放。

2014年升級版,支援排序、丟棄不完整幀,注意生成的檔案from...在抓拍檔案相同的目錄:

-- Dump RTP h.264 payload to raw h.264 file (*.264)
-- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
-- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
-- STAP-A and FU-A format RTP payload for H.264.
-- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
-- Author: Huang Qiangxiong ([email protected])
-- change log:
--      2012-03-13
--          Just can play
--      2012-04-28
--          Add local to local function, and add [local bit = require("bit")] to prevent
--          bit recleared in previous file.
--      2013-07-11
--          Add sort RTP and drop uncompleted frame option.
--      2013-07-19
--          Do nothing when tap is triggered other than button event.
--          Add check for first or last packs lost of one frame.
------------------------------------------------------------------------------------------------
do
    local bit = require("bit")

    -- for geting h264 data (the field's value is type of ByteArray)
    local f_h264 = Field.new("h264") 
    local f_rtp = Field.new("rtp") 
    local f_rtp_seq = Field.new("rtp.seq")
    local f_rtp_timestamp = Field.new("rtp.timestamp")
    local nalu_type_list = {
        [0] = "Unspecified",
        [1] = "P/B_slice",
        [2] = "P/B_A",
        [3] = "P/B_B",
        [4] = "P/B_C",
        [5] = "I_slice",
        [6] = "SEI",
        [7] = "SPS",
        [8] = "PPS",
        [9] = "AUD",
    }
    
    local function get_enum_name(list, index)
        local value = list[index]
        return value and value or "Unknown"
    end

    -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function
    local function export_h264_to_file()
        -- window for showing information
        local tw = TextWindow.new("Export H264 to File Info Win")
        --local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
        local pgtw;
        
        -- add message to information window
        function twappend(str)
            tw:append(str)
            tw:append("\n")
        end
        
        -- running first time for counting and finding sps+pps, second time for real saving
        local first_run = true 
        -- variable for storing rtp stream and dumping parameters
        local stream_infos = nil
        -- drop_uncompleted_frame
        local drop_uncompleted_frame = false
        -- max frame buffer size
        local MAX_FRAME_NUM = 3

        -- trigered by all h264 packats
        local my_h264_tap = Listener.new(tap, "h264")
        
        -- get rtp stream info by src and dst address
        function get_stream_info(pinfo)
            local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "to" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port) .. (drop_uncompleted_frame and "_dropped" or "_all")
            local stream_info = stream_infos[key]
            if not stream_info then -- if not exists, create one
                stream_info = { }
                stream_info.filename = key.. ".264"
                stream_info.file = io.open(stream_info.filename, "wb")
                stream_info.counter = 0 -- counting h264 total NALUs
                stream_info.counter2 = 0 -- for second time running
                stream_infos[key] = stream_info
                twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
                         .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:\n         [" .. stream_info.filename .. "] ...\n")
            end
            return stream_info
        end
        
        -- write a NALU or part of NALU to file.
        local function real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
            if first_run then
                stream_info.counter = stream_info.counter + 1
                
                if begin_with_nalu_hdr then
                    -- save SPS or PPS
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
                    if not stream_info.sps and nalu_type == 7 then
                        stream_info.sps = str_bytes
                    elseif not stream_info.pps and nalu_type == 8 then
                        stream_info.pps = str_bytes
                    end
                end
                
            else -- second time running
                --[[
                if begin_with_nalu_hdr then
                    -- drop AUD
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
                    if nalu_type == 9 then
                        return;
                    end
                end
                ]]
                
                if stream_info.counter2 == 0 then
                    -- write SPS and PPS to file header first
                    if stream_info.sps then
                        stream_info.file:write("\00\00\00\01")
                        stream_info.file:write(stream_info.sps)
                    else
                        twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")
                    end
                    if stream_info.pps then
                        stream_info.file:write("\00\00\00\01")
                        stream_info.file:write(stream_info.pps)
                    else
                        twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")
                    end
                end
            
                if begin_with_nalu_hdr then
                    -- *.264 raw file format seams that every nalu start with 0x00000001
                    stream_info.file:write("\00\00\00\01")
                end
                stream_info.file:write(str_bytes)
                stream_info.counter2 = stream_info.counter2 + 1

                -- update progress window's progress bar
                if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then pgtw:update(stream_info.counter2 / stream_info.counter) end
            end
        end
        
        local function comp_pack(p1, p2)
            if math.abs(p2.seq - p1.seq) < 1000 then
                return p1.seq < p2.seq
            else -- seqeunce is over 2^16, so the small one is much big
                return p1.seq > p2.seq
            end
        end
        
        local function print_seq_error(stream_info, str)
            if stream_info.seq_error_counter == nil then
                stream_info.seq_error_counter = 0
            end
            stream_info.seq_error_counter = stream_info.seq_error_counter + 1
            twappend(str .. " SeqErrCounts=" .. stream_info.seq_error_counter)
        end
        
        local function sort_and_write(stream_info, frame)
            table.sort(frame.packs, comp_pack)
            
            -- check if it is uncompleted frame
            local completed = true
            for i = 1, #frame.packs - 1, 1 do
                local seq1 = frame.packs[i].seq
                local seq2 = frame.packs[i+1].seq
                if bit.band(seq1+1, 0xFFFF) ~= seq2 then
                    print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq between " .. seq1 .. " and " .. seq2)
                    completed = false
                end
            end
            
            if not frame.packs[1].nalu_begin then
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq before " .. frame.packs[1].seq)
                completed = false
            end
            
            if not frame.packs[#frame.packs].nalu_end then
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq after " .. frame.packs[#frame.packs].seq)
                completed = false
            end
            
            if completed then
                for i = 1, #frame.packs, 1 do
                    real_write_to_file(stream_info, frame.packs[i].data, frame.packs[i].nalu_begin)
                end
            else
                twappend("   We drop one uncompleted frame: rtp.timestamp=" .. frame.timestamp 
                         .. " nalu_type=" .. frame.nalu_type .."(" .. get_enum_name(nalu_type_list, frame.nalu_type) .. ")")
            end
        end
        
        local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, timestamp, seq, end_of_nalu)
            if drop_uncompleted_frame and not first_run then -- sort and drop uncompleted frame
                if stream_info.frame_buffer_size == nil then
                    stream_info.frame_buffer_size = 0
                end
                
                if timestamp < 0 or seq < 0 then
                    twappend(" Invalid rtp timestamp (".. timestamp .. ") or seq (".. seq .. ")! We have to write it to file directly!")
                    real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
                    return;
                end
                
                -- check if this frame has existed
                local p = stream_info.frame_buffer
                while p do
                    if p.timestamp == timestamp then
                        break;
                    else
                        p = p.next
                    end
                end
                
                if p then  -- add this pack to frame
                    if begin_with_nalu_hdr then
                        p.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
                    end
                    table.insert(p.packs, { ["seq"] = seq, ["data"] = str_bytes , ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu })
                    return
                end
                
                if stream_info.frame_buffer_size >= MAX_FRAME_NUM then
                    -- write the most early frame to file
                    sort_and_write(stream_info, stream_info.frame_buffer)
                    stream_info.frame_buffer = stream_info.frame_buffer.next
                    stream_info.frame_buffer_size = stream_info.frame_buffer_size - 1
                end
                
                -- create a new frame buffer for new frame (timestamp)
                local frame = {}
                frame.timestamp = timestamp
                if begin_with_nalu_hdr then
                    frame.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
                end
                frame.packs = {{ ["seq"] = seq, ["data"] = str_bytes, ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu}}  -- put pack to index 1 pos
                frame.next = nil
                
                if stream_info.frame_buffer_size == 0 then  -- first frame
                    stream_info.frame_buffer = frame
                else
                    p = stream_info.frame_buffer
                    while p.next do
                        p = p.next
                    end
                    p.next = frame
                end
                stream_info.frame_buffer_size = stream_info.frame_buffer_size + 1
                
            else -- write data direct to file without sort or frame drop
                real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
            end
        end
        
        -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp
        -- single NALU: one rtp payload contains only NALU
        local function process_single_nalu(stream_info, h264, timestamp, seq)
            write_to_file(stream_info, h264:tvb()():string(), true, timestamp, seq, true)
        end
        
        -- STAP-A: one rtp payload contains more than one NALUs
        local function process_stap_a(stream_info, h264, timestamp, seq)
            local h264tvb = h264:tvb()
            local offset = 1
            local i = 1
            repeat
                local size = h264tvb(offset,2):uint()
                write_to_file(stream_info, h264tvb(offset+2, size):string(), true, timestamp, i, true)
                offset = offset + 2 + size
                i = i + 1
            until offset >= h264tvb:len()
        end
        
        -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
        local function process_fu_a(stream_info, h264, timestamp, seq)
            local h264tvb = h264:tvb()
            local fu_idr = h264:get_index(0)
            local fu_hdr = h264:get_index(1)
            local end_of_nalu =  (bit.band(fu_hdr, 0x40) ~= 0)
            if bit.band(fu_hdr, 0x80) ~= 0 then
                -- start bit is set then save nalu header and body
                local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))
                write_to_file(stream_info, string.char(nalu_hdr) .. h264tvb(2):string(), true, timestamp, seq, end_of_nalu)
            else
                -- start bit not set, just write part of nalu body
                write_to_file(stream_info, h264tvb(2):string(), false, timestamp, seq, end_of_nalu)
            end
        end
        
        -- call this function if a packet contains h264 payload
        function my_h264_tap.packet(pinfo,tvb)
            if stream_infos == nil then
                -- not triggered by button event, so do nothing.
                return
            end
            local h264s = { f_h264() } -- using table because one packet may contains more than one RTP
            local rtps = { f_rtp() }
            local rtp_seqs = { f_rtp_seq() }
            local rtp_timestamps = { f_rtp_timestamp() }
            
            for i,h264_f in ipairs(h264s) do
                if h264_f.len < 2 then
                    return
                end
                local h264 = h264_f.value   -- is ByteArray
                local hdr_type = bit.band(h264:get_index(0), 0x1F)
                local stream_info = get_stream_info(pinfo)
                
                -- search the RTP timestamp and sequence of this H264
                local timestamp = -1
                local seq = -1
                if drop_uncompleted_frame then
                    for j,rtp_f in ipairs(rtps) do
                        if h264_f.offset > rtp_f.offset and h264_f.offset - rtp_f.offset <= 16 and h264_f.offset+h264_f.len <= rtp_f.offset+rtp_f.len then
                            seq = rtp_seqs[j].value
                            timestamp = rtp_timestamps[j].value
			    break
                        end
                    end

                end
                
                if hdr_type > 0 and hdr_type < 24 then
                    -- Single NALU
                    process_single_nalu(stream_info, h264, timestamp, seq)
                elseif hdr_type == 24 then
                    -- STAP-A Single-time aggregation
                    process_stap_a(stream_info, h264, timestamp, seq)
                elseif hdr_type == 28 then
                    -- FU-A
                    process_fu_a(stream_info, h264, timestamp, seq)
                else
                    twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")
                end
            end
        end
        
        -- close all open files
        local function close_all_files()
            if stream_infos then
                local no_streams = true
                for id,stream in pairs(stream_infos) do
                    if stream and stream.file then
                        if stream.frame_buffer then
                            local p = stream.frame_buffer
                            while p do
                                sort_and_write(stream, p)
                                p = p.next
                            end
                            stream.frame_buffer = nil
                            stream.frame_buffer_size = 0
                        end
                        stream.file:flush()
                        stream.file:close()
                        twappend("File [" .. stream.filename .. "] generated OK!\n")
                        stream.file = nil
                        no_streams = false
                    end
                end
                
                if no_streams then
                    twappend("Not found any H.264 over RTP streams!")
                end
            end
        end
        
        function my_h264_tap.reset()
            -- do nothing now
        end
        
        local function remove()
            my_h264_tap:remove()
        end
        
        tw:set_atclose(remove)
        
        local function export_h264(drop_frame)
            pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
            first_run = true
            drop_uncompleted_frame = drop_frame
            stream_infos = {}
            -- first time it runs for counting h.264 packets and finding SPS and PPS
            retap_packets()
            first_run = false
            -- second time it runs for saving h264 data to target file.
            retap_packets()
            close_all_files()
            -- close progress window
            pgtw:close()
            stream_infos = nil
        end
        
        local function export_all()
            export_h264(false)
        end
        
        local function export_completed_frames()
            export_h264(true)
        end
        
        tw:add_button("Export All", export_all)
        tw:add_button("Export Completed Frames (Drop uncompleted frames)", export_completed_frames)
    end
    
    -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""
    register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)
end

2015年升級版:

-- Dump RTP h.264 payload to raw h.264 file (*.264)
-- According to RFC3984 to dissector H264 payload of RTP to NALU, and write it
-- to from<sourceIp_sourcePort>to<dstIp_dstPort>.264 file. By now, we support single NALU,
-- STAP-A and FU-A format RTP payload for H.264.
-- You can access this feature by menu "Tools->Export H264 to file [HQX's plugins]"
-- Author: Huang Qiangxiong ([email protected])
-- change log:
--      2012-03-13
--          Just can play
--      2012-04-28
--          Add local to local function, and add [local bit = require("bit")] to prevent
--          bit recleared in previous file.
--      2013-07-11
--          Add sort RTP and drop uncompleted frame option.
--      2013-07-19
--          Do nothing when tap is triggered other than button event.
--          Add check for first or last packs lost of one frame.
--      2014-10-23
--          Fixed bug about print a frame.nalu_type error.
--      2014-11-07
--          Add support for Lua 5.2(>1.10.1) and 5.1(<=1.10.1). 
--          Change range:string() to range:raw().
--          Change h264_f.value to h264_f.range:bytes() because of wireshark lua bug.
--      2015-06-03
--          Fixed bug that if ipv6 address is using the file will not generated.(we replace ':' to '.')
------------------------------------------------------------------------------------------------
do
    --local bit = require("bit") -- only work before 1.10.1
    --local bit = require("bit32") -- only work after 1.10.1 (only support in Lua 5.2)
	local version_str = string.match(_VERSION, "%d+[.]%d*")
	local version_num = version_str and tonumber(version_str) or 5.1
	local bit = (version_num >= 5.2) and require("bit32") or require("bit")

    -- for geting h264 data (the field's value is type of ByteArray)
    local f_h264 = Field.new("h264") 
    local f_rtp = Field.new("rtp") 
    local f_rtp_seq = Field.new("rtp.seq")
    local f_rtp_timestamp = Field.new("rtp.timestamp")
    local nalu_type_list = {
        [0] = "Unspecified",
        [1] = "P/B_slice",
        [2] = "P/B_A",
        [3] = "P/B_B",
        [4] = "P/B_C",
        [5] = "I_slice",
        [6] = "SEI",
        [7] = "SPS",
        [8] = "PPS",
        [9] = "AUD",
    }
    
    local function get_enum_name(list, index)
        local value = list[index]
        return value and value or "Unknown"
    end

    -- menu action. When you click "Tools->Export H264 to file [HQX's plugins]" will run this function
    local function export_h264_to_file()
        -- window for showing information
        local tw = TextWindow.new("Export H264 to File Info Win")
        --local pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
        local pgtw;
        
        -- add message to information window
        function twappend(str)
            tw:append(str)
            tw:append("\n")
        end
        
        -- running first time for counting and finding sps+pps, second time for real saving
        local first_run = true 
        -- variable for storing rtp stream and dumping parameters
        local stream_infos = nil
        -- drop_uncompleted_frame
        local drop_uncompleted_frame = false
        -- max frame buffer size
        local MAX_FRAME_NUM = 3

        -- trigered by all h264 packats
        local my_h264_tap = Listener.new(tap, "h264")
        
        -- get rtp stream info by src and dst address
        function get_stream_info(pinfo)
            local key = "from_" .. tostring(pinfo.src) .. "_" .. tostring(pinfo.src_port) .. "to" .. tostring(pinfo.dst) .. "_" .. tostring(pinfo.dst_port) .. (drop_uncompleted_frame and "_dropped" or "_all")
			key = key:gsub(":", ".")
            local stream_info = stream_infos[key]
            if not stream_info then -- if not exists, create one
                stream_info = { }
                stream_info.filename = key.. ".264"
                stream_info.file = io.open(stream_info.filename, "wb")
                stream_info.counter = 0 -- counting h264 total NALUs
                stream_info.counter2 = 0 -- for second time running
                stream_infos[key] = stream_info
                twappend("Ready to export H.264 data (RTP from " .. tostring(pinfo.src) .. ":" .. tostring(pinfo.src_port) 
                         .. " to " .. tostring(pinfo.dst) .. ":" .. tostring(pinfo.dst_port) .. " to file:\n         [" .. stream_info.filename .. "] ...\n")
            end
            return stream_info
        end
        
        -- write a NALU or part of NALU to file.
        local function real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
            if first_run then
                stream_info.counter = stream_info.counter + 1
                
                if begin_with_nalu_hdr then
                    -- save SPS or PPS
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
                    if not stream_info.sps and nalu_type == 7 then
                        stream_info.sps = str_bytes
                    elseif not stream_info.pps and nalu_type == 8 then
                        stream_info.pps = str_bytes
                    end
                end
                
            else -- second time running
                --[[
                if begin_with_nalu_hdr then
                    -- drop AUD
                    local nalu_type = bit.band(str_bytes:byte(0,1), 0x1F)
                    if nalu_type == 9 then
                        return;
                    end
                end
                ]]
                
                if stream_info.counter2 == 0 then
                    -- write SPS and PPS to file header first
                    if stream_info.sps then
                        stream_info.file:write("\00\00\00\01")
                        stream_info.file:write(stream_info.sps)
                    else
                        twappend("Not found SPS for [" .. stream_info.filename .. "], it might not be played!\n")
                    end
                    if stream_info.pps then
                        stream_info.file:write("\00\00\00\01")
                        stream_info.file:write(stream_info.pps)
                    else
                        twappend("Not found PPS for [" .. stream_info.filename .. "], it might not be played!\n")
                    end
                end
            
                if begin_with_nalu_hdr then
                    -- *.264 raw file format seams that every nalu start with 0x00000001
                    stream_info.file:write("\00\00\00\01")
                end
                stream_info.file:write(str_bytes)
                stream_info.counter2 = stream_info.counter2 + 1

                -- update progress window's progress bar
                if stream_info.counter > 0 and stream_info.counter2 < stream_info.counter then pgtw:update(stream_info.counter2 / stream_info.counter) end
            end
        end
        
        local function comp_pack(p1, p2)
            if math.abs(p2.seq - p1.seq) < 1000 then
                return p1.seq < p2.seq
            else -- seqeunce is over 2^16, so the small one is much big
                return p1.seq > p2.seq
            end
        end
        
        local function print_seq_error(stream_info, str)
            if stream_info.seq_error_counter == nil then
                stream_info.seq_error_counter = 0
            end
            stream_info.seq_error_counter = stream_info.seq_error_counter + 1
            twappend(str .. " SeqErrCounts=" .. stream_info.seq_error_counter)
        end
        
        local function sort_and_write(stream_info, frame)
            table.sort(frame.packs, comp_pack)
            
            -- check if it is uncompleted frame
            local completed = true
            for i = 1, #frame.packs - 1, 1 do
                local seq1 = frame.packs[i].seq
                local seq2 = frame.packs[i+1].seq
                if bit.band(seq1+1, 0xFFFF) ~= seq2 then
                    print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq between " .. seq1 .. " and " .. seq2)
                    completed = false
                end
            end
            
            if not frame.packs[1].nalu_begin then
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq before " .. frame.packs[1].seq)
                completed = false
            end
            
            if not frame.packs[#frame.packs].nalu_end then
                print_seq_error(stream_info, " RTP pack Lost: timestamp=" .. frame.timestamp .. " seq after " .. frame.packs[#frame.packs].seq)
                completed = false
            end
            
            if completed then
                for i = 1, #frame.packs, 1 do
                    real_write_to_file(stream_info, frame.packs[i].data, frame.packs[i].nalu_begin)
                end
            else
                twappend("   We drop one uncompleted frame: rtp.timestamp=" .. frame.timestamp 
                         .. " nalu_type=" .. (frame.nalu_type and frame.nalu_type .."(" .. get_enum_name(nalu_type_list, frame.nalu_type) .. ")" or "unknown") )
            end
        end
        
        local function write_to_file(stream_info, str_bytes, begin_with_nalu_hdr, timestamp, seq, end_of_nalu)
            if drop_uncompleted_frame and not first_run then -- sort and drop uncompleted frame
                if stream_info.frame_buffer_size == nil then
                    stream_info.frame_buffer_size = 0
                end
                
                if timestamp < 0 or seq < 0 then
                    twappend(" Invalid rtp timestamp (".. timestamp .. ") or seq (".. seq .. ")! We have to write it to file directly!")
                    real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
                    return;
                end
                
                -- check if this frame has existed
                local p = stream_info.frame_buffer
                while p do
                    if p.timestamp == timestamp then
                        break;
                    else
                        p = p.next
                    end
                end
                
                if p then  -- add this pack to frame
                    if begin_with_nalu_hdr then
                        p.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
                    end
                    table.insert(p.packs, { ["seq"] = seq, ["data"] = str_bytes , ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu })
                    return
                end
                
                if stream_info.frame_buffer_size >= MAX_FRAME_NUM then
                    -- write the most early frame to file
                    sort_and_write(stream_info, stream_info.frame_buffer)
                    stream_info.frame_buffer = stream_info.frame_buffer.next
                    stream_info.frame_buffer_size = stream_info.frame_buffer_size - 1
                end
                
                -- create a new frame buffer for new frame (timestamp)
                local frame = {}
                frame.timestamp = timestamp
                if begin_with_nalu_hdr then
                    frame.nalu_type = bit.band(str_bytes:byte(1), 0x1F)
                end
                frame.packs = {{ ["seq"] = seq, ["data"] = str_bytes, ["nalu_begin"] = begin_with_nalu_hdr, ["nalu_end"] = end_of_nalu}}  -- put pack to index 1 pos
                frame.next = nil
                
                if stream_info.frame_buffer_size == 0 then  -- first frame
                    stream_info.frame_buffer = frame
                else
                    p = stream_info.frame_buffer
                    while p.next do
                        p = p.next
                    end
                    p.next = frame
                end
                stream_info.frame_buffer_size = stream_info.frame_buffer_size + 1
                
            else -- write data direct to file without sort or frame drop
                real_write_to_file(stream_info, str_bytes, begin_with_nalu_hdr)
            end
        end
        
        -- read RFC3984 about single nalu/stap-a/fu-a H264 payload format of rtp
        -- single NALU: one rtp payload contains only NALU
        local function process_single_nalu(stream_info, h264, timestamp, seq)
            --write_to_file(stream_info, h264:tvb()():string(), true, timestamp, seq, true)
			write_to_file(stream_info, ((version_num >= 5.2) and h264:tvb():raw() or h264:tvb()():string()), true, timestamp, seq, true)
        end
        
        -- STAP-A: one rtp payload contains more than one NALUs
        local function process_stap_a(stream_info, h264, timestamp, seq)
            local h264tvb = h264:tvb()
            local offset = 1
            local i = 1
            repeat
                local size = h264tvb(offset,2):uint()
                --write_to_file(stream_info, h264tvb(offset+2, size):string(), true, timestamp, i, true)
				write_to_file(stream_info, ((version_num >= 5.2) and h264tvb:raw(offset+2, size) or h264tvb(offset+2, size):string()), true, timestamp, i, true)
                offset = offset + 2 + size
                i = i + 1
            until offset >= h264tvb:len()
        end
        
        -- FU-A: one rtp payload contains only one part of a NALU (might be begin, middle and end part of a NALU)
        local function process_fu_a(stream_info, h264, timestamp, seq)
            local h264tvb = h264:tvb()
            local fu_idr = h264:get_index(0)
            local fu_hdr = h264:get_index(1)
            local end_of_nalu =  (bit.band(fu_hdr, 0x40) ~= 0)
            if bit.band(fu_hdr, 0x80) ~= 0 then
                -- start bit is set then save nalu header and body
                local nalu_hdr = bit.bor(bit.band(fu_idr, 0xE0), bit.band(fu_hdr, 0x1F))
                --write_to_file(stream_info, string.char(nalu_hdr) .. h264tvb(2):string(), true, timestamp, seq, end_of_nalu)
				write_to_file(stream_info, string.char(nalu_hdr) .. ((version_num >= 5.2) and h264tvb:raw(2) or h264tvb(2):string()), true, timestamp, seq, end_of_nalu)
            else
                -- start bit not set, just write part of nalu body
                --write_to_file(stream_info, h264tvb(2):string(), false, timestamp, seq, end_of_nalu)
				write_to_file(stream_info, ((version_num >= 5.2) and h264tvb:raw(2) or h264tvb(2):string()), false, timestamp, seq, end_of_nalu)
            end
        end
        
        -- call this function if a packet contains h264 payload
        function my_h264_tap.packet(pinfo,tvb)
            if stream_infos == nil then
                -- not triggered by button event, so do nothing.
                return
            end
            local h264s = { f_h264() } -- using table because one packet may contains more than one RTP
            local rtps = { f_rtp() }
            local rtp_seqs = { f_rtp_seq() }
            local rtp_timestamps = { f_rtp_timestamp() }
            
            for i,h264_f in ipairs(h264s) do
                if h264_f.len < 2 then
                    return
                end
                --local h264 = h264_f.value   -- is ByteArray, it only works for 1.10.1 or early version
				--local h264 = h264_f.range:bytes()   -- according to user-guide.chm, there is a bug of fieldInfo.value, so we have to convert it to TVB range first
				local h264 = (version_num >= 5.2) and h264_f.range:bytes() or h264_f.value 
                local hdr_type = bit.band(h264:get_index(0), 0x1F)
                local stream_info = get_stream_info(pinfo)
--twappend(string.format("hdr_type=%X %d", hdr_type, hdr_type)) 
--twappend("bytearray=" .. tostring(h264))
--twappend("byterange=" .. tostring(h264_f.range):upper())
                -- search the RTP timestamp and sequence of this H264
                local timestamp = -1
                local seq = -1
				-- debug begin
				local rtplen = -1
				local preh264_foffset = -1
				local prertp_foffset = -1
				local preh264len = -1
				-- debug end
                if drop_uncompleted_frame then
					local matchx = 0;
                    for j,rtp_f in ipairs(rtps) do
                        if h264_f.offset > rtp_f.offset and h264_f.offset - rtp_f.offset <= 16 and h264_f.offset+h264_f.len <= rtp_f.offset+rtp_f.len then
						-- debug begin
						--if h264_f.offset > rtp_f.offset and h264_f.offset < rtp_f.offset+rtp_f.len then
					matchx = matchx + 1
					if matchx > 1 then
						print_seq_error(stream_info, "ASS seq=" .. seq .. " timestamp=" .. timestamp .. " rtplen=" .. rtplen .. " rtpoff=" .. prertp_foffset .. " h264off=" .. preh264_foffset .. " h264len=" .. preh264len .. "  |matched=" .. matchx .. "  New seq=" .. rtp_seqs[j].value .. " timestamp=" .. rtp_timestamps[j].value .. " rtplen=" .. rtp_f.len .." rtpoff=" .. rtp_f.offset .. " h264off=" .. h264_f.offset .. " h264.len=" .. h264_f.len)
					end		 
					-- debug end
                            seq = rtp_seqs[j].value
                            timestamp = rtp_timestamps[j].value
							-- debug begin
							rtplen = rtp_f.len
							preh264_foffset = h264_f.offset
							prertp_foffset = rtp_f.offset
							preh264len = h264_f.len
							-- debug end
							break
                        end
                    end

                end
                
                if hdr_type > 0 and hdr_type < 24 then
                    -- Single NALU
                    process_single_nalu(stream_info, h264, timestamp, seq)
                elseif hdr_type == 24 then
                    -- STAP-A Single-time aggregation
                    process_stap_a(stream_info, h264, timestamp, seq)
                elseif hdr_type == 28 then
                    -- FU-A
                    process_fu_a(stream_info, h264, timestamp, seq)
                else
                    twappend("Error: unknown type=" .. hdr_type .. " ; we only know 1-23(Single NALU),24(STAP-A),28(FU-A)!")
                end
            end
        end
        
        -- close all open files
        local function close_all_files()
            if stream_infos then
                local no_streams = true
                for id,stream in pairs(stream_infos) do
                    if stream and stream.file then
                        if stream.frame_buffer then
                            local p = stream.frame_buffer
                            while p do
                                sort_and_write(stream, p)
                                p = p.next
                            end
                            stream.frame_buffer = nil
                            stream.frame_buffer_size = 0
                        end
                        stream.file:flush()
                        stream.file:close()
                        twappend("File [" .. stream.filename .. "] generated OK!\n")
                        stream.file = nil
                        no_streams = false
                    end
                end
                
                if no_streams then
                    twappend("Not found any H.264 over RTP streams!")
                end
            end
        end
        
        function my_h264_tap.reset()
            -- do nothing now
        end
        
        local function remove()
            my_h264_tap:remove()
        end
        
        tw:set_atclose(remove)
        
        local function export_h264(drop_frame)
            pgtw = ProgDlg.new("Export H264 to File Process", "Dumping H264 data to file...")
            first_run = true
            drop_uncompleted_frame = drop_frame
            stream_infos = {}
            -- first time it runs for counting h.264 packets and finding SPS and PPS
            retap_packets()
            first_run = false
            -- second time it runs for saving h264 data to target file.
            retap_packets()
            close_all_files()
            -- close progress window
            pgtw:close()
            stream_infos = nil
        end
        
        local function export_all()
            export_h264(false)
        end
        
        local function export_completed_frames()
            export_h264(true)
        end
        
        tw:add_button("Export All", export_all)
        tw:add_button("Export Completed Frames (Drop uncompleted frames)", export_completed_frames)
    end
    
    -- Find this feature in menu "Tools->"Export H264 to file [HQX's plugins]""
    register_menu("Export H264 to file [HQX's plugins]", export_h264_to_file, MENU_TOOLS_UNSORTED)
end




相關推薦

Wireshark Lua: 一個RTP匯出H.264 Payload變成264檔案xxx.264Wireshark外掛

     抓取一個包含H.264 Payload RTP包的SIP會話或RTSP會話後,用Wireshark的Play功能只能播放聲音,不能播放視訊。把RTP payload直接匯出成檔案後也是不能直接播放的,因為H.264 over RTP封包是符合RFC3984規範的,

Wireshark對遠端Linux主機

對遠端Linux主機進行抓包 Wireshark是一款非常流行好用的抓包軟體 ,那麼我們能否在本地執行Wireshark來對遠端的Linux主機進行抓包呢,Wireshark是為我們提供了這項服務的,只需要為Linux安裝好rpcapd服務即可 01 Linux rpcapd服務的安裝 yum

util和sql的Date類區別該用哪一個

java.util.Date表示特定的瞬間,精確到毫秒---------------------------------------java.sql.Date一個包裝了毫秒值的瘦包裝器 (thin wrapper),它允許 JDBC 將毫秒值標識為 SQL DATE 值。毫秒

wireshark在win8.1上的問題解決

CMD.EXE4、點選右鍵,選擇【以管理員身份執行】5、選擇【是】,輸入【net start npf】6、系統提示【NetGroup Packet Filter Driver 服務已經啟動成功。】7、

wireshark lua: Dissector for RTP dynamic payload type如何編寫RTP Payload解析器

 本文介紹如何用wireshark lua編寫解析RTP Payload的解析器(dissector)。檢視wireshark RTP解析器原始碼可知,RTP裡有三個解析器table: 1. "rtp.pt" ——用於指定payload type值註冊解析器,一般payloa

一個jar有多個main指定執行某一個main

如果一個jar中含有多個主程式,而你沒有配置預設主程式,或者想要執行指定主程式,則可以通過如下命令執行:java -cp example03-1.0-SNAPSHOT.jar com.alan.HelloWorld-cp <目錄和 zip/jar 檔案的類搜尋路徑>

fiddler時很多是Tunnel to或者提示‘安全證書不能安裝成功’

1. 一開始我的fiddler轉包,一致是Tunnel  to。  別人的卻能轉包成功。 2、 網上折騰了一番終於搞定可以抓包成功了。 3、 將IE選項,安全證書,下面的個人證書,這塊全部刪除掉。 4、 重新安裝,fiddlercertmaker.exe  &nb

fiddler時很多是Tunnel to或者提示‘安全證書不能安裝成功’

nat root maker -c sig exe 證書 3.1 www 1. 一開始我的fiddler轉包,一致是Tunnel to。 別人的卻能轉包成功。 2、 網上折騰了一番終於搞定可以抓包成功了。 3、 將IE選項,安全證書,下面的個人證書,這塊全部刪除掉。 4

一串數字刪除k個數字使得新的那串數字最小

        最近接觸一道有趣的演算法題,意思是:給定一串數字,這串數字有可能大於long的最長長度,譬如12542670021,從這串數字中刪除k個數字,使得新數字串在所有可能性結果中最小,那麼應該刪除哪k個數字呢?     &nb

工具 charles 線上破解方法支援4.2.6版本

抓包工具 charles 線上破解方法 第一步:首先下載合適版本的charles進行安裝 第二步:點選立即使用進入破解連結 立即使用(支援4.1.3-4.2.6版本) 紅色圈出部分隨意填寫一個註冊名(示例aaa),選擇合適版本,點選生成 根據提示

Charles工具破解和https問題解決:中文亂碼Androidhttps亂碼

Charles 破解: 4.0.2 (Mac:/Application/Charles.app/Contents/Java)替換就破解了。 其實看這裡就夠了,下面總結下: 1.檢視電腦端ip.手機端編輯WiFi 繫結代理 手動 ip+埠。(手機wifi和電腦端網

MAC下最好用的工具charles簡單操作教程charles mac入門

ado 端口號 proc 通過 代理 關於 基本操作 sha 今天 Charles for Ma是 mac 平臺上一款非常強大的抓包神器,可以讓開發者監視查看所有連接互聯網的 HTTP 通信,包括請求,響應和 HTTP 頭信息等等,charles mac讓您的 Intern

LinkIt Smart 7688 構建 bootloader U-Boot

fig mil labs 技術分享 路徑 中文環境 問題 png 準備 操作系統:Ubuntu 16.04 LTS 以下操作均在普通用戶權限下執行:(註意不能含有中文路徑) 一.下載 U-Boot 源碼 創建工作目錄: $:mkdir LinkIt_Smart_mt7

[Golang] 零開始寫Socket Server4:將執行引數放入配置檔案XML/YAML

    為了將我們寫好的Server釋出到伺服器上,就要將我們的程式碼進行build打包,這樣如果以後想要修改一些程式碼的話,需要重新給程式碼進行編譯打包並上傳到伺服器上。     顯然,這麼做過於繁瑣。。。因此常見的做法都是將Server執行中

面試題:編寫一個函式來查詢字串陣列中的最長公共字首。 如果不存在公共字首返回空字串 ""。c++實現

例項說明 示例 1: 輸入: ["flower","flow","flight"] 輸出: "fl" 示例 2: 輸入: ["dog","racecar","car"] 輸出: "" 解釋: 輸入不存在公共字首。 說明: 所有輸入只包含小寫字母 a-z&

3.修改功能配置檔案package.xml

必要的ROS配置檔案之一的package.xml是一個包含功能包資訊的XML檔案,包括功能包名稱、作者、許可證和依賴功能包。 下面是對每個語句的說明。 ■ <?xml>         這是一個定義文件語法的語句,隨後的內容

AutoCAD2012入門到精通中文視訊教程 第18課 點等分及檢視縮放個人收藏

怎樣利用AutoCAD等分規則圖形? 然後可利用左側的繪圖工具繪製一個圓 怎樣利用AutoCAD等分規則圖形? 在選單欄中選擇“繪圖”工具,其下拉選單如下圖所示 怎樣利用AutoCAD等分規則圖形? 選擇“繪圖”下拉列表中的“點”,彈出下圖所示子選單 怎樣利用

AutoCAD2012入門到精通中文視訊教程 第17課 多段線,構造線,射線個人收藏

2然後在工作區中指定第一點作為起點 然後指定第二個點作為方向 3 如果沒有按右鍵退出 可以繼續以剛才設定好的第一個點為起點 新增任一條射線 4 點選右側刪除按鈕 框選這些射線 然後刪除 5 新建一條構造線 點選選單 構造線 如下圖所示 6 或者點選左側構造線

深度學習——零自己製作資料集到利用deepNN實現誇張人臉表情的實時監測tensorflow實現

一、背景介紹 這篇文章主要參考我的上一篇文章:深度學習(一)——deepNN模型實現攝像頭實時識別人臉表情(C++和python3.6混合程式設計)。由於上一篇文章的模型所採用的資料集為fer2013,前面也介紹過這個基於這個資料集的模型識別人臉表情的準確率大概在70%左右

【分散式系統】漫談分散式系統中的技術 —— IPC/RPCSOAWeb Service/REST 到 micro services微服務

1. 什麼是分散式系統 A distributed system is a system whose components are located on different networked computers, which then communicat