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