blob: 8f61384e6e4d68aed29625c86224a0d224d390e4 [file] [log] [blame]
Benny Prijono0f856722008-02-01 14:59:19 +00001/* $Id$ */
2/*
Benny Prijono32177c02008-06-20 22:44:47 +00003 * Copyright (C) 2003-2008 Benny Prijono <benny@prijono.org>
Benny Prijono0f856722008-02-01 14:59:19 +00004 *
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 */
Benny Prijonofe5a6942008-02-18 12:16:23 +0000157#if PJMEDIA_HAS_SRTP
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000158 if (app.srtp) {
159 int len = sz;
160 status = pjmedia_transport_srtp_decrypt_pkt(app.srtp, PJ_TRUE,
161 buf, &len);
162 if (status != PJ_SUCCESS) {
163 char errmsg[PJ_ERR_MSG_SIZE];
164 pj_strerror(status, errmsg, sizeof(errmsg));
165 printf("SRTP packet decryption failed, skipping packet: %s\n",
166 errmsg);
167 continue;
168 }
169 sz = len;
170
171 /* Decode RTP packet again */
172 status = pjmedia_rtp_decode_rtp(&app.rtp_sess, buf, sz, &r,
173 &p, payload_size);
174 if (status != PJ_SUCCESS) {
175 char errmsg[PJ_ERR_MSG_SIZE];
176 pj_strerror(status, errmsg, sizeof(errmsg));
177 printf("Not RTP packet, skipping packet: %s\n", errmsg);
178 continue;
179 }
180 }
Benny Prijonofe5a6942008-02-18 12:16:23 +0000181#endif
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000182
183 /* Update RTP session */
184 pjmedia_rtp_session_update(&app.rtp_sess, r, &seq_st);
185
186 /* Skip out-of-order packet */
187 if (seq_st.diff == 0) {
188 printf("Skipping out of order packet\n");
189 continue;
190 }
191
192 /* Skip if payload type is different */
193 if (r->pt != app.pt) {
194 printf("Skipping RTP packet with bad payload type\n");
195 continue;
196 }
197
198 /* Skip bad packet */
199 if (seq_st.status.flag.bad) {
200 printf("Skipping bad RTP\n");
201 continue;
202 }
203
204
205 *rtp = (pjmedia_rtp_hdr*)r;
206 *payload = (pj_uint8_t*)p;
207
208 /* We have good packet */
209 break;
210 }
Benny Prijono0f856722008-02-01 14:59:19 +0000211}
212
213static void pcap2wav(const char *wav_filename, const pj_str_t *srtp_crypto,
214 const pj_str_t *srtp_key)
215{
216 struct pkt
217 {
218 pj_uint8_t buffer[320];
219 pjmedia_rtp_hdr *rtp;
220 pj_uint8_t *payload;
221 unsigned payload_len;
222 } pkt0;
223 pjmedia_codec_mgr *cmgr;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000224 const pjmedia_codec_info *ci;
Benny Prijono0f856722008-02-01 14:59:19 +0000225 pjmedia_codec_param param;
Benny Prijono0f856722008-02-01 14:59:19 +0000226 unsigned samples_per_frame;
227 pj_status_t status;
228
229 /* Initialize all codecs */
230#if PJMEDIA_HAS_SPEEX_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000231 T( pjmedia_codec_speex_init(app.mept, 0, 10, 10) );
Benny Prijono0f856722008-02-01 14:59:19 +0000232#endif /* PJMEDIA_HAS_SPEEX_CODEC */
233
234#if PJMEDIA_HAS_ILBC_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000235 T( pjmedia_codec_ilbc_init(app.mept, 30) );
Benny Prijono0f856722008-02-01 14:59:19 +0000236#endif /* PJMEDIA_HAS_ILBC_CODEC */
237
238#if PJMEDIA_HAS_GSM_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000239 T( pjmedia_codec_gsm_init(app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000240#endif /* PJMEDIA_HAS_GSM_CODEC */
241
242#if PJMEDIA_HAS_G711_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000243 T( pjmedia_codec_g711_init(app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000244#endif /* PJMEDIA_HAS_G711_CODEC */
245
Benny Prijono7ffd7752008-03-17 14:07:53 +0000246#if PJMEDIA_HAS_G722_CODEC
247 T( pjmedia_codec_g722_init(app.mept) );
248#endif /* PJMEDIA_HAS_G722_CODEC */
249
Benny Prijono0f856722008-02-01 14:59:19 +0000250#if PJMEDIA_HAS_L16_CODEC
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000251 T( pjmedia_codec_l16_init(app.mept, 0) );
Benny Prijono0f856722008-02-01 14:59:19 +0000252#endif /* PJMEDIA_HAS_L16_CODEC */
253
254 /* Create SRTP transport is needed */
Benny Prijonofe5a6942008-02-18 12:16:23 +0000255#if PJMEDIA_HAS_SRTP
Benny Prijono0f856722008-02-01 14:59:19 +0000256 if (srtp_crypto->slen) {
257 pjmedia_srtp_crypto crypto;
258
259 pj_bzero(&crypto, sizeof(crypto));
260 crypto.key = *srtp_key;
261 crypto.name = *srtp_crypto;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000262 T( pjmedia_transport_srtp_create(app.mept, NULL, NULL, &app.srtp) );
263 T( pjmedia_transport_srtp_start(app.srtp, &crypto, &crypto) );
Benny Prijono0f856722008-02-01 14:59:19 +0000264 }
Benny Prijonofe5a6942008-02-18 12:16:23 +0000265#else
266 PJ_UNUSED_ARG(srtp_crypto);
267 PJ_UNUSED_ARG(srtp_key);
268#endif
Benny Prijono0f856722008-02-01 14:59:19 +0000269
270 /* Read first packet */
271 read_rtp(pkt0.buffer, sizeof(pkt0.buffer), &pkt0.rtp,
272 &pkt0.payload, &pkt0.payload_len);
273
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000274 cmgr = pjmedia_endpt_get_codec_mgr(app.mept);
Benny Prijono0f856722008-02-01 14:59:19 +0000275
276 /* Get codec info and param for the specified payload type */
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000277 app.pt = pkt0.rtp->pt;
Benny Prijono0f856722008-02-01 14:59:19 +0000278 T( pjmedia_codec_mgr_get_codec_info(cmgr, pkt0.rtp->pt, &ci) );
279 T( pjmedia_codec_mgr_get_default_param(cmgr, ci, &param) );
280
281 /* Alloc and init codec */
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000282 T( pjmedia_codec_mgr_alloc_codec(cmgr, ci, &app.codec) );
283 T( app.codec->op->init(app.codec, app.pool) );
284 T( app.codec->op->open(app.codec, &param) );
Benny Prijono0f856722008-02-01 14:59:19 +0000285
286 /* Open WAV file */
287 samples_per_frame = ci->clock_rate * param.info.frm_ptime / 1000;
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000288 T( pjmedia_wav_writer_port_create(app.pool, wav_filename,
Benny Prijono0f856722008-02-01 14:59:19 +0000289 ci->clock_rate, ci->channel_cnt,
290 samples_per_frame,
291 param.info.pcm_bits_per_sample, 0, 0,
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000292 &app.wav) );
Benny Prijono0f856722008-02-01 14:59:19 +0000293
294 /* Loop reading PCAP and writing WAV file */
295 for (;;) {
296 struct pkt pkt1;
297 pj_timestamp ts;
298 pjmedia_frame frames[16], pcm_frame;
299 short pcm[320];
300 unsigned i, frame_cnt;
301 long samples_cnt, ts_gap;
302
303 pj_assert(sizeof(pcm) >= samples_per_frame);
304
305 /* Parse first packet */
306 ts.u64 = 0;
307 frame_cnt = PJ_ARRAY_SIZE(frames);
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000308 T( app.codec->op->parse(app.codec, pkt0.payload, pkt0.payload_len,
309 &ts, &frame_cnt, frames) );
Benny Prijono0f856722008-02-01 14:59:19 +0000310
311 /* Decode and write to WAV file */
312 samples_cnt = 0;
313 for (i=0; i<frame_cnt; ++i) {
314 pjmedia_frame pcm_frame;
315
316 pcm_frame.buf = pcm;
317 pcm_frame.size = samples_per_frame * 2;
318
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000319 T( app.codec->op->decode(app.codec, &frames[i], pcm_frame.size,
320 &pcm_frame) );
321 T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000322 samples_cnt += samples_per_frame;
323 }
324
325 /* Read next packet */
326 read_rtp(pkt1.buffer, sizeof(pkt1.buffer), &pkt1.rtp,
327 &pkt1.payload, &pkt1.payload_len);
328
329 /* Fill in the gap (if any) between pkt0 and pkt1 */
330 ts_gap = pj_ntohl(pkt1.rtp->ts) - pj_ntohl(pkt0.rtp->ts) -
331 samples_cnt;
332 while (ts_gap >= (long)samples_per_frame) {
333
334 pcm_frame.buf = pcm;
335 pcm_frame.size = samples_per_frame * 2;
336
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000337 if (app.codec->op->recover) {
338 T( app.codec->op->recover(app.codec, pcm_frame.size,
339 &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000340 } else {
341 pj_bzero(pcm_frame.buf, pcm_frame.size);
342 }
343
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000344 T( pjmedia_port_put_frame(app.wav, &pcm_frame) );
Benny Prijono0f856722008-02-01 14:59:19 +0000345 ts_gap -= samples_per_frame;
346 }
347
348 /* Next */
349 pkt0 = pkt1;
350 pkt0.rtp = (pjmedia_rtp_hdr*)pkt0.buffer;
351 pkt0.payload = pkt0.buffer + (pkt1.payload - pkt1.buffer);
352 }
353}
354
355
356int main(int argc, char *argv[])
357{
358 pj_str_t input, output, wav, srtp_crypto, srtp_key;
359 pj_pcap_filter filter;
360 pj_status_t status;
361
362 enum { OPT_SRC_IP = 1, OPT_DST_IP, OPT_SRC_PORT, OPT_DST_PORT };
363 struct pj_getopt_option long_options[] = {
364 { "srtp-crypto", 1, 0, 'c' },
365 { "srtp-key", 1, 0, 'k' },
366 { "src-ip", 1, 0, OPT_SRC_IP },
367 { "dst-ip", 1, 0, OPT_DST_IP },
368 { "src-port", 1, 0, OPT_SRC_PORT },
369 { "dst-port", 1, 0, OPT_DST_PORT },
370 { NULL, 0, 0, 0}
371 };
372 int c;
373 int option_index;
374 char key_bin[32];
375
376 srtp_crypto.slen = srtp_key.slen = 0;
377
378 pj_pcap_filter_default(&filter);
379 filter.link = PJ_PCAP_LINK_TYPE_ETH;
380 filter.proto = PJ_PCAP_PROTO_TYPE_UDP;
381
382 /* Parse arguments */
383 pj_optind = 0;
384 while((c=pj_getopt_long(argc,argv, "c:k:", long_options, &option_index))!=-1) {
385 switch (c) {
386 case 'c':
387 srtp_crypto = pj_str(pj_optarg);
388 break;
389 case 'k':
390 {
391 int key_len = sizeof(key_bin);
392 srtp_key = pj_str(pj_optarg);
393 if (pj_base64_decode(&srtp_key, (pj_uint8_t*)key_bin, &key_len)) {
394 puts("Error: invalid key");
395 return 1;
396 }
397 srtp_key.ptr = key_bin;
398 srtp_key.slen = key_len;
399 }
400 break;
401 case OPT_SRC_IP:
402 {
403 pj_str_t t = pj_str(pj_optarg);
404 pj_in_addr a = pj_inet_addr(&t);
405 filter.ip_src = a.s_addr;
406 }
407 break;
408 case OPT_DST_IP:
409 {
410 pj_str_t t = pj_str(pj_optarg);
411 pj_in_addr a = pj_inet_addr(&t);
412 filter.ip_dst = a.s_addr;
413 }
414 break;
415 case OPT_SRC_PORT:
416 filter.src_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
417 break;
418 case OPT_DST_PORT:
419 filter.dst_port = pj_htons((pj_uint16_t)atoi(pj_optarg));
420 break;
421 default:
422 puts("Error: invalid option");
423 return 1;
424 }
425 }
426
427 if (pj_optind != argc - 2) {
428 puts(USAGE);
429 return 1;
430 }
431
432 if (!(srtp_crypto.slen) != !(srtp_key.slen)) {
433 puts("Error: both SRTP crypto and key must be specified");
434 puts(USAGE);
435 return 1;
436 }
437
438 input = pj_str(argv[pj_optind]);
439 output = pj_str(argv[pj_optind+1]);
440 wav = pj_str(".wav");
441
442 T( pj_init() );
443
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000444 pj_caching_pool_init(&app.cp, NULL, 0);
445 app.pool = pj_pool_create(&app.cp.factory, "pcaputil", 1000, 1000, NULL);
Benny Prijono0f856722008-02-01 14:59:19 +0000446
447 T( pjlib_util_init() );
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000448 T( pjmedia_endpt_create(&app.cp.factory, NULL, 0, &app.mept) );
Benny Prijono0f856722008-02-01 14:59:19 +0000449
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000450 T( pj_pcap_open(app.pool, input.ptr, &app.pcap) );
451 T( pj_pcap_set_filter(app.pcap, &filter) );
Benny Prijono0f856722008-02-01 14:59:19 +0000452
453 if (pj_stristr(&output, &wav)) {
454 pcap2wav(output.ptr, &srtp_crypto, &srtp_key);
455 } else {
456 err_exit("invalid output file", PJ_EINVAL);
457 }
458
Benny Prijonoc4bb78b2008-02-02 09:12:13 +0000459 pjmedia_endpt_destroy(app.mept);
460 pj_pool_release(app.pool);
461 pj_caching_pool_destroy(&app.cp);
Benny Prijono0f856722008-02-01 14:59:19 +0000462 pj_shutdown();
463 return 0;
464}
465