blob: e5954105b104f66fac6391e96f11d7f29d040fcf [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
Benny Prijonoc4bb78b2008-02-02 09:12:13 +000060static struct app
61{
62 pj_caching_pool cp;
63 pj_pool_t *pool;
64 pjmedia_endpt *mept;
65 pj_pcap_file *pcap;
66 pjmedia_port *wav;
67 pjmedia_codec *codec;
68 unsigned pt;
69 pjmedia_transport *srtp;
70 pjmedia_rtp_session rtp_sess;
71 pj_bool_t rtp_sess_init;
72} app;
Benny Prijono0f856722008-02-01 14:59:19 +000073
74static void err_exit(const char *title, pj_status_t status)
75{
76 if (status != PJ_SUCCESS) {
77 char errmsg[PJ_ERR_MSG_SIZE];
78 pj_strerror(status, errmsg, sizeof(errmsg));
79 printf("Error: %s: %s\n", title, errmsg);
80 } else {
81 printf("Error: %s\n", title);
82 }
83
Benny Prijonoc4bb78b2008-02-02 09:12:13 +000084 if (app.srtp) pjmedia_transport_close(app.srtp);
85 if (app.wav) {
86 pj_ssize_t pos = pjmedia_wav_writer_port_get_pos(app.wav);
87 if (pos >= 0) {
88 unsigned msec;
89 msec = pos / 2 * 1000 / app.wav->info.clock_rate;
90 printf("Written: %dm:%02ds.%03d\n",
91 msec / 1000 / 60,
92 (msec / 1000) % 60,
93 msec % 1000);
94 }
95 pjmedia_port_destroy(app.wav);
96 }
97 if (app.pcap) pj_pcap_close(app.pcap);
98 if (app.codec) {
99 pjmedia_codec_mgr *cmgr;
100 app.codec->op->close(app.codec);
101 cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
102 pjmedia_codec_mgr_dealloc_codec(cmgr, app.codec);
103 }
104 if (app.mept) pjmedia_endpt_destroy(app.mept);
105 if (app.pool) pj_pool_release(app.pool);
106 pj_caching_pool_destroy(&app.cp);
Benny Prijono0f856722008-02-01 14:59:19 +0000107 pj_shutdown();
108
109 exit(1);
110}
111
112#define T(op) do { \
113 status = op; \
114 if (status != PJ_SUCCESS) \
115 err_exit(#op, status); \
116 } while (0)
117
118
119static void read_rtp(pj_uint8_t *buf, pj_size_t bufsize,
120 pjmedia_rtp_hdr **rtp,
121 pj_uint8_t **payload,
122 unsigned *payload_size)
123{
Benny Prijono0f856722008-02-01 14:59:19 +0000124 pj_status_t status;
125
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000126 /* Init RTP session */
127 if (!app.rtp_sess_init) {
128 T(pjmedia_rtp_session_init(&app.rtp_sess, 0, 0));
129 app.rtp_sess_init = PJ_TRUE;
Benny Prijono0f856722008-02-01 14:59:19 +0000130 }
131
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000132 /* Loop reading until we have a good RTP packet */
133 for (;;) {
134 pj_size_t sz = bufsize;
135 const pjmedia_rtp_hdr *r;
136 const void *p;
137 pjmedia_rtp_status seq_st;
Benny Prijono0f856722008-02-01 14:59:19 +0000138
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000139 status = pj_pcap_read_udp(app.pcap, NULL, buf, &sz);
140 if (status != PJ_SUCCESS)
141 err_exit("Error reading PCAP file", status);
142
143 /* Decode RTP packet to make sure that this is an RTP packet.
144 * We will decode it again to get the payload after we do
145 * SRTP decoding
146 */
147 status = pjmedia_rtp_decode_rtp(&app.rtp_sess, buf, sz, &r,
148 &p, payload_size);
149 if (status != PJ_SUCCESS) {
150 char errmsg[PJ_ERR_MSG_SIZE];
151 pj_strerror(status, errmsg, sizeof(errmsg));
152 printf("Not RTP packet, skipping packet: %s\n", errmsg);
153 continue;
154 }
155
156 /* Decrypt SRTP */
157 if (app.srtp) {
158 int len = sz;
159 status = pjmedia_transport_srtp_decrypt_pkt(app.srtp, PJ_TRUE,
160 buf, &len);
161 if (status != PJ_SUCCESS) {
162 char errmsg[PJ_ERR_MSG_SIZE];
163 pj_strerror(status, errmsg, sizeof(errmsg));
164 printf("SRTP packet decryption failed, skipping packet: %s\n",
165 errmsg);
166 continue;
167 }
168 sz = len;
169
170 /* Decode RTP packet again */
171 status = pjmedia_rtp_decode_rtp(&app.rtp_sess, buf, sz, &r,
172 &p, payload_size);
173 if (status != PJ_SUCCESS) {
174 char errmsg[PJ_ERR_MSG_SIZE];
175 pj_strerror(status, errmsg, sizeof(errmsg));
176 printf("Not RTP packet, skipping packet: %s\n", errmsg);
177 continue;
178 }
179 }
180
181 /* Update RTP session */
182 pjmedia_rtp_session_update(&app.rtp_sess, r, &seq_st);
183
184 /* Skip out-of-order packet */
185 if (seq_st.diff == 0) {
186 printf("Skipping out of order packet\n");
187 continue;
188 }
189
190 /* Skip if payload type is different */
191 if (r->pt != app.pt) {
192 printf("Skipping RTP packet with bad payload type\n");
193 continue;
194 }
195
196 /* Skip bad packet */
197 if (seq_st.status.flag.bad) {
198 printf("Skipping bad RTP\n");
199 continue;
200 }
201
202
203 *rtp = (pjmedia_rtp_hdr*)r;
204 *payload = (pj_uint8_t*)p;
205
206 /* We have good packet */
207 break;
208 }
Benny Prijono0f856722008-02-01 14:59:19 +0000209}
210
211static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
212 const pj_str_t *srtp_key)
213{
214 struct pkt
215 {
216 pj_uint8_t buffer[320];
217 pjmedia_rtp_hdr *rtp;
218 pj_uint8_t *payload;
219 unsigned payload_len;
220 } pkt0;
221 pjmedia_codec_mgr *cmgr;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000222 const pjmedia_codec_info *ci;
Benny Prijono0f856722008-02-01 14:59:19 +0000223 pjmedia_codec_param param;
Benny Prijono0f856722008-02-01 14:59:19 +0000224 unsigned samples_per_frame;
225 pj_status_t status;
226
227 /* Initialize all codecs */
228#if PJMEDIA_HAS_SPEEX_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000229 T( pjmedia_codec_speex_init(app.mept, 0, 10, 10) );
Benny Prijono0f856722008-02-01 14:59:19 +0000230#endif /* PJMEDIA_HAS_SPEEX_CODEC */
231
232#if PJMEDIA_HAS_ILBC_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000233 T( pjmedia_codec_ilbc_init(app.mept, 30) );
Benny Prijono0f856722008-02-01 14:59:19 +0000234#endif /* PJMEDIA_HAS_ILBC_CODEC */
235
236#if PJMEDIA_HAS_GSM_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000237 T( pjmedia_codec_gsm_init(app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000238#endif /* PJMEDIA_HAS_GSM_CODEC */
239
240#if PJMEDIA_HAS_G711_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000241 T( pjmedia_codec_g711_init(app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000242#endif /* PJMEDIA_HAS_G711_CODEC */
243
244#if PJMEDIA_HAS_L16_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000245 T( pjmedia_codec_l16_init(app.mept, 0) );
Benny Prijono0f856722008-02-01 14:59:19 +0000246#endif /* PJMEDIA_HAS_L16_CODEC */
247
248 /* Create SRTP transport is needed */
249 if (srtp_crypto->slen) {
250 pjmedia_srtp_crypto crypto;
251
252 pj_bzero(&crypto, sizeof(crypto));
253 crypto.key = *srtp_key;
254 crypto.name = *srtp_crypto;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000255 T( pjmedia_transport_srtp_create(app.mept, NULL, NULL, &app.srtp) );
256 T( pjmedia_transport_srtp_start(app.srtp, &crypto, &crypto) );
Benny Prijono0f856722008-02-01 14:59:19 +0000257 }
258
259 /* Read first packet */
260 read_rtp(pkt0.buffer, sizeof(pkt0.buffer), &pkt0.rtp,
261 &pkt0.payload, &pkt0.payload_len);
262
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000263 cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
Benny Prijono0f856722008-02-01 14:59:19 +0000264
265 /* Get codec info and param for the specified payload type */
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000266 app.pt = pkt0.rtp->pt;
Benny Prijono0f856722008-02-01 14:59:19 +0000267 T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
268 T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );
269
270 /* Alloc and init codec */
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000271 T( pjmedia_codec_mgr_alloc_codec(cmgr, ci, &app.codec) );
272 T( app.codec->op->init(app.codec, app.pool) );
273 T( app.codec->op->open(app.codec, &param) );
Benny Prijono0f856722008-02-01 14:59:19 +0000274
275 /* Open WAV file */
276 samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000277 T( pjmedia_wav_writer_port_create(app.pool, wav_filename,
Benny Prijono0f856722008-02-01 14:59:19 +0000278 ci->clock_rate, ci->channel_cnt,
279 samples_per_frame,
280 param.info.pcm_bits_per_sample, 0, 0,
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000281 &app.wav) );
Benny Prijono0f856722008-02-01 14:59:19 +0000282
283 /* Loop reading PCAP and writing WAV file */
284 for (;;) {
285 struct pkt pkt1;
286 pj_timestamp ts;
287 pjmedia_frame frames[16], pcm_frame;
288 short pcm[320];
289 unsigned i, frame_cnt;
290 long samples_cnt, ts_gap;
291
292 pj_assert(sizeof(pcm) >= samples_per_frame);
293
294 /* Parse first packet */
295 ts.u64 = 0;
296 frame_cnt = PJ_ARRAY_SIZE(frames);
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000297 T( app.codec->op->parse(app.codec, pkt0.payload, pkt0.payload_len,
298 &ts, &frame_cnt, frames) );
Benny Prijono0f856722008-02-01 14:59:19 +0000299
300 /* Decode and write to WAV file */
301 samples_cnt = 0;
302 for (i=0; i<frame_cnt; ++i) {
303 pjmedia_frame pcm_frame;
304
305 pcm_frame.buf = pcm;
306 pcm_frame.size = samples_per_frame * 2;
307
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000308 T( app.codec->op->decode(app.codec, &frames[i], pcm_frame.size,
309 &pcm_frame) );
310 T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000311 samples_cnt += samples_per_frame;
312 }
313
314 /* Read next packet */
315 read_rtp(pkt1.buffer, sizeof(pkt1.buffer), &pkt1.rtp,
316 &pkt1.payload, &pkt1.payload_len);
317
318 /* Fill in the gap (if any) between pkt0 and pkt1 */
319 ts_gap = pj_ntohl(pkt1.rtp->ts) - pj_ntohl(pkt0.rtp->ts) -
320 samples_cnt;
321 while (ts_gap >= (long)samples_per_frame) {
322
323 pcm_frame.buf = pcm;
324 pcm_frame.size = samples_per_frame * 2;
325
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000326 if (app.codec->op->recover) {
327 T( app.codec->op->recover(app.codec, pcm_frame.size,
328 &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000329 } else {
330 pj_bzero(pcm_frame.buf, pcm_frame.size);
331 }
332
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000333 T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000334 ts_gap -= samples_per_frame;
335 }
336
337 /* Next */
338 pkt0 = pkt1;
339 pkt0.rtp = (pjmedia_rtp_hdr*)pkt0.buffer;
340 pkt0.payload = pkt0.buffer + (pkt1.payload - pkt1.buffer);
341 }
342}
343
344
345int main(int argc, char *argv[])
346{
347 pj_str_t input, output, wav, srtp_crypto, srtp_key;
348 pj_pcap_filter filter;
349 pj_status_t status;
350
351 enum { OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT };
352 struct pj_getopt_option long_options[] = {
353 { "srtp-crypto", 1, 0, 'c' },
354 { "srtp-key", 1, 0, 'k' },
355 { "src-ip", 1, 0, OPT_SRC_IP },
356 { "dst-ip", 1, 0, OPT_DST_IP },
357 { "src-port", 1, 0, OPT_SRC_PORT },
358 { "dst-port", 1, 0, OPT_DST_PORT },
359 { NULL, 0, 0, 0}
360 };
361 int c;
362 int option_index;
363 char key_bin[32];
364
365 srtp_crypto.slen = srtp_key.slen = 0;
366
367 pj_pcap_filter_default(&filter);
368 filter.link = PJ_PCAP_LINK_TYPE_ETH;
369 filter.proto = PJ_PCAP_PROTO_TYPE_UDP;
370
371 /* Parse arguments */
372 pj_optind = 0;
373 while((c=pj_getopt_long(argc,argv, "c:k:", long_options, &option_index))!=-1) {
374 switch (c) {
375 case 'c':
376 srtp_crypto = pj_str(pj_optarg);
377 break;
378 case 'k':
379 {
380 int key_len = sizeof(key_bin);
381 srtp_key = pj_str(pj_optarg);
382 if (pj_base64_decode(&srtp_key, (pj_uint8_t*)key_bin, &key_len)) {
383 puts("Error: invalid key");
384 return 1;
385 }
386 srtp_key.ptr = key_bin;
387 srtp_key.slen = key_len;
388 }
389 break;
390 case OPT_SRC_IP:
391 {
392 pj_str_t t = pj_str(pj_optarg);
393 pj_in_addr a = pj_inet_addr(&t);
394 filter.ip_src = a.s_addr;
395 }
396 break;
397 case OPT_DST_IP:
398 {
399 pj_str_t t = pj_str(pj_optarg);
400 pj_in_addr a = pj_inet_addr(&t);
401 filter.ip_dst = a.s_addr;
402 }
403 break;
404 case OPT_SRC_PORT:
405 filter.src_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
406 break;
407 case OPT_DST_PORT:
408 filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
409 break;
410 default:
411 puts("Error: invalid option");
412 return 1;
413 }
414 }
415
416 if (pj_optind != argc - 2) {
417 puts(USAGE);
418 return 1;
419 }
420
421 if (!(srtp_crypto.slen) != !(srtp_key.slen)) {
422 puts("Error: both SRTP crypto and key must be specified");
423 puts(USAGE);
424 return 1;
425 }
426
427 input = pj_str(argv[pj_optind]);
428 output = pj_str(argv[pj_optind+1]);
429 wav = pj_str(".wav");
430
431 T( pj_init() );
432
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000433 pj_caching_pool_init(&app.cp, NULL, 0);
434 app.pool = pj_pool_create(&app.cp.factory, "pcaputil", 1000, 1000, NULL);
Benny Prijono0f856722008-02-01 14:59:19 +0000435
436 T( pjlib_util_init() );
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000437 T( pjmedia_endpt_create(&app.cp.factory, NULL, 0, &app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000438
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000439 T( pj_pcap_open(app.pool, input.ptr, &app.pcap) );
440 T( pj_pcap_set_filter(app.pcap, &filter) );
Benny Prijono0f856722008-02-01 14:59:19 +0000441
442 if (pj_stristr(&output, &wav)) {
443 pcap2wav(output.ptr, &srtp_crypto, &srtp_key);
444 } else {
445 err_exit("invalid output file", PJ_EINVAL);
446 }
447
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000448 pjmedia_endpt_destroy(app.mept);
449 pj_pool_release(app.pool);
450 pj_caching_pool_destroy(&app.cp);
Benny Prijono0f856722008-02-01 14:59:19 +0000451 pj_shutdown();
452 return 0;
453}
454