Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 1 | /* $Id$ */
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 2 | /*
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 3 | * Copyright (C) 2003-2006 Benny Prijono <benny@prijono.org>
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 4 | *
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 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.
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 9 | *
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 10 | * This program is distributed in the hope that it will be useful,
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
Benny Prijono | e0312a7 | 2005-11-18 00:16:43 +0000 | [diff] [blame] | 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
|
Benny Prijono | e722461 | 2005-11-13 19:40:44 +0000 | [diff] [blame] | 18 | */
|
| 19 | #include <pjmedia.h>
|
| 20 | #include <pjlib.h>
|
| 21 | #include <stdio.h>
|
| 22 |
|
| 23 | #define THIS_FILE "audio_tool.c"
|
| 24 |
|
| 25 | static pj_caching_pool caching_pool;
|
| 26 | static pj_pool_factory *pf;
|
| 27 | static FILE *fhnd;
|
| 28 | static pj_med_mgr_t *mm;
|
| 29 | static pj_codec *codec;
|
| 30 | static pj_codec_attr cattr;
|
| 31 |
|
| 32 |
|
| 33 | #define WRITE_ORIGINAL_PCM 0
|
| 34 | #if WRITE_ORIGINAL_PCM
|
| 35 | static FILE *fhnd_pcm;
|
| 36 | #endif
|
| 37 |
|
| 38 | static char talker_sdp[] =
|
| 39 | "v=0\r\n"
|
| 40 | "o=- 0 0 IN IP4 127.0.0.1\r\n"
|
| 41 | "s=-\r\n"
|
| 42 | "c=IN IP4 127.0.0.1\r\n"
|
| 43 | "t=0 0\r\n"
|
| 44 | "m=audio 4002 RTP/AVP 0\r\n"
|
| 45 | "a=rtpmap:0 PCMU/8000\r\n"
|
| 46 | "a=sendonly\r\n";
|
| 47 | static char listener_sdp[] =
|
| 48 | "v=0\r\n"
|
| 49 | "o=- 0 0 IN IP4 127.0.0.1\r\n"
|
| 50 | "s=-\r\n"
|
| 51 | "c=IN IP4 127.0.0.1\r\n"
|
| 52 | "t=0 0\r\n"
|
| 53 | "m=audio 4000 RTP/AVP 0\r\n"
|
| 54 | "a=rtpmap:0 PCMU/8000\r\n"
|
| 55 | "a=recvonly\r\n";
|
| 56 |
|
| 57 | static pj_status_t play_callback(/* in */ void *user_data,
|
| 58 | /* in */ pj_uint32_t timestamp,
|
| 59 | /* out */ void *frame,
|
| 60 | /* out */ unsigned size)
|
| 61 | {
|
| 62 | char pkt[160];
|
| 63 | struct pj_audio_frame in, out;
|
| 64 | int frmsz = cattr.avg_bps / 8 * cattr.ptime / 1000;
|
| 65 |
|
| 66 | if (fread(pkt, frmsz, 1, fhnd) != 1) {
|
| 67 | puts("EOF");
|
| 68 | return -1;
|
| 69 | } else {
|
| 70 | in.type = PJ_AUDIO_FRAME_AUDIO;
|
| 71 | in.buf = pkt;
|
| 72 | in.size = frmsz;
|
| 73 | out.buf = frame;
|
| 74 | if (codec->op->decode (codec, &in, size, &out) != 0)
|
| 75 | return -1;
|
| 76 |
|
| 77 | size = out.size;
|
| 78 | return 0;
|
| 79 | }
|
| 80 | }
|
| 81 |
|
| 82 | static pj_status_t rec_callback( /* in */ void *user_data,
|
| 83 | /* in */ pj_uint32_t timestamp,
|
| 84 | /* in */ const void *frame,
|
| 85 | /* in*/ unsigned size)
|
| 86 | {
|
| 87 | char pkt[160];
|
| 88 | struct pj_audio_frame in, out;
|
| 89 | //int frmsz = cattr.avg_bps / 8 * cattr.ptime / 1000;
|
| 90 |
|
| 91 | #if WRITE_ORIGINAL_PCM
|
| 92 | fwrite(frame, size, 1, fhnd_pcm);
|
| 93 | #endif
|
| 94 |
|
| 95 | in.type = PJ_AUDIO_FRAME_AUDIO;
|
| 96 | in.buf = (void*)frame;
|
| 97 | in.size = size;
|
| 98 | out.buf = pkt;
|
| 99 |
|
| 100 | if (codec->op->encode(codec, &in, sizeof(pkt), &out) != 0)
|
| 101 | return -1;
|
| 102 |
|
| 103 | if (fwrite(pkt, out.size, 1, fhnd) != 1)
|
| 104 | return -1;
|
| 105 | return 0;
|
| 106 | }
|
| 107 |
|
| 108 | static pj_status_t init()
|
| 109 | {
|
| 110 | pj_codec_mgr *cm;
|
| 111 | pj_codec_id id;
|
| 112 | int i;
|
| 113 |
|
| 114 | pj_caching_pool_init(&caching_pool, &pj_pool_factory_default_policy, 0);
|
| 115 | pf = &caching_pool.factory;
|
| 116 |
|
| 117 | if (pj_snd_init(&caching_pool.factory))
|
| 118 | return -1;
|
| 119 |
|
| 120 | PJ_LOG(3,(THIS_FILE, "Dumping audio devices:"));
|
| 121 | for (i=0; i<pj_snd_get_dev_count(); ++i) {
|
| 122 | const pj_snd_dev_info *info;
|
| 123 | info = pj_snd_get_dev_info(i);
|
| 124 | PJ_LOG(3,(THIS_FILE, " %d: %s\t(%d in, %d out",
|
| 125 | i, info->name,
|
| 126 | info->input_count, info->output_count));
|
| 127 | }
|
| 128 |
|
| 129 | mm = pj_med_mgr_create (&caching_pool.factory);
|
| 130 | cm = pj_med_mgr_get_codec_mgr (mm);
|
| 131 |
|
| 132 | id.type = PJ_MEDIA_TYPE_AUDIO;
|
| 133 | id.pt = 0;
|
| 134 | id.encoding_name = pj_str("PCMU");
|
| 135 | id.sample_rate = 8000;
|
| 136 |
|
| 137 | codec = pj_codec_mgr_alloc_codec (cm, &id);
|
| 138 | codec->op->default_attr(codec, &cattr);
|
| 139 | codec->op->open(codec, &cattr);
|
| 140 | return 0;
|
| 141 | }
|
| 142 |
|
| 143 | static pj_status_t deinit()
|
| 144 | {
|
| 145 | pj_codec_mgr *cm;
|
| 146 | cm = pj_med_mgr_get_codec_mgr (mm);
|
| 147 | codec->op->close(codec);
|
| 148 | pj_codec_mgr_dealloc_codec (cm, codec);
|
| 149 | pj_med_mgr_destroy (mm);
|
| 150 | pj_caching_pool_destroy(&caching_pool);
|
| 151 | return 0;
|
| 152 | }
|
| 153 |
|
| 154 | static pj_status_t record_file (const char *filename)
|
| 155 | {
|
| 156 | pj_snd_stream *stream;
|
| 157 | pj_snd_stream_info info;
|
| 158 | int status;
|
| 159 | char s[10];
|
| 160 |
|
| 161 | printf("Recording to file %s...\n", filename);
|
| 162 |
|
| 163 | fhnd = fopen(filename, "wb");
|
| 164 | if (!fhnd)
|
| 165 | return -1;
|
| 166 |
|
| 167 | #if WRITE_ORIGINAL_PCM
|
| 168 | fhnd_pcm = fopen("ORIGINAL.PCM", "wb");
|
| 169 | if (!fhnd_pcm)
|
| 170 | return -1;
|
| 171 | #endif
|
| 172 |
|
| 173 | pj_memset(&info, 0, sizeof(info));
|
| 174 | info.bits_per_sample = 16;
|
| 175 | info.bytes_per_frame = 2;
|
| 176 | info.frames_per_packet = 160;
|
| 177 | info.samples_per_frame = 1;
|
| 178 | info.samples_per_sec = 8000;
|
| 179 |
|
| 180 | stream = pj_snd_open_recorder(-1, &info, &rec_callback, NULL);
|
| 181 | if (!stream)
|
| 182 | return -1;
|
| 183 |
|
| 184 | status = pj_snd_stream_start(stream);
|
| 185 | if (status != 0)
|
| 186 | goto on_error;
|
| 187 |
|
| 188 | puts("Press <ENTER> to exit recording");
|
| 189 | fgets(s, sizeof(s), stdin);
|
| 190 |
|
| 191 | pj_snd_stream_stop(stream);
|
| 192 | pj_snd_stream_close(stream);
|
| 193 |
|
| 194 | #if WRITE_ORIGINAL_PCM
|
| 195 | fclose(fhnd_pcm);
|
| 196 | #endif
|
| 197 | fclose(fhnd);
|
| 198 | return 0;
|
| 199 |
|
| 200 | on_error:
|
| 201 | pj_snd_stream_stop(stream);
|
| 202 | pj_snd_stream_close(stream);
|
| 203 | return -1;
|
| 204 | }
|
| 205 |
|
| 206 |
|
| 207 | static pj_status_t play_file (const char *filename)
|
| 208 | {
|
| 209 | pj_snd_stream *stream;
|
| 210 | pj_snd_stream_info info;
|
| 211 | int status;
|
| 212 | char s[10];
|
| 213 |
|
| 214 | printf("Playing file %s...\n", filename);
|
| 215 |
|
| 216 | fhnd = fopen(filename, "rb");
|
| 217 | if (!fhnd)
|
| 218 | return -1;
|
| 219 |
|
| 220 | pj_memset(&info, 0, sizeof(info));
|
| 221 | info.bits_per_sample = 16;
|
| 222 | info.bytes_per_frame = 2;
|
| 223 | info.frames_per_packet = 160;
|
| 224 | info.samples_per_frame = 1;
|
| 225 | info.samples_per_sec = 8000;
|
| 226 |
|
| 227 | stream = pj_snd_open_player(-1, &info, &play_callback, NULL);
|
| 228 | if (!stream)
|
| 229 | return -1;
|
| 230 |
|
| 231 | status = pj_snd_stream_start(stream);
|
| 232 | if (status != 0)
|
| 233 | goto on_error;
|
| 234 |
|
| 235 | puts("Press <ENTER> to exit playing");
|
| 236 | fgets(s, sizeof(s), stdin);
|
| 237 |
|
| 238 | pj_snd_stream_stop(stream);
|
| 239 | pj_snd_stream_close(stream);
|
| 240 |
|
| 241 | fclose(fhnd);
|
| 242 | return 0;
|
| 243 |
|
| 244 | on_error:
|
| 245 | pj_snd_stream_stop(stream);
|
| 246 | pj_snd_stream_close(stream);
|
| 247 | return -1;
|
| 248 | }
|
| 249 |
|
| 250 | static int create_ses_by_remote_sdp(int local_port, char *sdp)
|
| 251 | {
|
| 252 | pj_media_session_t *ses = NULL;
|
| 253 | pjsdp_session_desc *sdp_ses;
|
| 254 | pj_media_sock_info skinfo;
|
| 255 | pj_pool_t *pool;
|
| 256 | char s[4];
|
| 257 | const pj_media_stream_info *info[2];
|
| 258 | int i, count;
|
| 259 |
|
| 260 | pool = pj_pool_create(pf, "sdp", 1024, 0, NULL);
|
| 261 | if (!pool) {
|
| 262 | PJ_LOG(1,(THIS_FILE, "Unable to create pool"));
|
| 263 | return -1;
|
| 264 | }
|
| 265 |
|
| 266 | pj_memset(&skinfo, 0, sizeof(skinfo));
|
| 267 | skinfo.rtp_sock = skinfo.rtcp_sock = pj_sock_socket(PJ_AF_INET, PJ_SOCK_DGRAM, 0, 0);
|
| 268 | if (skinfo.rtp_sock == PJ_INVALID_SOCKET) {
|
| 269 | PJ_LOG(1,(THIS_FILE, "Unable to create socket"));
|
| 270 | goto on_error;
|
| 271 | }
|
| 272 |
|
| 273 | pj_sockaddr_init2(&skinfo.rtp_addr_name, "0.0.0.0", local_port);
|
| 274 | if (pj_sock_bind(skinfo.rtp_sock, (struct pj_sockaddr*)&skinfo.rtp_addr_name, sizeof(pj_sockaddr_in)) != 0) {
|
| 275 | PJ_LOG(1,(THIS_FILE, "Unable to bind socket"));
|
| 276 | goto on_error;
|
| 277 | }
|
| 278 |
|
| 279 | sdp_ses = pjsdp_parse(sdp, strlen(sdp), pool);
|
| 280 | if (!sdp_ses) {
|
| 281 | PJ_LOG(1,(THIS_FILE, "Error parsing SDP"));
|
| 282 | goto on_error;
|
| 283 | }
|
| 284 |
|
| 285 | ses = pj_media_session_create_from_sdp(mm, sdp_ses, &skinfo);
|
| 286 | if (!ses) {
|
| 287 | PJ_LOG(1,(THIS_FILE, "Unable to create session from SDP"));
|
| 288 | goto on_error;
|
| 289 | }
|
| 290 |
|
| 291 | if (pj_media_session_activate(ses) != 0) {
|
| 292 | PJ_LOG(1,(THIS_FILE, "Error activating session"));
|
| 293 | goto on_error;
|
| 294 | }
|
| 295 |
|
| 296 | count = pj_media_session_enum_streams(ses, 2, info);
|
| 297 | printf("\nDumping streams: \n");
|
| 298 | for (i=0; i<count; ++i) {
|
| 299 | const char *dir;
|
| 300 | char *local_ip;
|
| 301 |
|
| 302 | switch (info[i]->dir) {
|
| 303 | case PJ_MEDIA_DIR_NONE:
|
| 304 | dir = "- NONE -"; break;
|
| 305 | case PJ_MEDIA_DIR_ENCODING:
|
| 306 | dir = "SENDONLY"; break;
|
| 307 | case PJ_MEDIA_DIR_DECODING:
|
| 308 | dir = "RECVONLY"; break;
|
| 309 | case PJ_MEDIA_DIR_ENCODING_DECODING:
|
| 310 | dir = "SENDRECV"; break;
|
| 311 | default:
|
| 312 | dir = "?UNKNOWN"; break;
|
| 313 | }
|
| 314 |
|
| 315 | local_ip = pj_sockaddr_get_str_addr(&info[i]->sock_info.rtp_addr_name);
|
| 316 |
|
| 317 | printf(" Stream %d: %.*s %s local=%s:%d remote=%.*s:%d\n",
|
| 318 | i, info[i]->type.slen, info[i]->type.ptr,
|
| 319 | dir,
|
| 320 | local_ip, pj_sockaddr_get_port(&info[i]->sock_info.rtp_addr_name),
|
| 321 | info[i]->rem_addr.slen, info[i]->rem_addr.ptr, info[i]->rem_port);
|
| 322 | }
|
| 323 |
|
| 324 | puts("Press <ENTER> to quit");
|
| 325 | fgets(s, sizeof(s), stdin);
|
| 326 |
|
| 327 | pj_media_session_destroy(ses);
|
| 328 | pj_sock_close(skinfo.rtp_sock);
|
| 329 | pj_pool_release(pool);
|
| 330 |
|
| 331 | return 0;
|
| 332 |
|
| 333 | on_error:
|
| 334 | if (ses)
|
| 335 | pj_media_session_destroy(ses);
|
| 336 | if (skinfo.rtp_sock != PJ_INVALID_SOCKET)
|
| 337 | pj_sock_close(skinfo.rtp_sock);
|
| 338 | if (pool)
|
| 339 | pj_pool_release(pool);
|
| 340 | return -1;
|
| 341 | }
|
| 342 |
|
| 343 | #if WRITE_ORIGINAL_PCM
|
| 344 | static pj_status_t convert(const char *src, const char *dst)
|
| 345 | {
|
| 346 | char pcm[320];
|
| 347 | char frame[160];
|
| 348 | struct pj_audio_frame in, out;
|
| 349 |
|
| 350 | fhnd_pcm = fopen(src, "rb");
|
| 351 | if (!fhnd_pcm)
|
| 352 | return -1;
|
| 353 | fhnd = fopen(dst, "wb");
|
| 354 | if (!fhnd)
|
| 355 | return -1;
|
| 356 |
|
| 357 | while (fread(pcm, 320, 1, fhnd_pcm) == 1) {
|
| 358 |
|
| 359 | in.type = PJ_AUDIO_FRAME_AUDIO;
|
| 360 | in.buf = pcm;
|
| 361 | in.size = 320;
|
| 362 | out.buf = frame;
|
| 363 |
|
| 364 | if (codec->op->encode(codec, &in, 160, &out) != 0)
|
| 365 | break;
|
| 366 |
|
| 367 | if (fwrite(frame, out.size, 1, fhnd) != 1)
|
| 368 | break;
|
| 369 |
|
| 370 | }
|
| 371 |
|
| 372 | fclose(fhnd);
|
| 373 | fclose(fhnd_pcm);
|
| 374 | return 0;
|
| 375 | }
|
| 376 | #endif
|
| 377 |
|
| 378 | static void usage(const char *exe)
|
| 379 | {
|
| 380 | printf("Usage: %s <command> <file>\n", exe);
|
| 381 | puts("where:");
|
| 382 | puts(" <command> play|record|send|recv");
|
| 383 | }
|
| 384 |
|
| 385 | int main(int argc, char *argv[])
|
| 386 | {
|
| 387 | if (argc < 2) {
|
| 388 | usage(argv[0]);
|
| 389 | return 1;
|
| 390 | }
|
| 391 |
|
| 392 | pj_init();
|
| 393 |
|
| 394 | init();
|
| 395 |
|
| 396 | if (stricmp(argv[1], "record")==0) {
|
| 397 | record_file("FILE.PCM");
|
| 398 | } else if (stricmp(argv[1], "play")==0) {
|
| 399 | play_file("FILE.PCM");
|
| 400 | } else if (stricmp(argv[1], "send")==0) {
|
| 401 | create_ses_by_remote_sdp(4002, listener_sdp);
|
| 402 | } else if (stricmp(argv[1], "recv")==0) {
|
| 403 | create_ses_by_remote_sdp(4000, talker_sdp);
|
| 404 | } else {
|
| 405 | usage(argv[0]);
|
| 406 | }
|
| 407 | deinit();
|
| 408 | return 0;
|
| 409 | }
|