blob: f2d33111ffb5b90ed9a87c743e272f4c8685b812 [file] [log] [blame]
Benny Prijono0f856722008-02-01 14:59:19 +00001/* $Id$ */
2/*
3 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19#include <pjlib.h>
20#include <pjlib-util.h>
21#include <pjmedia.h>
22#include <pjmedia-codec.h>
23
24static const char *USAGE =
25"pcaputil [options] INPUT OUTPUT\n"
26"\n"
27" Convert captured RTP packets in PCAP file to WAV or stream it\n"
28" to remote destination.\n"
29"\n"
30"INPUT is the PCAP file name/path\n"
31"\n"
32"Options to filter packets from PCAP file:\n"
Benny Prijono41b15db2008-02-01 16:44:25 +000033"(you can always select the relevant packets from Wireshark of course!)\n"
Benny Prijono0f856722008-02-01 14:59:19 +000034" --src-ip=IP Only include packets from this source address\n"
35" --dst-ip=IP Only include packets destined to this address\n"
36" --src-port=port Only include packets from this source port number\n"
37" --dst-port=port Only include packets destined to this port number\n"
38"\n"
39"Options for saving to WAV file:\n"
40""
41" OUTPUT is WAV file: Set output to WAV file. The program will decode the\n"
42" RTP contents to the specified WAV file using codec\n"
43" that is available in PJMEDIA, and optionally decrypt\n"
44" the content using the SRTP crypto and keys below.\n"
45" --srtp-crypto=TAG, -c Set crypto to be used to decrypt SRTP packets. Valid\n"
46" tags are: \n"
47" AES_CM_128_HMAC_SHA1_80 \n"
48" AES_CM_128_HMAC_SHA1_32\n"
49" --srtp-key=KEY, -k Set the base64 key to decrypt SRTP packets.\n"
50"\n"
51" Example:\n"
52" pcaputil file.pcap output.wav\n"
53" pcaputil -c AES_CM_128_HMAC_SHA1_80 \\\n"
54" -k VLDONbsbGl2Puqy+0PV7w/uGfpSPKFevDpxGsxN3 \\\n"
55" file.pcap output.wav\n"
Benny Prijono41b15db2008-02-01 16:44:25 +000056"\n"
57"Remote streaming is not supported yet."
Benny Prijono0f856722008-02-01 14:59:19 +000058;
59
60static pj_caching_pool cp;
61static pj_pool_t *pool;
62static pjmedia_endpt *mept;
63static pj_pcap_file *pcap;
64static pjmedia_port *wav;
65static pjmedia_transport *srtp;
66
67static void err_exit(const char *title, pj_status_t status)
68{
69 if (status != PJ_SUCCESS) {
70 char errmsg[PJ_ERR_MSG_SIZE];
71 pj_strerror(status, errmsg, sizeof(errmsg));
72 printf("Error: %s: %s\n", title, errmsg);
73 } else {
74 printf("Error: %s\n", title);
75 }
76
77 if (srtp) pjmedia_transport_close(srtp);
78 if (wav) pjmedia_port_destroy(wav);
79 if (pcap) pj_pcap_close(pcap);
80 if (mept) pjmedia_endpt_destroy(mept);
81 if (pool) pj_pool_release(pool);
82 pj_caching_pool_destroy(&cp);
83 pj_shutdown();
84
85 exit(1);
86}
87
88#define T(op) do { \
89 status = op; \
90 if (status != PJ_SUCCESS) \
91 err_exit(#op, status); \
92 } while (0)
93
94
95static void read_rtp(pj_uint8_t *buf, pj_size_t bufsize,
96 pjmedia_rtp_hdr **rtp,
97 pj_uint8_t **payload,
98 unsigned *payload_size)
99{
100 pj_size_t sz = bufsize;
101 pj_status_t status;
102
103 status = pj_pcap_read_udp(pcap, buf, &sz);
104 if (status != PJ_SUCCESS)
105 err_exit("Error reading PCAP file", status);
106
107 if (sz < sizeof(pjmedia_rtp_hdr) + 10) {
108 err_exit("Invalid RTP packet", PJ_SUCCESS);
109 }
110
111 /* Decrypt SRTP */
112 if (srtp) {
113 int len = sz;
114 T(pjmedia_transport_srtp_decrypt_pkt(srtp, PJ_TRUE, buf, &len));
115 sz = len;
116 }
117
118 *rtp = (pjmedia_rtp_hdr*)buf;
119 *payload = (pj_uint8_t*) (buf + sizeof(pjmedia_rtp_hdr));
120 *payload_size = sz - sizeof(pjmedia_rtp_hdr);
121}
122
123static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
124 const pj_str_t *srtp_key)
125{
126 struct pkt
127 {
128 pj_uint8_t buffer[320];
129 pjmedia_rtp_hdr *rtp;
130 pj_uint8_t *payload;
131 unsigned payload_len;
132 } pkt0;
133 pjmedia_codec_mgr *cmgr;
134 pjmedia_codec_info *ci;
135 pjmedia_codec_param param;
136 pjmedia_codec *codec;
137 unsigned samples_per_frame;
138 pj_status_t status;
139
140 /* Initialize all codecs */
141#if PJMEDIA_HAS_SPEEX_CODEC
142 T( pjmedia_codec_speex_init(mept, 0, 10, 10) );
143#endif /* PJMEDIA_HAS_SPEEX_CODEC */
144
145#if PJMEDIA_HAS_ILBC_CODEC
146 T( pjmedia_codec_ilbc_init(mept, 30) );
147#endif /* PJMEDIA_HAS_ILBC_CODEC */
148
149#if PJMEDIA_HAS_GSM_CODEC
150 T( pjmedia_codec_gsm_init(mept) );
151#endif /* PJMEDIA_HAS_GSM_CODEC */
152
153#if PJMEDIA_HAS_G711_CODEC
154 T( pjmedia_codec_g711_init(mept) );
155#endif /* PJMEDIA_HAS_G711_CODEC */
156
157#if PJMEDIA_HAS_L16_CODEC
158 T( pjmedia_codec_l16_init(mept, 0) );
159#endif /* PJMEDIA_HAS_L16_CODEC */
160
161 /* Create SRTP transport is needed */
162 if (srtp_crypto->slen) {
163 pjmedia_srtp_crypto crypto;
164
165 pj_bzero(&crypto, sizeof(crypto));
166 crypto.key = *srtp_key;
167 crypto.name = *srtp_crypto;
168 T( pjmedia_transport_srtp_create(mept, NULL, NULL, &srtp) );
169 T( pjmedia_transport_srtp_start(srtp, &crypto, &crypto) );
170 }
171
172 /* Read first packet */
173 read_rtp(pkt0.buffer, sizeof(pkt0.buffer), &pkt0.rtp,
174 &pkt0.payload, &pkt0.payload_len);
175
176 cmgr = pjmedia_endpt_get_codec_mgr(mept);
177
178 /* Get codec info and param for the specified payload type */
179 T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
180 T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );
181
182 /* Alloc and init codec */
183 T( pjmedia_codec_mgr_alloc_codec(cmgr, ci, &codec) );
184 T( codec->op->init(codec, pool) );
185 T( codec->op->open(codec, &param) );
186
187 /* Open WAV file */
188 samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
189 T( pjmedia_wav_writer_port_create(pool, wav_filename,
190 ci->clock_rate, ci->channel_cnt,
191 samples_per_frame,
192 param.info.pcm_bits_per_sample, 0, 0,
193 &wav) );
194
195 /* Loop reading PCAP and writing WAV file */
196 for (;;) {
197 struct pkt pkt1;
198 pj_timestamp ts;
199 pjmedia_frame frames[16], pcm_frame;
200 short pcm[320];
201 unsigned i, frame_cnt;
202 long samples_cnt, ts_gap;
203
204 pj_assert(sizeof(pcm) >= samples_per_frame);
205
206 /* Parse first packet */
207 ts.u64 = 0;
208 frame_cnt = PJ_ARRAY_SIZE(frames);
209 T( codec->op->parse(codec, pkt0.payload, pkt0.payload_len, &ts,
210 &frame_cnt, frames) );
211
212 /* Decode and write to WAV file */
213 samples_cnt = 0;
214 for (i=0; i<frame_cnt; ++i) {
215 pjmedia_frame pcm_frame;
216
217 pcm_frame.buf = pcm;
218 pcm_frame.size = samples_per_frame * 2;
219
220 T( codec->op->decode(codec, &frames[i], pcm_frame.size, &pcm_frame) );
221 T( pjmedia_port_put_frame(wav, &pcm_frame) );
222 samples_cnt += samples_per_frame;
223 }
224
225 /* Read next packet */
226 read_rtp(pkt1.buffer, sizeof(pkt1.buffer), &pkt1.rtp,
227 &pkt1.payload, &pkt1.payload_len);
228
229 /* Fill in the gap (if any) between pkt0 and pkt1 */
230 ts_gap = pj_ntohl(pkt1.rtp->ts) - pj_ntohl(pkt0.rtp->ts) -
231 samples_cnt;
232 while (ts_gap >= (long)samples_per_frame) {
233
234 pcm_frame.buf = pcm;
235 pcm_frame.size = samples_per_frame * 2;
236
237 if (codec->op->recover) {
238 T( codec->op->recover(codec, pcm_frame.size, &pcm_frame) );
239 } else {
240 pj_bzero(pcm_frame.buf, pcm_frame.size);
241 }
242
243 T( pjmedia_port_put_frame(wav, &pcm_frame) );
244 ts_gap -= samples_per_frame;
245 }
246
247 /* Next */
248 pkt0 = pkt1;
249 pkt0.rtp = (pjmedia_rtp_hdr*)pkt0.buffer;
250 pkt0.payload = pkt0.buffer + (pkt1.payload - pkt1.buffer);
251 }
252}
253
254
255int main(int argc, char *argv[])
256{
257 pj_str_t input, output, wav, srtp_crypto, srtp_key;
258 pj_pcap_filter filter;
259 pj_status_t status;
260
261 enum { OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT };
262 struct pj_getopt_option long_options[] = {
263 { "srtp-crypto", 1, 0, 'c' },
264 { "srtp-key", 1, 0, 'k' },
265 { "src-ip", 1, 0, OPT_SRC_IP },
266 { "dst-ip", 1, 0, OPT_DST_IP },
267 { "src-port", 1, 0, OPT_SRC_PORT },
268 { "dst-port", 1, 0, OPT_DST_PORT },
269 { NULL, 0, 0, 0}
270 };
271 int c;
272 int option_index;
273 char key_bin[32];
274
275 srtp_crypto.slen = srtp_key.slen = 0;
276
277 pj_pcap_filter_default(&filter);
278 filter.link = PJ_PCAP_LINK_TYPE_ETH;
279 filter.proto = PJ_PCAP_PROTO_TYPE_UDP;
280
281 /* Parse arguments */
282 pj_optind = 0;
283 while((c=pj_getopt_long(argc,argv, "c:k:", long_options, &option_index))!=-1) {
284 switch (c) {
285 case 'c':
286 srtp_crypto = pj_str(pj_optarg);
287 break;
288 case 'k':
289 {
290 int key_len = sizeof(key_bin);
291 srtp_key = pj_str(pj_optarg);
292 if (pj_base64_decode(&srtp_key, (pj_uint8_t*)key_bin, &key_len)) {
293 puts("Error: invalid key");
294 return 1;
295 }
296 srtp_key.ptr = key_bin;
297 srtp_key.slen = key_len;
298 }
299 break;
300 case OPT_SRC_IP:
301 {
302 pj_str_t t = pj_str(pj_optarg);
303 pj_in_addr a = pj_inet_addr(&t);
304 filter.ip_src = a.s_addr;
305 }
306 break;
307 case OPT_DST_IP:
308 {
309 pj_str_t t = pj_str(pj_optarg);
310 pj_in_addr a = pj_inet_addr(&t);
311 filter.ip_dst = a.s_addr;
312 }
313 break;
314 case OPT_SRC_PORT:
315 filter.src_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
316 break;
317 case OPT_DST_PORT:
318 filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
319 break;
320 default:
321 puts("Error: invalid option");
322 return 1;
323 }
324 }
325
326 if (pj_optind != argc - 2) {
327 puts(USAGE);
328 return 1;
329 }
330
331 if (!(srtp_crypto.slen) != !(srtp_key.slen)) {
332 puts("Error: both SRTP crypto and key must be specified");
333 puts(USAGE);
334 return 1;
335 }
336
337 input = pj_str(argv[pj_optind]);
338 output = pj_str(argv[pj_optind+1]);
339 wav = pj_str(".wav");
340
341 T( pj_init() );
342
343 pj_caching_pool_init(&cp, NULL, 0);
344 pool = pj_pool_create(&cp.factory, "pcaputil", 1000, 1000, NULL);
345
346 T( pjlib_util_init() );
347 T( pjmedia_endpt_create(&cp.factory, NULL, 0, &mept) );
348
349 T( pj_pcap_open(pool, input.ptr, &pcap) );
350 T( pj_pcap_set_filter(pcap, &filter) );
351
352 if (pj_stristr(&output, &wav)) {
353 pcap2wav(output.ptr, &srtp_crypto, &srtp_key);
354 } else {
355 err_exit("invalid output file", PJ_EINVAL);
356 }
357
358 pjmedia_endpt_destroy(mept);
359 pj_pool_release(pool);
360 pj_caching_pool_destroy(&cp);
361 pj_shutdown();
362 return 0;
363}
364