diff --git a/bbb-voice-conference/config/asterisk/bbb_sip.conf b/bbb-voice-conference/config/asterisk/bbb_sip.conf index 6dbe04d2c3226458d0f13fb5ebcc9278f34dfb6b..101eab4285f3cfe5835c663e460d4fdb7bcfd23d 100644 --- a/bbb-voice-conference/config/asterisk/bbb_sip.conf +++ b/bbb-voice-conference/config/asterisk/bbb_sip.conf @@ -1,1394 +1,6 @@ - - -[34380] -type=friend -username=34380 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34381] -type=friend -username=34381 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34382] -type=friend -username=34382 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34383] -type=friend -username=34383 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34384] -type=friend -username=34384 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34385] -type=friend -username=34385 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34386] -type=friend -username=34386 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34387] -type=friend -username=34387 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34388] -type=friend -username=34388 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34389] -type=friend -username=34389 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34390] -type=friend -username=34390 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34391] -type=friend -username=34391 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34392] -type=friend -username=34392 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34393] -type=friend -username=34393 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34394] -type=friend -username=34394 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34395] -type=friend -username=34395 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34396] -type=friend -username=34396 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34397] -type=friend -username=34397 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34398] -type=friend -username=34398 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34399] -type=friend -username=34399 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34400] -type=friend -username=34400 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34401] -type=friend -username=34401 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34402] -type=friend -username=34402 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34403] -type=friend -username=34403 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34404] -type=friend -username=34404 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34405] -type=friend -username=34405 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34406] -type=friend -username=34406 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34407] -type=friend -username=34407 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34408] -type=friend -username=34408 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34409] -type=friend -username=34409 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34410] -type=friend -username=34410 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34411] -type=friend -username=34411 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34412] -type=friend -username=34412 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34413] -type=friend -username=34413 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34414] -type=friend -username=34414 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34415] -type=friend -username=34415 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34416] -type=friend -username=34416 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34417] -type=friend -username=34417 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34418] -type=friend -username=34418 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34419] -type=friend -username=34419 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34420] -type=friend -username=34420 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34421] -type=friend -username=34421 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34422] -type=friend -username=34422 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34423] -type=friend -username=34423 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34424] -type=friend -username=34424 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34425] -type=friend -username=34425 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34426] -type=friend -username=34426 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34427] -type=friend -username=34427 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34428] -type=friend -username=34428 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34429] -type=friend -username=34429 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34430] -type=friend -username=34430 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34431] -type=friend -username=34431 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34432] -type=friend -username=34432 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34433] -type=friend -username=34433 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34434] -type=friend -username=34434 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34435] -type=friend -username=34435 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34436] -type=friend -username=34436 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34437] -type=friend -username=34437 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34438] -type=friend -username=34438 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34439] -type=friend -username=34439 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34440] -type=friend -username=34440 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34441] -type=friend -username=34441 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34442] -type=friend -username=34442 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34443] -type=friend -username=34443 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34444] -type=friend -username=34444 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34445] -type=friend -username=34445 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34446] -type=friend -username=34446 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34447] -type=friend -username=34447 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34448] -type=friend -username=34448 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34449] -type=friend -username=34449 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34450] -type=friend -username=34450 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34451] -type=friend -username=34451 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34452] -type=friend -username=34452 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34453] -type=friend -username=34453 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34454] -type=friend -username=34454 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34455] -type=friend -username=34455 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34456] -type=friend -username=34456 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34457] -type=friend -username=34457 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34458] -type=friend -username=34458 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34459] -type=friend -username=34459 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34460] -type=friend -username=34460 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34461] -type=friend -username=34461 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34462] -type=friend -username=34462 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34463] -type=friend -username=34463 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34464] -type=friend -username=34464 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34465] -type=friend -username=34465 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34466] -type=friend -username=34466 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34467] -type=friend -username=34467 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34468] -type=friend -username=34468 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34469] -type=friend -username=34469 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34470] -type=friend -username=34470 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34471] -type=friend -username=34471 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34472] -type=friend -username=34472 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34473] -type=friend -username=34473 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34474] -type=friend -username=34474 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34475] -type=friend -username=34475 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34476] -type=friend -username=34476 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34477] -type=friend -username=34477 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34478] -type=friend -username=34478 -insecure=very -qualify=no -nat=yes -host=dynamic -canreinvite=no -context=bbb-voip -disallow=all -allow=ulaw - - - -[34479] +[bbbuser] type=friend -username=34479 +username=bbbuser insecure=very qualify=no nat=yes diff --git a/bbb-voice/.gitignore b/bbb-voice/.gitignore index 5d514e6c4aad4f857dbc71ac9ae6282609575ceb..d359ba41acf11b53d2c667f424539aad82d78a1b 100644 --- a/bbb-voice/.gitignore +++ b/bbb-voice/.gitignore @@ -1,3 +1,4 @@ .project .classpath .settings +build diff --git a/bbb-voice/INSTALL b/bbb-voice/INSTALL index e69029c14b6590f1add87341f2da1251f09e6e54..5ef773eb5c173f5ad8338a43a5ab0fd8b5d4dbf4 100644 --- a/bbb-voice/INSTALL +++ b/bbb-voice/INSTALL @@ -1,305 +1,305 @@ - -; Add to /etc/asterisk/sip.conf -;------------------------------------- -[3000] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3001] -type=friend -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3002] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3003] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3004] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3005] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3006] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3007] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3008] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3009] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3010] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3011] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3012] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3013] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3014] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3015] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3016] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3017] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3018] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3019] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3020] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3021] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3022] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3023] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3024] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3025] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3026] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3027] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3028] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - -[3029] -type=friend -qualify=yes -port=5070 -nat=yes -host=dynamic -dtmfmode=rfc2833 -context=from-internal -canreinvite=no - - - - + +; Add to /etc/asterisk/sip.conf +;------------------------------------- +[3000] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3001] +type=friend +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3002] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3003] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3004] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3005] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3006] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3007] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3008] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3009] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3010] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3011] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3012] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3013] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3014] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3015] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3016] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3017] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3018] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3019] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3020] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3021] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3022] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3023] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3024] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3025] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3026] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3027] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3028] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + +[3029] +type=friend +qualify=yes +port=5070 +nat=yes +host=dynamic +dtmfmode=rfc2833 +context=from-internal +canreinvite=no + + + + diff --git a/bbb-voice/README b/bbb-voice/README new file mode 100644 index 0000000000000000000000000000000000000000..4cc4bdc918ca06e69afda3cc3d24ca639f1a3376 --- /dev/null +++ b/bbb-voice/README @@ -0,0 +1,8 @@ + +To build +gradle copyToLib +gradle eclipse + +import into eclipse +setup build path + diff --git a/bbb-voice/build.gradle b/bbb-voice/build.gradle old mode 100644 new mode 100755 index e2cdf0f6858a397055235a4497507e0c65a8d97a..246bc8277b3706198b9541fa93b96e7d4bb16fe6 --- a/bbb-voice/build.gradle +++ b/bbb-voice/build.gradle @@ -6,6 +6,12 @@ version = '0.64' jar.enabled = true archivesBaseName = 'sip' +task dependencies(type: Copy) { + into('lib') + from configurations.default + from configurations.default.allArtifacts*.file +} + task copyToLib(dependsOn: configurations.default.buildArtifacts, type: Copy) { into('lib') from configurations.default diff --git a/bbb-voice/build/.gitignore b/bbb-voice/build/.gitignore deleted file mode 100644 index 7ee0c497c4e9499a45009f1765a13949f3af3737..0000000000000000000000000000000000000000 --- a/bbb-voice/build/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -classes -dependency-cache -libs -sip - diff --git a/bbb-voice/src/main/java/local/net/RtpSocket.java b/bbb-voice/src/main/java/local/net/RtpSocket.java old mode 100644 new mode 100755 index 638add57c8a5528a84d0330d4e63ffe01bca54f9..190d816370467d9384b9f0885619f9f60332ec7f --- a/bbb-voice/src/main/java/local/net/RtpSocket.java +++ b/bbb-voice/src/main/java/local/net/RtpSocket.java @@ -21,12 +21,10 @@ package local.net; - import java.net.InetAddress; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.io.IOException; -import org.zoolu.tools.Random; /** RtpSocket implements a RTP socket for receiving and sending RTP packets. diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Application.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Application.java new file mode 100755 index 0000000000000000000000000000000000000000..8a478f1a4312ca58f1220b3decb81245d442b3a1 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Application.java @@ -0,0 +1,144 @@ +package org.bigbluebutton.voiceconf.red5; + +import java.text.MessageFormat; +import java.util.List; +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.sip.PeerNotFoundException; +import org.bigbluebutton.voiceconf.sip.SipPeerManager; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.adapter.MultiThreadedApplicationAdapter; +import org.red5.server.api.IClient; +import org.red5.server.api.IConnection; +import org.red5.server.api.IScope; +import org.red5.server.api.Red5; +import org.red5.server.api.service.IServiceCapableConnection; +import org.red5.server.api.stream.IBroadcastStream; + +public class Application extends MultiThreadedApplicationAdapter { + private static Logger log = Red5LoggerFactory.getLogger(Application.class, "sip"); + + private SipPeerManager sipPeerManager; + private ClientConnectionManager clientConnManager; + + private String sipServerHost = "localhost"; + private int sipPort = 5070; + private int startAudioPort = 3000; + private int stopAudioPort = 3029; + private String password = "secret"; + private String username; + private CallStreamFactory callStreamFactory; + + @Override + public boolean appStart(IScope scope) { + log.debug("VoiceConferenceApplication appStart[" + scope.getName() + "]"); + callStreamFactory = new CallStreamFactory(); + callStreamFactory.setScope(scope); + sipPeerManager.setCallStreamFactory(callStreamFactory); + sipPeerManager.setClientConnectionManager(clientConnManager); + sipPeerManager.createSipPeer("default", sipServerHost, sipPort, startAudioPort, stopAudioPort); + try { + sipPeerManager.register("default", username, password); + } catch (PeerNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return true; + } + + @Override + public void appStop(IScope scope) { + log.debug("VoiceConferenceApplication appStop[" + scope.getName() + "]"); + sipPeerManager.destroyAllSessions(); + } + + @Override + public boolean appJoin(IClient client, IScope scope) { + log.debug("VoiceConferenceApplication roomJoin[" + client.getId() + "]"); + clientConnManager.createClient(client.getId(), (IServiceCapableConnection) Red5.getConnectionLocal()); + return true; + } + + @Override + public void appLeave(IClient client, IScope scope) { + log.debug("VoiceConferenceApplication roomLeave[" + client.getId() + "]"); + clientConnManager.removeClient(client.getId()); + log.debug( "Red5SIP Client closing client {}", client.getId()); + + String peerId = (String) Red5.getConnectionLocal().getAttribute("VOICE_CONF_PEER"); + if (peerId != null) { + try { + sipPeerManager.hangup(peerId, client.getId()); + } catch (PeerNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + @Override + public void streamPublishStart(IBroadcastStream stream) { + log.debug("streamPublishStart: {}; {}", stream, stream.getPublishedName()); + System.out.println("streamPublishStart: " + stream.getPublishedName()); + IConnection conn = Red5.getConnectionLocal(); + String peerId = (String) conn.getAttribute("VOICE_CONF_PEER"); + if (peerId != null) { + super.streamPublishStart(stream); + String clientId = conn.getClient().getId(); + sipPeerManager.startTalkStream(peerId, clientId, stream, conn.getScope()); + } + } + + @Override + public void streamBroadcastClose(IBroadcastStream stream) { + System.out.println("streamBroadcastClose: " + stream.getPublishedName()); + IConnection conn = Red5.getConnectionLocal(); + String peerId = (String) conn.getAttribute("VOICE_CONF_PEER"); + if (peerId != null) { + super.streamPublishStart(stream); + String clientId = conn.getClient().getId(); + sipPeerManager.stopTalkStream(peerId, clientId, stream, conn.getScope()); + super.streamBroadcastClose(stream); + } + } + + public List<String> getStreams() { + IConnection conn = Red5.getConnectionLocal(); + return getBroadcastStreamNames( conn.getScope() ); + } + + public void onPing() { + log.debug( "Red5SIP Ping" ); + } + + public void setSipServerHost(String h) { + sipServerHost = h; + } + + public void setUsername(String un) { + this.username = un; + } + + public void setPassword(String pw) { + this.password = pw; + } + + public void setSipPort(int sipPort) { + this.sipPort = sipPort; + } + + public void setStartAudioPort(int startRTPPort) { + this.startAudioPort = startRTPPort; + } + + public void setStopAudioPort(int stopRTPPort) { + this.stopAudioPort = stopRTPPort; + } + + public void setSipPeerManager(SipPeerManager spm) { + sipPeerManager = spm; + } + + public void setClientConnectionManager(ClientConnectionManager ccm) { + clientConnManager = ccm; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/CallStreamFactory.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/CallStreamFactory.java new file mode 100755 index 0000000000000000000000000000000000000000..1602e5ea22ac5c819b194cebf7e4b39da20f2be0 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/CallStreamFactory.java @@ -0,0 +1,18 @@ +package org.bigbluebutton.voiceconf.red5; + +import org.bigbluebutton.voiceconf.red5.media.CallStream; +import org.bigbluebutton.voiceconf.sip.SipConnectInfo; +import org.red5.app.sip.codecs.Codec; +import org.red5.server.api.IScope; + +public class CallStreamFactory { + private IScope scope; + + public CallStream createCallStream(Codec sipCodec, SipConnectInfo connInfo) { + return new CallStream(sipCodec, connInfo, scope); + } + + public void setScope(IScope scope) { + this.scope = scope; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnection.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnection.java new file mode 100755 index 0000000000000000000000000000000000000000..2502819676ba230e3cc2d6d3f13abe55532db2e6 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnection.java @@ -0,0 +1,36 @@ +package org.bigbluebutton.voiceconf.red5; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.service.IServiceCapableConnection; +import org.slf4j.Logger; + +public class ClientConnection { +private static Logger log = Red5LoggerFactory.getLogger(ClientConnection.class, "sip"); + + private final IServiceCapableConnection connection; + private final String connId; + + public ClientConnection(String connId, IServiceCapableConnection connection) { + this.connection = connection; + this.connId = connId; + } + + public String getConnId() { + return connId; + } + + public void onJoinConferenceSuccess(String publishName, String playName) { + log.debug( "SIP Call Connected" ); + connection.invoke("successfullyJoinedVoiceConferenceCallback", new Object[] {publishName, playName}); + } + + public void onJoinConferenceFail() { + log.debug("onOutgoingCallFailed"); + connection.invoke("failedToJoinVoiceConferenceCallback", new Object[] {"onUaCallFailed"}); + } + + public void onLeaveConference() { + log.debug("onCallClosed"); + connection.invoke("disconnectedFromJoinVoiceConferenceCallback", new Object[] {"onUaCallClosed"}); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnectionManager.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnectionManager.java new file mode 100755 index 0000000000000000000000000000000000000000..f6bd9f8c5c68904a8d125358fade06d21a1bea5f --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/ClientConnectionManager.java @@ -0,0 +1,51 @@ +package org.bigbluebutton.voiceconf.red5; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.service.IServiceCapableConnection; +import org.slf4j.Logger; + +public class ClientConnectionManager { + private static Logger log = Red5LoggerFactory.getLogger(ClientConnectionManager.class, "sip"); + + private Map<String, ClientConnection> clients = new ConcurrentHashMap<String, ClientConnection>(); + + public void createClient(String id, IServiceCapableConnection connection) { + ClientConnection cc = new ClientConnection(id, connection); + clients.put(id, cc); + } + + public void removeClient(String id) { + ClientConnection cc = clients.remove(id); + if (cc == null) log.warn("Failed to remove client {}.", id); + } + + public void joinConferenceSuccess(String clientId, String usertalkStream, String userListenStream) { + ClientConnection cc = clients.get(clientId); + if (cc != null) { + cc.onJoinConferenceSuccess(usertalkStream, userListenStream); + } else { + log.warn("Can't find connection {}", clientId); + } + } + + public void joinConferenceFailed(String clientId) { + ClientConnection cc = clients.get(clientId); + if (cc != null) { + cc.onJoinConferenceFail(); + } else { + log.warn("Can't find connection {}", clientId); + } + } + + public void leaveConference(String clientId) { + ClientConnection cc = clients.get(clientId); + if (cc != null) { + cc.onLeaveConference(); + } else { + log.warn("Can't find connection {}", clientId); + } + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Service.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Service.java new file mode 100755 index 0000000000000000000000000000000000000000..17d71a1be87889b582610d6cd4d11f5944bd0053 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/Service.java @@ -0,0 +1,54 @@ +package org.bigbluebutton.voiceconf.red5; + +import java.text.MessageFormat; +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.sip.PeerNotFoundException; +import org.bigbluebutton.voiceconf.sip.SipPeerManager; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IConnection; +import org.red5.server.api.Red5; + +public class Service { + private static Logger log = Red5LoggerFactory.getLogger(Service.class, "sip"); + + private SipPeerManager sipPeerManager; + + private MessageFormat callExtensionPattern = new MessageFormat("{0}"); + + public Boolean call(String peerId, String callerName, String destination) { + log.debug("Joining voice conference " + destination); + String extension = callExtensionPattern.format(new String[] { destination }); + try { + sipPeerManager.call(peerId, getClientId(), callerName, extension); + Red5.getConnectionLocal().setAttribute("VOICE_CONF_PEER", peerId); + return true; + } catch (PeerNotFoundException e) { + log.error("PeerNotFound {}", peerId); + return false; + } + } + + public Boolean hangup(String peerId) { + log.debug("Red5SIP Hangup"); + try { + sipPeerManager.hangup(peerId, getClientId()); + return true; + } catch (PeerNotFoundException e) { + log.error("PeerNotFound {}", peerId); + return false; + } + } + + private String getClientId() { + IConnection conn = Red5.getConnectionLocal(); + return conn.getClient().getId(); + } + + public void setCallExtensionPattern(String callExtensionPattern) { + this.callExtensionPattern = new MessageFormat(callExtensionPattern); + } + + public void setSipPeerManager(SipPeerManager sum) { + sipPeerManager = sum; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/AudioBroadcastStream.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/AudioBroadcastStream.java new file mode 100755 index 0000000000000000000000000000000000000000..c6a750d4ffc0428c7b7d572e43ce907b34779819 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/AudioBroadcastStream.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2008, 2009 by Xuggle Incorporated. All rights reserved. + * + * This file is part of Xuggler. + * + * You can redistribute Xuggler and/or modify it under the terms of the GNU + * Affero General Public License as published by the Free Software + * Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * Xuggler is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public + * License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with Xuggler. If not, see <http://www.gnu.org/licenses/>. + * + */ +package org.bigbluebutton.voiceconf.red5.media; + +import java.io.IOException; +import java.util.Collection; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.event.IEvent; +import org.red5.server.api.stream.IBroadcastStream; +import org.red5.server.api.stream.IStreamCodecInfo; +import org.red5.server.api.stream.IStreamListener; +import org.red5.server.api.stream.ResourceExistException; +import org.red5.server.api.stream.ResourceNotFoundException; +import org.red5.server.messaging.IMessageComponent; +import org.red5.server.messaging.IPipe; +import org.red5.server.messaging.IPipeConnectionListener; +import org.red5.server.messaging.IProvider; +import org.red5.server.messaging.OOBControlMessage; +import org.red5.server.messaging.PipeConnectionEvent; +import org.red5.server.net.rtmp.event.IRTMPEvent; +import org.red5.server.net.rtmp.event.Notify; +import org.red5.server.stream.codec.StreamCodecInfo; +import org.red5.server.stream.message.RTMPMessage; + +import org.slf4j.Logger; + +import org.red5.server.api.stream.IStreamPacket;; + +public class AudioBroadcastStream implements IBroadcastStream, IProvider, IPipeConnectionListener { + final private Logger log = Red5LoggerFactory.getLogger(AudioBroadcastStream.class, "sip"); + + private Set<IStreamListener> streamListeners = new CopyOnWriteArraySet<IStreamListener>(); + private String publishedStreamName; + private IPipe livePipe; + private IScope scope; + + // Codec handling stuff for frame dropping + private StreamCodecInfo streamCodecInfo; + private Long creationTime; + + public AudioBroadcastStream(String name) { + publishedStreamName = name; + livePipe = null; + log.trace("publishedStreamName: {}", name); + + streamCodecInfo = new StreamCodecInfo(); + creationTime = null; + } + + public IProvider getProvider() { + log.trace("getProvider()"); + return this; + } + + public Notify getMetaData() { + log.debug("**** GETTING METADATA ******"); + return null; + } + + public String getPublishedName() { + log.trace("getPublishedName()"); + return publishedStreamName; + } + + public String getSaveFilename() { + log.trace("getSaveFilename()"); + throw new Error("unimplemented method"); + } + + public void addStreamListener(IStreamListener listener) { + log.trace("addStreamListener(listener: {})", listener); + streamListeners.add(listener); + } + + public Collection<IStreamListener> getStreamListeners() { + log.trace("getStreamListeners()"); + return streamListeners; + } + + public void removeStreamListener(IStreamListener listener) { + log.trace("removeStreamListener({})", listener); + streamListeners.remove(listener); + } + + public void saveAs(String filePath, boolean isAppend) + throws IOException, ResourceNotFoundException, ResourceExistException { + log.trace("saveAs(filepath:{}, isAppend:{})", filePath, isAppend); + throw new Error("unimplemented method"); + } + + public void setPublishedName(String name) { + log.trace("setPublishedName(name:{})", name); + publishedStreamName = name; + } + + public void close() { + log.trace("close()"); + } + + public IStreamCodecInfo getCodecInfo() { + // log.trace("getCodecInfo()"); + // we don't support this right now. + return streamCodecInfo; + } + + public String getName() { + log.trace("getName(): {}", publishedStreamName); + // for now, just return the published name + return publishedStreamName; + } + + public void setScope(IScope scope) { + this.scope = scope; + } + + public IScope getScope() { + log.trace("getScope(): {}", scope); + return scope; + } + + public void start() { + log.trace("start()"); + } + + public void stop() { + log.trace("stop"); + } + + public void onOOBControlMessage(IMessageComponent source, IPipe pipe, OOBControlMessage oobCtrlMsg) { + log.trace("onOOBControlMessage"); + } + + public void onPipeConnectionEvent(PipeConnectionEvent event) { + log.trace("onPipeConnectionEvent(event:{})", event); + switch (event.getType()) { + case PipeConnectionEvent.PROVIDER_CONNECT_PUSH: + log.trace("PipeConnectionEvent.PROVIDER_CONNECT_PUSH"); + System.out.println("PipeConnectionEvent.PROVIDER_CONNECT_PUSH"); + if (event.getProvider() == this + && (event.getParamMap() == null + || !event.getParamMap().containsKey("record"))) { + log.trace("Creating a live pipe"); + this.livePipe = (IPipe) event.getSource(); + } + break; + case PipeConnectionEvent.PROVIDER_DISCONNECT: + log.trace("PipeConnectionEvent.PROVIDER_DISCONNECT"); + if (this.livePipe == event.getSource()) { + log.trace("PipeConnectionEvent.PROVIDER_DISCONNECT - this.mLivePipe = null;"); + this.livePipe = null; + } + break; + case PipeConnectionEvent.CONSUMER_CONNECT_PUSH: + log.trace("PipeConnectionEvent.CONSUMER_CONNECT_PUSH"); + break; + case PipeConnectionEvent.CONSUMER_DISCONNECT: + log.trace("PipeConnectionEvent.CONSUMER_DISCONNECT"); + break; + default: + log.trace("PipeConnectionEvent default"); + break; + } + } + + public void dispatchEvent(IEvent event) { +// log.trace("dispatchEvent(event:{})", event); + if (event instanceof IRTMPEvent) { + IRTMPEvent rtmpEvent = (IRTMPEvent) event; + if (livePipe != null) { + RTMPMessage msg = new RTMPMessage(); + msg.setBody(rtmpEvent); + + if (creationTime == null) + creationTime = (long)rtmpEvent.getTimestamp(); + + try { +// log.debug("dispatchEvent(event:)" + event); + livePipe.pushMessage(msg); + + if (rtmpEvent instanceof IStreamPacket) { +// log.debug("dispatchEvent(IStreamPacket:)" + event); + for (IStreamListener listener : getStreamListeners()) { + try { +// log.debug("dispatchEvent(event:)" + event); + listener.packetReceived(this, (IStreamPacket) rtmpEvent); + } catch (Exception e) { + log.error("Error while notifying listener " + listener, e); + } + } + } + } catch (IOException ex) { + log.error("Got exception: {}", ex); + } + } + } + } + + public long getCreationTime() { + return creationTime != null ? creationTime : 0L; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/CallStream.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/CallStream.java new file mode 100755 index 0000000000000000000000000000000000000000..d256684c0209029f2cd76ea74ae21d84d8e50ef2 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/CallStream.java @@ -0,0 +1,73 @@ +package org.bigbluebutton.voiceconf.red5.media; + +import java.net.DatagramSocket; +import java.net.SocketException; +import org.bigbluebutton.voiceconf.red5.media.transcoder.NellyToPcmTranscoder; +import org.bigbluebutton.voiceconf.red5.media.transcoder.PcmToNellyTranscoder; +import org.bigbluebutton.voiceconf.red5.media.transcoder.SpeexToSpeexTranscoder; +import org.bigbluebutton.voiceconf.red5.media.transcoder.Transcoder; +import org.bigbluebutton.voiceconf.sip.SipConnectInfo; +import org.red5.app.sip.codecs.Codec; +import org.red5.app.sip.codecs.SpeexCodec; +import org.slf4j.Logger; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.stream.IBroadcastStream; + +public class CallStream implements StreamObserver { + private final static Logger log = Red5LoggerFactory.getLogger(CallStream.class, "sip"); + + private FlashToSipAudioStream userTalkStream; + private SipToFlashAudioStream userListenStream; + private final Codec sipCodec; + private final SipConnectInfo connInfo; + private final IScope scope; + + public CallStream(Codec sipCodec, SipConnectInfo connInfo, IScope scope) { + this.sipCodec = sipCodec; + this.connInfo = connInfo; + this.scope = scope; + } + + public void start() { + Transcoder rtmpToRtpTranscoder, rtpToRtmpTranscoder; + if (sipCodec.getCodecId() == SpeexCodec.codecId) { + rtmpToRtpTranscoder = new SpeexToSpeexTranscoder(sipCodec); + rtpToRtmpTranscoder = new SpeexToSpeexTranscoder(sipCodec, userListenStream); + } else { + rtmpToRtpTranscoder = new NellyToPcmTranscoder(sipCodec); + rtpToRtmpTranscoder = new PcmToNellyTranscoder(sipCodec); + userListenStream = new SipToFlashAudioStream(scope, rtpToRtmpTranscoder, connInfo.getSocket()); + userListenStream.addListenStreamObserver(this); + ((PcmToNellyTranscoder)rtpToRtmpTranscoder).addTranscodedAudioDataListener(userListenStream); + } + + userTalkStream = new FlashToSipAudioStream(rtmpToRtpTranscoder, connInfo.getSocket(), connInfo); + } + + public String getTalkStreamName() { + return userTalkStream.getStreamName(); + } + + public String getListenStreamName() { + return userListenStream.getStreamName(); + } + + public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) throws StreamException { + userListenStream.start(); + userTalkStream.start(broadcastStream, scope); + } + + public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) { + userTalkStream.stop(broadcastStream, scope); + } + + public void stop() { + userListenStream.stop(); + } + + @Override + public void onStreamStopped() { + + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/FlashToSipAudioStream.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/FlashToSipAudioStream.java new file mode 100755 index 0000000000000000000000000000000000000000..b3c7f6b9e4693e31af7979807321a4597c0ea1b2 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/FlashToSipAudioStream.java @@ -0,0 +1,68 @@ +package org.bigbluebutton.voiceconf.red5.media; + + +import java.net.DatagramSocket; +import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.voiceconf.red5.media.transcoder.Transcoder; +import org.bigbluebutton.voiceconf.sip.SipConnectInfo; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.stream.IBroadcastStream; +import org.red5.server.api.stream.IStreamListener; +import org.red5.server.api.stream.IStreamPacket; +import org.red5.server.net.rtmp.event.AudioData; +import org.red5.server.net.rtmp.event.SerializeUtils; +import org.slf4j.Logger; + +public class FlashToSipAudioStream { + private final static Logger log = Red5LoggerFactory.getLogger(FlashToSipAudioStream.class, "sip"); + + private final Transcoder transcoder; + private IStreamListener mInputListener; + private final DatagramSocket srcSocket; + private final SipConnectInfo connInfo; + private String talkStreamName; + + private RtpStreamSender rtpSender; + + public FlashToSipAudioStream(final Transcoder transcoder, DatagramSocket srcSocket, SipConnectInfo connInfo) { + this.transcoder = transcoder; + this.srcSocket = srcSocket; + this.connInfo = connInfo; + talkStreamName = "microphone_" + System.currentTimeMillis(); + } + + public void start(IBroadcastStream broadcastStream, IScope scope) throws StreamException { + log.debug("startTranscodingStream({},{})", broadcastStream.getPublishedName(), scope.getName()); +// talkStreamName = broadcastStream.getPublishedName(); + + mInputListener = new IStreamListener() { + public void packetReceived(IBroadcastStream broadcastStream, IStreamPacket packet) { + IoBuffer buf = packet.getData(); + if (buf != null) + buf.rewind(); + + if (buf == null || buf.remaining() == 0){ + log.debug("skipping empty packet with no data"); + return; + } + + if (packet instanceof AudioData) { + byte[] data = SerializeUtils.ByteBufferToByteArray(buf); + rtpSender.send(data, 1, data.length-1); + } + } + }; + broadcastStream.addStreamListener(mInputListener); + rtpSender = new RtpStreamSender(transcoder, srcSocket, connInfo); + rtpSender.connect(); + } + + public void stop(IBroadcastStream broadcastStream, IScope scope) { + broadcastStream.removeStreamListener(mInputListener); + } + + public String getStreamName() { + return talkStreamName; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiver.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiver.java new file mode 100755 index 0000000000000000000000000000000000000000..115d461de420234c27155833b4b5a3214f29ce60 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiver.java @@ -0,0 +1,81 @@ +package org.bigbluebutton.voiceconf.red5.media; + +import local.net.RtpPacket; +import local.net.RtpSocket; +import java.io.IOException; +import java.net.DatagramSocket; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.red5.media.transcoder.Transcoder; +import org.red5.logging.Red5LoggerFactory; + +public class RtpStreamReceiver { + protected static Logger log = Red5LoggerFactory.getLogger(RtpStreamReceiver.class, "sip"); + + // Maximum blocking time, spent waiting for reading new bytes [milliseconds] +// private static final int SO_TIMEOUT = 200; + private static int RTP_HEADER_SIZE = 12; + private RtpSocket rtpSocket = null; + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable rtpPacketReceiver; + private volatile boolean receivePackets = false; + private RtpStreamReceiverListener listener; + private Transcoder transcoder; + private final int payloadLength; + + public RtpStreamReceiver(Transcoder transcoder, DatagramSocket socket) { + this.transcoder = transcoder; + this.payloadLength = transcoder.getIncomingEncodedFrameSize(); + rtpSocket = new RtpSocket(socket); + + initializeSocket(); + } + + public void setRtpStreamReceiverListener(RtpStreamReceiverListener listener) { + this.listener = listener; + } + + private void initializeSocket() { +/* try { + rtpSocket.getDatagramSocket().setSoTimeout(SO_TIMEOUT); + } catch (SocketException e1) { + log.warn("SocketException while setting socket block time."); + } +*/ } + + public void start() { + receivePackets = true; + rtpPacketReceiver = new Runnable() { + public void run() { + receiveRtpPackets(); + } + }; + exec.execute(rtpPacketReceiver); + } + + public void stop() { + receivePackets = false; + } + + public void receiveRtpPackets() { + int packetReceivedCounter = 0; + int internalBufferLength = payloadLength + RTP_HEADER_SIZE; + + while (receivePackets) { + try { + byte[] internalBuffer = new byte[internalBufferLength]; + RtpPacket rtpPacket = new RtpPacket(internalBuffer, 0); + rtpSocket.receive(rtpPacket); + packetReceivedCounter++; + transcoder.transcode(rtpPacket.getPayload()); + } catch (IOException e) { + log.error("IOException while receiving rtp packets."); + receivePackets = false; + } + } + log.debug("Rtp Receiver stopped." ); + log.debug("Packet Received = " + packetReceivedCounter + "." ); + if (listener != null) listener.onStoppedReceiving(); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiverListener.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiverListener.java new file mode 100755 index 0000000000000000000000000000000000000000..73eae36fef10a843af8e9adb605c51b14fd6f5f0 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamReceiverListener.java @@ -0,0 +1,6 @@ +package org.bigbluebutton.voiceconf.red5.media; + +public interface RtpStreamReceiverListener { + + void onStoppedReceiving(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamSender.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamSender.java new file mode 100755 index 0000000000000000000000000000000000000000..ab28d29b2411ce6a46bd711648bea408145c43e2 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/RtpStreamSender.java @@ -0,0 +1,77 @@ +package org.bigbluebutton.voiceconf.red5.media; + +import local.net.RtpPacket; +import local.net.RtpSocket; + +import java.io.IOException; +import java.net.DatagramSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; + +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.red5.media.transcoder.Transcoder; +import org.bigbluebutton.voiceconf.sip.SipConnectInfo; +import org.bigbluebutton.voiceconf.util.StackTraceUtil; +import org.red5.logging.Red5LoggerFactory; + +public class RtpStreamSender { + private static Logger log = Red5LoggerFactory.getLogger(RtpStreamSender.class, "sip"); + + private static final int RTP_HEADER_SIZE = 12; + private RtpSocket rtpSocket = null; + private byte[] transcodedAudioDataBuffer; + private RtpPacket rtpPacket; + private int sequenceNum = 0; + private long timestamp = 0; + private final Transcoder transcoder; + private final DatagramSocket srcSocket; + private final SipConnectInfo connInfo; + + public RtpStreamSender(Transcoder transcoder, DatagramSocket srcSocket, SipConnectInfo connInfo) { + this.transcoder = transcoder; + this.srcSocket = srcSocket; + this.connInfo = connInfo; + } + + public void connect() throws StreamException { + try { + rtpSocket = new RtpSocket(srcSocket, InetAddress.getByName(connInfo.getRemoteAddr()), connInfo.getRemotePort()); + init(); + } catch (UnknownHostException e) { + log.error("Failed to connect to {}", connInfo.getRemoteAddr()); + log.error(StackTraceUtil.getStackTrace(e)); + throw new StreamException("Rtp sender failed to connect to " + connInfo.getRemoteAddr() + "."); + } + } + + private void init() { + transcodedAudioDataBuffer = new byte[transcoder.getOutgoingEncodedFrameSize() + RTP_HEADER_SIZE]; + rtpPacket = new RtpPacket(transcodedAudioDataBuffer, 0); + rtpPacket.setPayloadType(transcoder.getCodecId()); + sequenceNum = 0; + timestamp = 0; + } + + public void send(byte[] audioData, int offset, int num) { + transcoder.transcode(audioData, offset, num, transcodedAudioDataBuffer, RTP_HEADER_SIZE, this); + } + + public void sendTranscodedData() throws StreamException { + rtpPacket.setSequenceNumber(sequenceNum++); + timestamp += transcoder.getOutgoingEncodedFrameSize(); + rtpPacket.setTimestamp(timestamp); + rtpPacket.setPayloadLength(transcoder.getOutgoingEncodedFrameSize()); + rtpSocketSend(rtpPacket); + } + + private synchronized void rtpSocketSend(RtpPacket rtpPacket) throws StreamException { + try { + rtpSocket.send(rtpPacket); + timestamp += rtpPacket.getPayloadLength(); + } catch (IOException e) { + log.error("Exception while trying to send rtp packet"); + log.error(StackTraceUtil.getStackTrace(e)); + throw new StreamException("Failed to send data to server."); + } + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java new file mode 100755 index 0000000000000000000000000000000000000000..fe291aa98c08fa03e3df4924d1945d84f257de39 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/SipToFlashAudioStream.java @@ -0,0 +1,87 @@ +package org.bigbluebutton.voiceconf.red5.media; + +import java.net.DatagramSocket; +import org.bigbluebutton.voiceconf.red5.media.transcoder.TranscodedAudioDataListener; +import org.bigbluebutton.voiceconf.red5.media.transcoder.Transcoder; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IContext; +import org.red5.server.api.IScope; +import org.red5.server.net.rtmp.event.AudioData; +import org.red5.server.stream.BroadcastScope; +import org.red5.server.stream.IBroadcastScope; +import org.red5.server.stream.IProviderService; +import org.slf4j.Logger; + +public class SipToFlashAudioStream implements TranscodedAudioDataListener, RtpStreamReceiverListener { + final private Logger log = Red5LoggerFactory.getLogger(SipToFlashAudioStream.class, "sip"); + + private AudioBroadcastStream audioBroadcastStream; + private IScope scope; + private final String listenStreamName; + private RtpStreamReceiver rtpStreamReceiver; + private StreamObserver observer; + private long startTimestamp; + + public SipToFlashAudioStream(IScope scope, Transcoder transcoder, DatagramSocket socket) { + this.scope = scope; + rtpStreamReceiver = new RtpStreamReceiver(transcoder, socket); + rtpStreamReceiver.setRtpStreamReceiverListener(this); + listenStreamName = "speaker_" + System.currentTimeMillis(); + + scope.setName(listenStreamName); + } + + public String getStreamName() { + return listenStreamName; + } + + public void addListenStreamObserver(StreamObserver o) { + observer = o; + } + + public void stop() { + rtpStreamReceiver.stop(); + audioBroadcastStream.stop(); + audioBroadcastStream.close(); + log.debug("stopping and closing stream {}", listenStreamName); + } + + public void start() { + log.debug("started publishing stream in " + scope.getName()); + audioBroadcastStream = new AudioBroadcastStream(listenStreamName); + audioBroadcastStream.setPublishedName(listenStreamName); + audioBroadcastStream.setScope(scope); + + IContext context = scope.getContext(); + + IProviderService providerService = (IProviderService) context.getBean(IProviderService.BEAN_NAME); + if (providerService.registerBroadcastStream(scope, listenStreamName, audioBroadcastStream)){ + IBroadcastScope bScope = (BroadcastScope) providerService.getLiveProviderInput(scope, listenStreamName, true); + + bScope.setAttribute(IBroadcastScope.STREAM_ATTRIBUTE, audioBroadcastStream); + } else{ + log.error("could not register broadcast stream"); + throw new RuntimeException("could not register broadcast stream"); + } + startTimestamp = System.currentTimeMillis(); + audioBroadcastStream.start(); + rtpStreamReceiver.start(); + } + + public void handleTranscodedAudioData(AudioData audioData) { + /* NOTE: + * Don't set the timestamp as it results in choppy audio. Let the client + * play the audio as soon as they receive the packets. (ralam dec 10, 2009) + * + * Let's try this out...if connection to client is slow...audio should be dropped. + */ + audioData.setTimestamp((int)(System.currentTimeMillis() - startTimestamp)); + audioBroadcastStream.dispatchEvent(audioData); + audioData.release(); + } + + @Override + public void onStoppedReceiving() { + if (observer != null) observer.onStreamStopped(); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamException.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamException.java new file mode 100755 index 0000000000000000000000000000000000000000..222bc7b121b36d9580500fd3243295f04c7343b8 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamException.java @@ -0,0 +1,10 @@ +package org.bigbluebutton.voiceconf.red5.media; + +public class StreamException extends Exception { + + private static final long serialVersionUID = 1L; + + public StreamException(String message) { + super(message); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamObserver.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamObserver.java new file mode 100755 index 0000000000000000000000000000000000000000..72656a84569e19f79973a0d865dc6a51cc053808 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/StreamObserver.java @@ -0,0 +1,6 @@ +package org.bigbluebutton.voiceconf.red5.media; + +public interface StreamObserver { + + void onStreamStopped(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/NellyToPcmTranscoder.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/NellyToPcmTranscoder.java new file mode 100755 index 0000000000000000000000000000000000000000..df35d4a45fb8cbc0326ac984d96da163177900fc --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/NellyToPcmTranscoder.java @@ -0,0 +1,177 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.red5.media.RtpStreamSender; +import org.bigbluebutton.voiceconf.red5.media.StreamException; +import org.red5.logging.Red5LoggerFactory; +import org.red5.app.sip.codecs.Codec; +import org.red5.app.sip.codecs.asao.ByteStream; +import org.red5.app.sip.codecs.asao.Decoder; +import org.red5.app.sip.codecs.asao.DecoderMap; + +/** + * Transcodes audio from voice conferencing server to Flash. + * Specifically U-law to Nelly. + * @author Richard Alam + * + */ +public class NellyToPcmTranscoder implements Transcoder { + protected static Logger log = Red5LoggerFactory.getLogger( NellyToPcmTranscoder.class, "sip" ); + + private static final int NELLYMOSER_DECODED_PACKET_SIZE = 256; + private static final int NELLYMOSER_ENCODED_PACKET_SIZE = 64; + + private Codec sipCodec = null; // Sip codec to be used on audio session + private Decoder decoder; + private DecoderMap decoderMap; + float[] tempBuffer; // Temporary buffer with received PCM audio from FlashPlayer. + int tempBufferRemaining = 0; // Floats remaining on temporary buffer. + float[] encodingBuffer; // Encoding buffer used to encode to final codec format; + int encodingOffset = 0; // Offset of encoding buffer. + boolean asao_buffer_processed = false; // Indicates whether the current asao buffer was processed. + boolean hasInitilializedBuffers = false; // Indicates whether the handling buffers have already been initialized. + + public NellyToPcmTranscoder(Codec sipCodec) { + this.sipCodec = sipCodec; + decoder = new Decoder(); + decoderMap = null; + } + + public int getOutgoingEncodedFrameSize() { + return sipCodec.getOutgoingEncodedFrameSize(); + } + + public int getCodecId() { + return sipCodec.getCodecId(); + } + + public int getOutgoingPacketization() { + return sipCodec.getOutgoingPacketization(); + } + + private int fillRtpPacketBuffer(byte[] audioData, byte[] transcodedData, int dataOffset) { + int copyingSize = 0; + int finalCopySize = 0; + byte[] codedBuffer = new byte[sipCodec.getOutgoingEncodedFrameSize()]; + + try { + if ((tempBufferRemaining + encodingOffset) >= sipCodec.getOutgoingDecodedFrameSize()) { + copyingSize = encodingBuffer.length - encodingOffset; + + System.arraycopy(tempBuffer, tempBuffer.length-tempBufferRemaining, + encodingBuffer, encodingOffset, copyingSize); + + encodingOffset = sipCodec.getOutgoingDecodedFrameSize(); + tempBufferRemaining -= copyingSize; + finalCopySize = sipCodec.getOutgoingDecodedFrameSize(); + } + else { + if (tempBufferRemaining > 0) { + System.arraycopy(tempBuffer, tempBuffer.length - tempBufferRemaining, + encodingBuffer, encodingOffset, tempBufferRemaining); + + encodingOffset += tempBufferRemaining; + finalCopySize += tempBufferRemaining; + tempBufferRemaining = 0; + } + + // Decode new asao packet. + asao_buffer_processed = true; + ByteStream audioStream = new ByteStream(audioData, 1, NELLYMOSER_ENCODED_PACKET_SIZE); + decoderMap = decoder.decode(decoderMap, audioStream.bytes, 1, tempBuffer, 0); + + //tempBuffer = ResampleUtils.normalize(tempBuffer, 256); // normalise volume + tempBufferRemaining = tempBuffer.length; + + if (tempBuffer.length <= 0) { + log.error("Asao decoder Error." ); + } + + // Try to complete the encodingBuffer with necessary data. + if ((encodingOffset + tempBufferRemaining) > sipCodec.getOutgoingDecodedFrameSize()) { + copyingSize = encodingBuffer.length - encodingOffset; + } + else { + copyingSize = tempBufferRemaining; + } + + System.arraycopy(tempBuffer, 0, encodingBuffer, encodingOffset, copyingSize); + + encodingOffset += copyingSize; + tempBufferRemaining -= copyingSize; + finalCopySize += copyingSize; + } + + if (encodingOffset == encodingBuffer.length) { + int encodedBytes = sipCodec.pcmToCodec( encodingBuffer, codedBuffer ); + + if (encodedBytes == sipCodec.getOutgoingEncodedFrameSize()) { + System.arraycopy(codedBuffer, 0, transcodedData, dataOffset, codedBuffer.length); + } + else { + log.error("Failure encoding buffer." ); + } + } + } + catch (Exception e) { + log.error("Exception - " + e.toString()); + e.printStackTrace(); + } + + return finalCopySize; + } + + public void transcode(byte[] asaoBuffer, int offset, int num, byte[] transcodedData, int dataOffset, RtpStreamSender rtpSender) { + asao_buffer_processed = false; + + if (!hasInitilializedBuffers) { + tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE]; + encodingBuffer = new float[sipCodec.getOutgoingDecodedFrameSize()]; + hasInitilializedBuffers = true; + } + + if (num > 0) { + do { + int encodedBytes = fillRtpPacketBuffer( asaoBuffer, transcodedData, dataOffset ); +// log.debug( "send " + sipCodec.getCodecName() + " encoded " + encodedBytes + " bytes." ); + + if ( encodedBytes == 0 ) { + break; + } + + if (encodingOffset == sipCodec.getOutgoingDecodedFrameSize()) { +// log.debug("Send this audio to asterisk."); + try { + rtpSender.sendTranscodedData(); + } catch (StreamException e) { + // Swallow this error for now. We don't really want to end the call if sending hiccups. + // Just log it for now. (ralam june 18, 2010) + log.warn("Error while sending transcoded audio packet."); + } + encodingOffset = 0; + } + +// log.debug("asao_buffer_processed = [" + asao_buffer_processed + "] ." ); + } + while (!asao_buffer_processed); + } + else if (num < 0) { + log.debug("Closing" ); + } + } + + /** + * Not implemented. Implemented by transcoders for voice conf server to Flash. + */ + public void transcode(byte[] codedBuffer) { + log.error("Not implemented."); + } + + /** + * Not implemented. Implemented by transcoders for voice conf server to Flash. + */ + public int getIncomingEncodedFrameSize() { + log.error("Not implemented."); + return 0; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/PcmToNellyTranscoder.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/PcmToNellyTranscoder.java new file mode 100755 index 0000000000000000000000000000000000000000..1d7200c1a551f0360225d809c04c934f7581a298 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/PcmToNellyTranscoder.java @@ -0,0 +1,134 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.slf4j.Logger; +import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.voiceconf.red5.media.RtpStreamSender; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.net.rtmp.event.AudioData; + +import org.red5.app.sip.codecs.Codec; +import org.red5.app.sip.codecs.asao.ByteStream; +import org.red5.app.sip.codecs.asao.CodecImpl; + +public class PcmToNellyTranscoder implements Transcoder { + protected static Logger log = Red5LoggerFactory.getLogger(PcmToNellyTranscoder.class, "sip"); + + private static final int NELLYMOSER_DECODED_PACKET_SIZE = 256; + private static final int NELLYMOSER_ENCODED_PACKET_SIZE = 64; + private static final int NELLYMOSER_CODEC_ID = 82; + + private float[] encoderMap; + private Codec audioCodec = null; + private float[] tempBuffer; // Temporary buffer with PCM audio to be sent to FlashPlayer. + private int tempBufferOffset = 0; + private TranscodedAudioDataListener listener; + private long start = 0; + + public PcmToNellyTranscoder(Codec audioCodec) { + this.audioCodec = audioCodec; + + encoderMap = new float[64]; + tempBuffer = new float[NELLYMOSER_DECODED_PACKET_SIZE]; + start = System.currentTimeMillis(); + } + + public void addTranscodedAudioDataListener(TranscodedAudioDataListener listener) { + this.listener = listener; + } + + /** + * Fills the tempBuffer with necessary PCM's floats and encodes + * the audio to be sent to FlashPlayer. + */ + private void forwardAudioToFlashPlayer(float[] pcmBuffer) { + int pcmBufferOffset = 0; + int copySize = 0; + boolean pcmBufferProcessed = false; + + do { + if ((tempBuffer.length - tempBufferOffset) <= (pcmBuffer.length - pcmBufferOffset)) { + copySize = tempBuffer.length - tempBufferOffset; + } + else { + copySize = pcmBuffer.length - pcmBufferOffset; + } + + System.arraycopy( pcmBuffer, pcmBufferOffset, tempBuffer, tempBufferOffset, copySize); + + tempBufferOffset += copySize; + pcmBufferOffset += copySize; + + if (tempBufferOffset == NELLYMOSER_DECODED_PACKET_SIZE) { + ByteStream encodedStream = new ByteStream(NELLYMOSER_ENCODED_PACKET_SIZE); + encoderMap = CodecImpl.encode(encoderMap, tempBuffer, encodedStream.bytes); + pushAudio(encodedStream.bytes); + + tempBufferOffset = 0; + } + + if ( pcmBufferOffset == pcmBuffer.length ) { + pcmBufferProcessed = true; + } + } + while (!pcmBufferProcessed); + } + + public void transcode(byte[] codedBuffer) { + float[] decodingBuffer = new float[codedBuffer.length]; + int decodedBytes = audioCodec.codecToPcm(codedBuffer, decodingBuffer); + +// log.debug("incoming = " + codedBuffer.length + ", encodedBytes = " + decodedBytes + ", incomingDecodedFrameSize = " + +// audioCodec.getIncomingDecodedFrameSize() + "." ); + + if (decodedBytes == audioCodec.getIncomingDecodedFrameSize()) { + forwardAudioToFlashPlayer(decodingBuffer); + } else { + log.warn("Failure decoding buffer." ); + } + + } + + private void pushAudio(byte[] audio) { + IoBuffer buffer = IoBuffer.allocate(1024); + buffer.setAutoExpand(true); + + buffer.clear(); + + buffer.put((byte) NELLYMOSER_CODEC_ID); + byte[] copy = new byte[audio.length]; + System.arraycopy(audio, 0, copy, 0, audio.length ); + + buffer.put(copy); + buffer.flip(); + + AudioData audioData = new AudioData(buffer); + audioData.setTimestamp((int)(System.currentTimeMillis() - start) ); + if (listener != null) + listener.handleTranscodedAudioData(audioData); + else + log.warn("No interested parties for transcoded audio."); + } + + public int getIncomingEncodedFrameSize() { + return audioCodec.getIncomingEncodedFrameSize(); + } + + /** + * Not implemented. Implemented by transcoders for FP to voice conference server. + */ + public void transcode(byte[] asaoBuffer, int offset, int num, + byte[] transcodedData, int dataOffset, RtpStreamSender rtpSender) { + } + + public int getCodecId() { + return 0; + } + + public int getOutgoingEncodedFrameSize() { + return 0; + } + + public int getOutgoingPacketization() { + return 0; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/SpeexToSpeexTranscoder.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/SpeexToSpeexTranscoder.java new file mode 100755 index 0000000000000000000000000000000000000000..e0030314468413e1fbff9467b98f50f4ccbde838 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/SpeexToSpeexTranscoder.java @@ -0,0 +1,81 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.apache.mina.core.buffer.IoBuffer; +import org.bigbluebutton.voiceconf.red5.media.RtpStreamSender; +import org.bigbluebutton.voiceconf.red5.media.StreamException; +import org.red5.app.sip.codecs.Codec; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.net.rtmp.event.AudioData; +import org.slf4j.Logger; + +public class SpeexToSpeexTranscoder implements Transcoder { + protected static Logger log = Red5LoggerFactory.getLogger(SpeexToSpeexTranscoder.class, "sip"); + + private Codec audioCodec; + private TranscodedAudioDataListener listener; + private int timestamp = 0; + + private static final int SPEEX_CODEC = 178; /* 1011 1111 (see flv spec) */ + + public SpeexToSpeexTranscoder(Codec audioCodec, TranscodedAudioDataListener listener) { + this.audioCodec = audioCodec; + this.listener = listener; + } + + public SpeexToSpeexTranscoder(Codec audioCodec) { + this.audioCodec = audioCodec; + } + + public void transcode(byte[] asaoBuffer, int offset, int num, + byte[] transcodedData, int dataOffset, RtpStreamSender rtpSender) { + System.arraycopy(asaoBuffer, offset, transcodedData, dataOffset, num); + try { + rtpSender.sendTranscodedData(); + } catch (StreamException e) { + // Swallow this error for now. We don't really want to end the call if sending hiccups. + // Just log it for now. (ralam june 18, 2010) + log.warn("Error while sending transcoded audio packet."); + } + } + + public void transcode(byte[] codedBuffer) { + pushAudio(codedBuffer); + } + + private void pushAudio(byte[] audio) { + timestamp = timestamp + audio.length; + + IoBuffer buffer = IoBuffer.allocate(1024); + buffer.setAutoExpand(true); + + buffer.clear(); + + buffer.put((byte) SPEEX_CODEC); + byte[] copy = new byte[audio.length]; + System.arraycopy(audio, 0, copy, 0, audio.length ); + + buffer.put(copy); + buffer.flip(); + + AudioData audioData = new AudioData( buffer ); + audioData.setTimestamp((int)timestamp ); + + listener.handleTranscodedAudioData(audioData); + } + + public int getCodecId() { + return audioCodec.getCodecId(); + } + + public int getOutgoingEncodedFrameSize() { + return audioCodec.getOutgoingEncodedFrameSize(); + } + + public int getOutgoingPacketization() { + return audioCodec.getOutgoingPacketization(); + } + + public int getIncomingEncodedFrameSize() { + return audioCodec.getIncomingEncodedFrameSize(); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedAudioDataListener.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedAudioDataListener.java new file mode 100755 index 0000000000000000000000000000000000000000..56162a7a6fb7585f92a42ddfc1fa876ab5f9f5a6 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedAudioDataListener.java @@ -0,0 +1,8 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.red5.server.net.rtmp.event.AudioData; + +public interface TranscodedAudioDataListener { + + public void handleTranscodedAudioData(AudioData audioData); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedPcmAudioBuffer.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedPcmAudioBuffer.java new file mode 100755 index 0000000000000000000000000000000000000000..1f1a9bc8824fe4cef9b610058846fa28dea21237 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/TranscodedPcmAudioBuffer.java @@ -0,0 +1,28 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.red5.app.sip.stream.RtpStreamSender; + +public class TranscodedPcmAudioBuffer { + + private byte[] buffer; + private int offset; + private RtpStreamSender sender; + + TranscodedPcmAudioBuffer(byte[] data, int offset, RtpStreamSender sender) { + buffer = data; + this.offset = offset; + } + + boolean copyData(byte[] data) { + if (data.length > buffer.length - offset) + return false; + + System.arraycopy(data, 0, buffer, offset, data.length); + return true; + } + + void sendData() { + sender.sendTranscodedData(); + } + +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/Transcoder.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/Transcoder.java new file mode 100755 index 0000000000000000000000000000000000000000..960171fdb7fae252cdc0fef2ae74c4fffb21522e --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/red5/media/transcoder/Transcoder.java @@ -0,0 +1,15 @@ +package org.bigbluebutton.voiceconf.red5.media.transcoder; + +import org.bigbluebutton.voiceconf.red5.media.RtpStreamSender; + +public interface Transcoder { + void transcode(byte[] asaoBuffer, int offset, int num, byte[] transcodedData, int dataOffset, RtpStreamSender rtpSender); + void transcode(byte[] codedBuffer); + + int getOutgoingEncodedFrameSize(); + + int getCodecId(); + + int getOutgoingPacketization(); + int getIncomingEncodedFrameSize(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/AudioConferenceProvider.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/AudioConferenceProvider.java new file mode 100755 index 0000000000000000000000000000000000000000..334e56db61ea84ddba9609c2e461fbd3a3bc0b52 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/AudioConferenceProvider.java @@ -0,0 +1,34 @@ +package org.bigbluebutton.voiceconf.sip; + +public class AudioConferenceProvider { + private final String host; + private final int sipPort; + private final int startAudioPort; + private final int stopAudioPort; + private int curAvailablePort; + + public AudioConferenceProvider(String host, int sipPort, int startAudioPort, int stopAudioPort) { + this.host = host; + this.sipPort = sipPort; + this.startAudioPort = startAudioPort; + this.stopAudioPort = stopAudioPort; + curAvailablePort = startAudioPort; + } + + public int getFreeAudioPort() { + synchronized(this) { + int availablePort = curAvailablePort; + curAvailablePort++; + if (curAvailablePort > stopAudioPort) curAvailablePort = startAudioPort; + return availablePort; + } + } + + public String getHost() { + return host; + } + + public int getStartAudioPort() { + return startAudioPort; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallAgent.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallAgent.java new file mode 100755 index 0000000000000000000000000000000000000000..3bab0363c834d2d267963758be8c4c36870f3a66 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallAgent.java @@ -0,0 +1,365 @@ +package org.bigbluebutton.voiceconf.sip; + +import org.zoolu.sip.call.*; +import org.zoolu.sip.provider.SipProvider; +import org.zoolu.sip.provider.SipStack; +import org.zoolu.sip.message.*; +import org.zoolu.sdp.*; +import org.bigbluebutton.voiceconf.red5.CallStreamFactory; +import org.bigbluebutton.voiceconf.red5.ClientConnectionManager; +import org.bigbluebutton.voiceconf.red5.media.CallStream; +import org.bigbluebutton.voiceconf.red5.media.StreamException; +import org.red5.app.sip.codecs.Codec; +import org.red5.app.sip.codecs.CodecUtils; +import org.slf4j.Logger; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.stream.IBroadcastStream; +import org.zoolu.tools.Parser; + +import java.net.DatagramSocket; +import java.net.SocketException; +import java.util.Enumeration; +import java.util.Vector; + +public class CallAgent extends CallListenerAdapter { + private static Logger log = Red5LoggerFactory.getLogger(CallAgent.class, "sip"); + + private final SipPeerProfile userProfile; + private final SipProvider sipProvider; + private ExtendedCall call; + private CallStream callStream; + private String localSession = null; + private Codec sipCodec = null; + private CallStreamFactory callStreamFactory; + private ClientConnectionManager clientConnManager; + private final String clientId; + private final AudioConferenceProvider portProvider; + private DatagramSocket localSocket; + + private enum CallState { + UA_IDLE(0), UA_INCOMING_CALL(1), UA_OUTGOING_CALL(2), UA_ONCALL(3); + private final int state; + CallState(int state) {this.state = state;} + private int getState() {return state;} + } + + private CallState callState; + + public CallAgent(SipProvider sipProvider, SipPeerProfile userProfile, AudioConferenceProvider portProvider, String clientId) { + this.sipProvider = sipProvider; + this.userProfile = userProfile; + this.portProvider = portProvider; + this.clientId = clientId; + } + + public String getCallId() { + return clientId; + } + + private void initSessionDescriptor() { + log.debug("initSessionDescriptor"); + SessionDescriptor newSdp = SdpUtils.createInitialSdp(userProfile.username, + sipProvider.getViaAddress(), userProfile.audioPort, + userProfile.videoPort, userProfile.audioCodecsPrecedence ); + localSession = newSdp.toString(); + log.debug("localSession Descriptor = " + localSession ); + } + + public void call(String callerName, String destination) { + log.debug("call {}", destination); + try { + localSocket = getLocalAudioSocket(); + userProfile.audioPort = localSocket.getLocalPort(); + } catch (Exception e) { + notifyListenersOnOutgoingCallFailed(); + return; + } + + setupCallerDisplayName(callerName, destination); + userProfile.initContactAddress(sipProvider); + initSessionDescriptor(); + + callState = CallState.UA_OUTGOING_CALL; + + call = new ExtendedCall(sipProvider, userProfile.fromUrl, + userProfile.contactUrl, userProfile.username, + userProfile.realm, userProfile.passwd, this); + + // In case of incomplete url (e.g. only 'user' is present), + // try to complete it. + destination = sipProvider.completeNameAddress(destination).toString(); + log.debug("call {}", destination); + if (userProfile.noOffer) { + call.call(destination); + } else { + call.call(destination, localSession); + } + } + + private void setupCallerDisplayName(String callerName, String destination) { + String fromURL = "\"" + callerName + "\" <sip:" + destination + "@" + portProvider.getHost() + ">"; + userProfile.username = callerName; + userProfile.fromUrl = fromURL; + userProfile.contactUrl = "sip:" + destination + "@" + sipProvider.getViaAddress(); + if (sipProvider.getPort() != SipStack.default_port) { + userProfile.contactUrl += ":" + sipProvider.getPort(); + } + } + + /** Closes an ongoing, incoming, or pending call */ + public void hangup() { + log.debug("hangup"); + + if (callState == CallState.UA_IDLE) return; + closeVoiceStreams(); + if (call != null) call.hangup(); + callState = CallState.UA_IDLE; + } + + private DatagramSocket getLocalAudioSocket() throws Exception { + DatagramSocket socket = null; + boolean failedToGetSocket = true; + + for (int i = 0; i < 3; i++) { + try { + socket = new DatagramSocket(portProvider.getFreeAudioPort()); + failedToGetSocket = false; + break; + } catch (SocketException e) { + log.error("Failed to setup local audio socket."); + } + } + + if (failedToGetSocket) { + throw new Exception("Exception while initializing CallStream"); + } + + return socket; + } + + private void createVoiceStreams() { + if (callStream != null) { + log.debug("Media application is already running."); + return; + } + + SessionDescriptor localSdp = new SessionDescriptor(call.getLocalSessionDescriptor()); + SessionDescriptor remoteSdp = new SessionDescriptor(call.getRemoteSessionDescriptor()); + String remoteMediaAddress = SessionDescriptorUtil.getRemoteMediaAddress(remoteSdp); + int remoteAudioPort = SessionDescriptorUtil.getRemoteAudioPort(remoteSdp); + int localAudioPort = SessionDescriptorUtil.getLocalAudioPort(localSdp); + + SipConnectInfo connInfo = new SipConnectInfo(localSocket, remoteMediaAddress, remoteAudioPort); + + log.debug("[localAudioPort=" + localAudioPort + ",remoteAudioPort=" + remoteAudioPort + "]"); + + if (userProfile.audio && localAudioPort != 0 && remoteAudioPort != 0) { + if ((callStream == null) && (sipCodec != null)) { + try { + callStream = callStreamFactory.createCallStream(sipCodec, connInfo); + callStream.start(); + notifyListenersOnCallConnected(callStream.getTalkStreamName(), callStream.getListenStreamName()); + } catch (Exception e) { + log.error("Failed to create Call Stream."); + } + } + } + } + + + public void startTalkStream(IBroadcastStream broadcastStream, IScope scope) { + try { + callStream.startTalkStream(broadcastStream, scope); + } catch (StreamException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public void stopTalkStream(IBroadcastStream broadcastStream, IScope scope) { + if (callStream != null) { + callStream.stopTalkStream(broadcastStream, scope); + } + } + + private void closeVoiceStreams() { + log.debug("closeMediaApplication" ); + + if (callStream != null) { + callStream.stop(); + callStream = null; + } + } + + // ********************** Call callback functions ********************** + private void createAudioCodec(SessionDescriptor newSdp) { + sipCodec = SdpUtils.getNegotiatedAudioCodec(newSdp); + } + + private void setupSdpAndCodec(String sdp) { + SessionDescriptor remoteSdp = new SessionDescriptor(sdp); + SessionDescriptor localSdp = new SessionDescriptor(localSession); + + log.debug("localSdp = " + localSdp.toString() + "."); + log.debug("remoteSdp = " + remoteSdp.toString() + "."); + + // First we need to make payloads negotiation so the related attributes can be then matched. + SessionDescriptor newSdp = SdpUtils.makeMediaPayloadsNegotiation(localSdp, remoteSdp); + createAudioCodec(newSdp); + + // Now we complete the SDP negotiation informing the selected + // codec, so it can be internally updated during the process. + SdpUtils.completeSdpNegotiation(newSdp, localSdp, remoteSdp); + localSession = newSdp.toString(); + + log.debug("newSdp = " + localSession + "." ); + + // Finally, we use the "newSdp" and "remoteSdp" to initialize the lasting codec informations. + CodecUtils.initSipAudioCodec(sipCodec, userProfile.audioDefaultPacketization, + userProfile.audioDefaultPacketization, newSdp, remoteSdp); + } + + + /** Callback function called when arriving a 2xx (call accepted) + * The user has managed to join the conference. + */ + public void onCallAccepted(Call call, String sdp, Message resp) { + log.debug("Received 200/OK. So user has successfully joined the conference."); + if (!isCurrentCall(call)) return; + + log.debug("ACCEPTED/CALL."); + callState = CallState.UA_ONCALL; + + setupSdpAndCodec(sdp); + + if (userProfile.noOffer) { + // Answer with the local sdp. + call.ackWithAnswer(localSession); + } + + createVoiceStreams(); + } + + /** Callback function called when arriving an ACK method (call confirmed) */ + public void onCallConfirmed(Call call, String sdp, Message ack) { + log.debug("Received ACK. Hmmm...is this for when the server initiates the call????"); + + if (!isCurrentCall(call)) return; + callState = CallState.UA_ONCALL; + createVoiceStreams(); + } + + /** Callback function called when arriving a 4xx (call failure) */ + public void onCallRefused(Call call, String reason, Message resp) { + log.debug("Call has been refused."); + if (!isCurrentCall(call)) return; + callState = CallState.UA_IDLE; + notifyListenersOnOutgoingCallFailed(); + } + + /** Callback function called when arriving a 3xx (call redirection) */ + public void onCallRedirection(Call call, String reason, Vector contact_list, Message resp) { + log.debug("onCallRedirection"); + + if (!isCurrentCall(call)) return; + call.call(((String) contact_list.elementAt(0))); + } + + + /** + * Callback function that may be overloaded (extended). Called when arriving a CANCEL request + */ + public void onCallCanceling(Call call, Message cancel) { + log.error("Server shouldn't cancel call...or does it???"); + + if (!isCurrentCall(call)) return; + + log.debug("Server has CANCEL-led the call."); + callState = CallState.UA_IDLE; + notifyListenersOfOnIncomingCallCancelled(); + } + + private void notifyListenersOnCallConnected(String talkStream, String listenStream) { + log.debug("notifyListenersOnCallConnected for {}", clientId); + clientConnManager.joinConferenceSuccess(clientId, talkStream, listenStream); + } + + private void notifyListenersOnOutgoingCallFailed() { + log.debug("notifyListenersOnOutgoingCallFailed for {}", clientId); + clientConnManager.joinConferenceFailed(clientId); + cleanup(); + } + + + private void notifyListenersOfOnIncomingCallCancelled() { + log.debug("notifyListenersOfOnIncomingCallCancelled for {}", clientId); + } + + private void notifyListenersOfOnCallClosed() { + log.debug("notifyListenersOfOnCallClosed for {}", clientId); + clientConnManager.leaveConference(clientId); + cleanup(); + } + + private void cleanup() { + localSocket.close(); + } + + /** Callback function called when arriving a BYE request */ + public void onCallClosing(Call call, Message bye) { + log.debug("onCallClosing"); + + if (!isCurrentCall(call)) return; + + log.debug("CLOSE."); + + closeVoiceStreams(); + + notifyListenersOfOnCallClosed(); + callState = CallState.UA_IDLE; + + // Reset local sdp for next call. + initSessionDescriptor(); + } + + + /** + * Callback function called when arriving a response after a BYE request + * (call closed) + */ + public void onCallClosed(Call call, Message resp) { + log.debug("onCallClosed"); + + if (!isCurrentCall(call)) return; + log.debug("CLOSE/OK."); + + notifyListenersOfOnCallClosed(); + callState = CallState.UA_IDLE; + } + + + /** Callback function called when the invite expires */ + public void onCallTimeout(Call call) { + log.debug("onCallTimeout"); + + if (!isCurrentCall(call)) return; + + log.debug("NOT FOUND/TIMEOUT."); + callState = CallState.UA_IDLE; + + notifyListenersOnOutgoingCallFailed(); + } + + private boolean isCurrentCall(Call call) { + return this.call == call; + } + + public void setCallStreamFactory(CallStreamFactory csf) { + this.callStreamFactory = csf; + } + + public void setClientConnectionManager(ClientConnectionManager ccm) { + clientConnManager = ccm; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallManager.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallManager.java new file mode 100755 index 0000000000000000000000000000000000000000..bbaeae73aac883ad3e1de0e7d55ae80ff47b194c --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/CallManager.java @@ -0,0 +1,26 @@ +package org.bigbluebutton.voiceconf.sip; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class CallManager { + + private final Map<String, CallAgent> calls = new ConcurrentHashMap<String, CallAgent>(); + + public CallAgent add(CallAgent ca) { + return calls.put(ca.getCallId(), ca); + } + + public CallAgent remove(String id) { + return calls.remove(id); + } + + public CallAgent get(String id) { + return calls.get(id); + } + + public Collection<CallAgent> getAll() { + return calls.values(); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/OptionMethodListener.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/OptionMethodListener.java new file mode 100755 index 0000000000000000000000000000000000000000..9b2552b45110b48e9582d4335bad9feab0c72e31 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/OptionMethodListener.java @@ -0,0 +1,22 @@ +package org.bigbluebutton.voiceconf.sip; + +import org.red5.logging.Red5LoggerFactory; +import org.slf4j.Logger; +import org.zoolu.sip.message.Message; +import org.zoolu.sip.message.MessageFactory; +import org.zoolu.sip.provider.SipProvider; +import org.zoolu.sip.provider.SipProviderListener; + +public class OptionMethodListener implements SipProviderListener { + + protected static Logger log = Red5LoggerFactory.getLogger( OptionMethodListener.class, "sip" ); + + public void onReceivedMessage(SipProvider sip_provider, Message message) { + if (message.isOption()) { + log.debug("Received OPTION message"); + Message response = MessageFactory.createResponse(message, 200, "OK", message.getFromHeader().getNameAddress()); + sip_provider.sendMessage(response); + } + } + +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/PeerNotFoundException.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/PeerNotFoundException.java new file mode 100755 index 0000000000000000000000000000000000000000..a65c7f50aec2c73721abaa01e5c2a04530db823c --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/PeerNotFoundException.java @@ -0,0 +1,10 @@ +package org.bigbluebutton.voiceconf.sip; + +public class PeerNotFoundException extends Exception { + + private static final long serialVersionUID = 1L; + + public PeerNotFoundException(String message) { + super(message); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/ScopeProvider.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/ScopeProvider.java new file mode 100755 index 0000000000000000000000000000000000000000..88b7d5d2de6ca6e6f39c82908170d1d8eb8a120f --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/ScopeProvider.java @@ -0,0 +1,8 @@ +package org.bigbluebutton.voiceconf.sip; + +import org.red5.server.api.IScope; + +public interface ScopeProvider { + + public IScope getScope(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SdpUtils.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SdpUtils.java new file mode 100755 index 0000000000000000000000000000000000000000..525b8d39b8b2e886884070a3645b1f9071e4d483 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SdpUtils.java @@ -0,0 +1,535 @@ +package org.bigbluebutton.voiceconf.sip; + +import java.util.Enumeration; +import java.util.Vector; +import org.red5.app.sip.codecs.Codec; +import org.red5.app.sip.codecs.CodecFactory; +import org.zoolu.sdp.AttributeField; +import org.zoolu.sdp.MediaDescriptor; +import org.zoolu.sdp.MediaField; +import org.zoolu.sdp.SessionDescriptor; +import org.slf4j.Logger; +import org.red5.logging.Red5LoggerFactory; + +public class SdpUtils { + protected static Logger log = Red5LoggerFactory.getLogger(SdpUtils.class, "sip"); + + + /** + * @return Returns the audio codec to be used on current session. + */ + public static Codec getNegotiatedAudioCodec(SessionDescriptor negotiatedSDP){ + int payloadId; + String rtpmap; + Codec sipCodec = null; + + MediaDescriptor md = negotiatedSDP.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO ); + rtpmap = md.getAttribute(Codec.ATTRIBUTE_RTPMAP).getAttributeValue(); + + if (!rtpmap.isEmpty()) { + payloadId = Integer.parseInt(rtpmap.substring(0, rtpmap.indexOf(" "))); + + sipCodec = CodecFactory.getInstance().getSIPAudioCodec(payloadId); + if (sipCodec == null) { + log.error("Negotiated codec {} not found", payloadId); + } + else { + log.info("Found codec: payloadType={}, payloadName={}.", sipCodec.getCodecId(), + sipCodec.getCodecName()); + } + } + return sipCodec; + } + + + /** + * + * @param userName + * @param viaAddress + * + * @return Return the initial local SDP. + */ + public static SessionDescriptor createInitialSdp(String userName, String viaAddress, + int audioPort, int videoPort, String audioCodecsPrecedence) { + + SessionDescriptor initialDescriptor = null; + + try { +// log.debug("userName = [" + userName + "], viaAddress = [" + viaAddress + +// "], audioPort = [" + audioPort + "], videoPort = [" + videoPort + +// "], audioCodecsPrecedence = [" + audioCodecsPrecedence + "]." ); + + int audioCodecsNumber = CodecFactory.getInstance().getAvailableAudioCodecsCount(); + int videoCodecsNumber = CodecFactory.getInstance().getAvailableVideoCodecsCount(); + + if ((audioCodecsNumber == 0) && (videoCodecsNumber == 0)) { + log.debug("audioCodecsNumber = [" + audioCodecsNumber + + "], videoCodecsNumber = [" + videoCodecsNumber + "]."); + return null; + } + + initialDescriptor = new SessionDescriptor(userName, viaAddress); + + if (initialDescriptor == null) { + log.error("Error instantiating the initialDescriptor!"); + return null; + } + + if (audioCodecsNumber > 0) { + Codec[] audioCodecs; + Vector audioAttributes = new Vector(); + + if (audioCodecsPrecedence.isEmpty()) { + audioCodecs = CodecFactory.getInstance().getAvailableAudioCodecs(); + } else { + audioCodecs = CodecFactory.getInstance().getAvailableAudioCodecsWithPrecedence(audioCodecsPrecedence); + } + + for (int audioIndex = 0; audioIndex < audioCodecsNumber; audioIndex++) { + String payloadId = String.valueOf(audioCodecs[audioIndex].getCodecId()); + String rtpmapParamValue = payloadId; + rtpmapParamValue += " " + audioCodecs[audioIndex].getCodecName(); + rtpmapParamValue += "/" + audioCodecs[audioIndex].getSampleRate() + "/1"; + +// log.debug("Adding rtpmap for payload [" + payloadId + +// "] with value = [" + rtpmapParamValue + "]." ); + + audioAttributes.add(new AttributeField(Codec.ATTRIBUTE_RTPMAP, rtpmapParamValue)); + + String[] codecMediaAttributes = audioCodecs[audioIndex].getCodecMediaAttributes(); + + if (codecMediaAttributes != null) { +// log.debug("Adding " + codecMediaAttributes.length + +// " audio codec media attributes." ); + + for (int attribIndex = 0; attribIndex < codecMediaAttributes.length; attribIndex++) { +// log.debug("Adding audio media attribute [" + +// codecMediaAttributes[attribIndex] + "]." ); + + AttributeField newAttribute = parseAttributeField(codecMediaAttributes[attribIndex]); + + if (newAttribute != null) { + audioAttributes.add(newAttribute); + } + } + } else { + log.warn("Audio codec has no especific media attributes." ); + } + } + + // Calculate the format list to be used on MediaDescriptor creation. + String formatList = getFormatList(audioAttributes); + + for (Enumeration attributesEnum = audioAttributes.elements(); attributesEnum.hasMoreElements();) { + AttributeField audioAttribute = (AttributeField) attributesEnum.nextElement(); + + if (initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO) == null) { +// log.debug("Creating audio media descriptor." ); + + MediaField mf = new MediaField(Codec.MEDIA_TYPE_AUDIO, audioPort, 0, "RTP/AVP", formatList); + initialDescriptor.addMedia(mf, audioAttribute); + } else { +// log.debug("Just adding attribute."); + initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO).addAttribute(audioAttribute); + } + } + + String[] commonAudioMediaAttributes = CodecFactory.getInstance().getCommonAudioMediaAttributes(); + + if (commonAudioMediaAttributes != null) { +// log.debug("Adding " + commonAudioMediaAttributes.length + " common audio media attributes." ); + + for (int attribIndex = 0; attribIndex < commonAudioMediaAttributes.length; attribIndex++) { +// log.debug("Adding common audio media attribute [" + commonAudioMediaAttributes[attribIndex] + "]."); + + AttributeField newAttribute = parseAttributeField(commonAudioMediaAttributes[attribIndex]); + + if (newAttribute != null) { + initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_AUDIO).addAttribute( newAttribute); + } + } + } else { + log.debug("No common audio media attributes."); + } + } + + if (videoCodecsNumber > 0) { + Codec[] videoCodecs = CodecFactory.getInstance().getAvailableVideoCodecs(); + Vector videoAttributes = new Vector(); + + for (int videoIndex = 0; videoIndex < audioCodecsNumber; videoIndex++) { + String payloadId = String.valueOf(videoCodecs[videoIndex].getCodecId()); + String rtpmapParamValue = payloadId; + rtpmapParamValue += " " + videoCodecs[videoIndex].getCodecName(); + rtpmapParamValue += "/" + videoCodecs[videoIndex].getSampleRate() + "/1"; + +// log.debug("Adding rtpmap for payload [" + payloadId + "] with value = [" + rtpmapParamValue + "]."); + + videoAttributes.add(new AttributeField(Codec.ATTRIBUTE_RTPMAP, rtpmapParamValue)); + String[] codecMediaAttributes = videoCodecs[videoIndex].getCodecMediaAttributes(); + + if (codecMediaAttributes != null) { +// log.debug("Adding " + codecMediaAttributes.length + " video codec media attributes."); + + for (int attribIndex = 0; attribIndex < codecMediaAttributes.length; attribIndex++) { +// log.debug("Adding video media attribute [" + codecMediaAttributes[attribIndex] + "]."); + + AttributeField newAttribute = parseAttributeField(codecMediaAttributes[attribIndex]); + + if (newAttribute != null) { + videoAttributes.add(newAttribute); + } + } + } else { + log.info("Video codec has no especific media attributes."); + } + } + + // Calculate the format list to be used on MediaDescriptor creation. + String formatList = getFormatList(videoAttributes); + + for (Enumeration attributesEnum = videoAttributes.elements(); attributesEnum.hasMoreElements();) { + AttributeField videoAttribute = (AttributeField) attributesEnum.nextElement(); + + if (initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO) == null) { + MediaField mf = new MediaField(Codec.MEDIA_TYPE_VIDEO, audioPort, 0, "RTP/AVP", formatList); + initialDescriptor.addMedia(mf, videoAttribute); + } else { + initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO).addAttribute(videoAttribute); + } + } + + String[] commonVideoMediaAttributes = CodecFactory.getInstance().getCommonAudioMediaAttributes(); + + if (commonVideoMediaAttributes != null) { +// log.debug("Adding " + commonVideoMediaAttributes.length + " common video media attributes."); + + for (int attribIndex = 0; attribIndex < commonVideoMediaAttributes.length; attribIndex++) { +// log.debug("Adding common video media attribute [" + commonVideoMediaAttributes[attribIndex] + "]." ); + + AttributeField newAttribute = parseAttributeField(commonVideoMediaAttributes[attribIndex]); + + if (newAttribute != null) { + initialDescriptor.getMediaDescriptor(Codec.MEDIA_TYPE_VIDEO).addAttribute(newAttribute); + } + } + } else { + log.info("No common video media attributes."); + } + } + } catch (Exception exception) { + log.error("Failure creating initial SDP: " + exception.toString()); + } + +// log.debug("Created initial SDP"); + + return initialDescriptor; + } + + + private static String getFormatList(Vector mediaAttributes) { + AttributeField mediaAttribute = null; + String formatList = ""; + +// log.debug("getting Format List"); + + for (Enumeration attributeEnum = mediaAttributes.elements(); attributeEnum.hasMoreElements();) { + mediaAttribute = (AttributeField) attributeEnum.nextElement(); + + if (mediaAttribute.getAttributeName().equalsIgnoreCase(Codec.ATTRIBUTE_RTPMAP)) { + if (!formatList.isEmpty()) { + formatList += " "; + } + + formatList += getPayloadIdFromAttribute(mediaAttribute); + } + } + +// log.debug("formatList = [" + formatList + "]."); + + return formatList; + } + + + private static AttributeField parseAttributeField(String codecMediaAttribute) { + AttributeField newAttribute = null; + +// log.debug("codecMediaAttribute = [" + codecMediaAttribute + "]."); + + String attribName = codecMediaAttribute.substring(0, codecMediaAttribute.indexOf(":")); + String attribValue = codecMediaAttribute.substring(codecMediaAttribute.indexOf(":") + 1); + +// log.debug("attribName = [" + attribName + "] attribValue = [" + attribValue + "]."); + + if ((!attribName.isEmpty()) && (!attribValue.isEmpty())) { + newAttribute = new AttributeField(attribName, attribValue); + } + + return newAttribute; + } + + + /** + * We must validate the existence of all remote "rtpmap" attributes + * on local SDP. + * If some exist, we add it to newSdp negotiated SDP result. + * + * @param localSdp + * @param remoteSdp + * + * @return Returns the new local descriptor as a result of media + * payloads negotiation. + */ + public static SessionDescriptor makeMediaPayloadsNegotiation(SessionDescriptor localSdp, SessionDescriptor remoteSdp) { + log.debug("makeMediaPayloadsNegotiation"); + + SessionDescriptor newSdp = null; + try { + newSdp = new SessionDescriptor(remoteSdp.getOrigin(), remoteSdp.getSessionName(), + localSdp.getConnection(), localSdp.getTime()); + + Vector remoteDescriptors = remoteSdp.getMediaDescriptors(); + + for (Enumeration descriptorsEnum = remoteDescriptors.elements(); descriptorsEnum.hasMoreElements();) { + MediaDescriptor remoteDescriptor = (MediaDescriptor) descriptorsEnum.nextElement(); + MediaDescriptor localDescriptor = localSdp.getMediaDescriptor(remoteDescriptor.getMedia().getMedia() ); + + if (localDescriptor != null) { + Vector remoteAttributes = remoteDescriptor.getAttributes(Codec.ATTRIBUTE_RTPMAP); + Vector newSdpAttributes = new Vector(); + + for (Enumeration attributesEnum = remoteAttributes.elements(); attributesEnum.hasMoreElements();) { + AttributeField remoteAttribute = (AttributeField) attributesEnum.nextElement(); + + String payloadId = getPayloadIdFromAttribute(remoteAttribute); + + if ("".equals(payloadId)) { + log.error("Payload id not found on attribute: Name = [" + + remoteAttribute.getAttributeName() + "], Value = [" + + remoteAttribute.getAttributeValue() + "]." ); + } else if (findAttributeByPayloadId(remoteAttribute.getAttributeName(), + payloadId, localDescriptor) != null) { + newSdpAttributes.add(remoteAttribute); + } + } + + // Calculate the format list to be used on MediaDescriptor creation. + String formatList = getFormatList(newSdpAttributes); + + for (Enumeration attributesEnum = newSdpAttributes.elements(); attributesEnum.hasMoreElements();) { + AttributeField mediaAttribute = (AttributeField) attributesEnum.nextElement(); + + if (newSdp.getMediaDescriptors().size() == 0) { + MediaField mf = new MediaField(localDescriptor.getMedia().getMedia(), + localDescriptor.getMedia().getPort(), + 0, localDescriptor.getMedia().getTransport(), + formatList); + newSdp.addMediaDescriptor(new MediaDescriptor(mf, localDescriptor.getConnection())); + } + + newSdp.getMediaDescriptor(localDescriptor.getMedia().getMedia()).addAttribute( mediaAttribute ); + } + } + } + } catch (Exception exception) { + log.error("Failure creating initial SDP: " + exception.toString()); + } + + return newSdp; + } + + + /** + * Parameter "newSdp" must be the returning value from method's + * "makeMediaPayloadsNegotiation" execution. + * Here the pending attributes will be negotiated as well. + * + * @param newSdp + * @param localSdp + * @param remoteSdp + * + */ + public static void completeSdpNegotiation(SessionDescriptor newSdp, SessionDescriptor localSdp, SessionDescriptor remoteSdp) { + try { + if (newSdp.getMediaDescriptors().size() == 0) { + // Something is wrong. + // We should have at least a "audio" media descriptor with + // all audio payloads suported. + + log.error("No media descriptors after \"makeMediaPayloadsNegotiation\"." ); + return; + } + + Vector remoteDescriptors = remoteSdp.getMediaDescriptors(); + + for (Enumeration descriptorsEnum = remoteDescriptors.elements(); descriptorsEnum.hasMoreElements();) { + MediaDescriptor remoteDescriptor = (MediaDescriptor) descriptorsEnum.nextElement(); + MediaDescriptor localDescriptor = localSdp.getMediaDescriptor(remoteDescriptor.getMedia().getMedia()); + + if (localDescriptor != null) { + // First we make the negotiation of remote attributes with + // local ones to generate the new SDP "newSdp". + + Vector remoteAttributes = remoteDescriptor.getAttributes(); + + for (Enumeration atributesEnum = remoteAttributes.elements(); atributesEnum.hasMoreElements();) { + AttributeField remoteAttribute = (AttributeField) atributesEnum.nextElement(); + makeAttributeNegotiation(newSdp, localDescriptor, remoteAttribute); + } + + // Now we add to "newSdp" all the local attributes that + // were not negotiated yet. + + Vector localAttributes = localDescriptor.getAttributes(); + + for (Enumeration atributesEnum = localAttributes.elements(); atributesEnum.hasMoreElements();) { + AttributeField localAttribute = (AttributeField) atributesEnum.nextElement(); + MediaDescriptor newLocalDescriptor = newSdp.getMediaDescriptor(localDescriptor.getMedia().getMedia()); + + if (isPayloadRelatedAttribute(localAttribute)) { + String payloadId = getPayloadIdFromAttribute(localAttribute); + + if (findAttributeByPayloadId(localAttribute.getAttributeName(), + payloadId, newLocalDescriptor) == null) { + newLocalDescriptor.addAttribute(localAttribute); + } + } else if (newLocalDescriptor.getAttribute(localAttribute.getAttributeName()) == null) { + newLocalDescriptor.addAttribute(localAttribute); + } + } + } + } + } catch (Exception exception) { + log.error("Failure creating initial SDP: " + exception.toString()); + } + } + + + /** + * Here we make the negotiation of all attributes besides "rtpmap" ( + * these are negotiated on "makeMediaPayloadsNegotiation" method). + * + * @param newSdp + * @param localMedia + * @param remoteAttribute + */ + private static void makeAttributeNegotiation(SessionDescriptor newSdp, MediaDescriptor localMedia, AttributeField remoteAttribute ) { + try { +// log.debug("AttributeName = [" + remoteAttribute.getAttributeName() + +// "], AttributeValue = [" + remoteAttribute.getAttributeValue() + "]."); + + if (remoteAttribute.getAttributeName().equals(Codec.ATTRIBUTE_RTPMAP)) { + log.info("\"rtpmap\" attributes were already negotiated." ); + } else if (!isPayloadRelatedAttribute(remoteAttribute)) { + // We do nothing with attributes that are not payload + // related, like: "ptime", "direction", etc. + // For now, we consider that they don't demand negotiation. + log.info("Attribute is not payload related. Do not negotiate it..."); + } else { + String payloadId = getPayloadIdFromAttribute(remoteAttribute); + + if ("".equals(payloadId)) { + log.error("Payload id not found on attribute: Name = [" + + remoteAttribute.getAttributeName() + "], Value = [" + + remoteAttribute.getAttributeValue() + "]." ); + } else if (findAttributeByPayloadId( Codec.ATTRIBUTE_RTPMAP, payloadId, + newSdp.getMediaDescriptor(localMedia.getMedia().getMedia())) != null) { + // We must be sure this attribute is related with a payload + // already present on newSdp. +// log.debug("Payload " + payloadId + " present on newSdp."); + + AttributeField localAttribute = findAttributeByPayloadId(remoteAttribute.getAttributeName(), payloadId, localMedia ); + + Codec sipCodec = CodecFactory.getInstance().getSIPAudioCodec(Integer.valueOf( payloadId)); + + if (sipCodec != null) { + String localAttibuteValue = ""; + + if (localAttribute != null) { + localAttibuteValue = localAttribute.getAttributeValue(); + } else { + log.info("Attribute not found on local media."); + } + + String attributeValueResult = sipCodec.codecNegotiateAttribute(remoteAttribute.getAttributeName(), + localAttibuteValue, remoteAttribute.getAttributeValue()); + + if ((attributeValueResult != null) && (!"".equals(attributeValueResult))) { + AttributeField af = new AttributeField(remoteAttribute.getAttributeName(), attributeValueResult); + MediaDescriptor md = newSdp.getMediaDescriptor(localMedia.getMedia().getMedia()); + md.addAttribute(af); + } + } else { + log.warn("Codec not found!"); + } + } + } + } catch (Exception exception) { + log.error("Failure creating initial SDP: " + exception.toString()); + } + } + + + private static AttributeField findAttributeByPayloadId(String attributeName, String payloadId, + MediaDescriptor mediaDescriptor) { + AttributeField searchingMediaAttribute = null; + +// log.debug("attributeName = [" + attributeName + "], payloadId = [" + payloadId + "]."); + + Vector mediaAttributes = mediaDescriptor.getAttributes( attributeName ); + + for (Enumeration attributesEnum = mediaAttributes.elements(); attributesEnum.hasMoreElements();) { + AttributeField mediaAttribute = (AttributeField) attributesEnum.nextElement(); + +// log.debug("Validating attribute with name = [" + mediaAttribute.getAttributeName() + +// "] and value = [" + mediaAttribute.getAttributeValue() + "]."); + + if (getPayloadIdFromAttribute(mediaAttribute).equals(payloadId)) { + searchingMediaAttribute = mediaAttribute; + break; + } + } + + if (searchingMediaAttribute != null) { +// log.debug("Attribute found with name = [" + +// searchingMediaAttribute.getAttributeName() + "] and value = [" + +// searchingMediaAttribute.getAttributeValue() + "]." ); + } else { +// log.info("Attribute with name [" + attributeName + "] and payloadId [" + payloadId + "] was not found." ); + } + + return searchingMediaAttribute; + } + + + private static String getPayloadIdFromAttribute(AttributeField attribute) { + String payloadId = ""; + +// log.debug("AttributeName = [" + attribute.getAttributeName() + "], AttributeValue = [" + attribute.getAttributeValue() + "]." ); + + if (isPayloadRelatedAttribute(attribute)) { + payloadId = attribute.getAttributeValue().substring(0, attribute.getAttributeValue().indexOf(" ")); + } + +// log.debug("payloadId = " + payloadId); + + return payloadId; + } + + + private static boolean isPayloadRelatedAttribute(AttributeField attribute) { + boolean isPayloadAttribute = false; + +// log.debug("AttributeName = [" + attribute.getAttributeName() + "], AttributeValue = [" + attribute.getAttributeValue() + "]." ); + + if ((attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_RTPMAP) == 0) || + (attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_AS) == 0) || + (attribute.getAttributeName().compareToIgnoreCase(Codec.ATTRIBUTE_FMTP) == 0)) { + isPayloadAttribute = true; + } + +// log.debug("isPayloadAttribute = " + isPayloadAttribute); + + return isPayloadAttribute; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SessionDescriptorUtil.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SessionDescriptorUtil.java new file mode 100755 index 0000000000000000000000000000000000000000..9b13d30b8e1c664f9868246c050196dd3027d47b --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SessionDescriptorUtil.java @@ -0,0 +1,42 @@ +package org.bigbluebutton.voiceconf.sip; + +import java.util.Enumeration; + +import org.zoolu.sdp.MediaDescriptor; +import org.zoolu.sdp.MediaField; +import org.zoolu.sdp.SessionDescriptor; +import org.zoolu.tools.Parser; + +public class SessionDescriptorUtil { + public static int getLocalAudioPort(SessionDescriptor localSdp) { + int localAudioPort = 0; + + for (Enumeration e = localSdp.getMediaDescriptors().elements(); e.hasMoreElements();) { + MediaField media = ((MediaDescriptor) e.nextElement()).getMedia(); + if (media.getMedia().equals("audio")) { + localAudioPort = media.getPort(); + } + } + + return localAudioPort; + } + + public static int getRemoteAudioPort(SessionDescriptor remoteSdp) { + int remoteAudioPort = 0; + + for (Enumeration e = remoteSdp.getMediaDescriptors().elements(); e.hasMoreElements();) { + MediaDescriptor descriptor = (MediaDescriptor) e.nextElement(); + MediaField media = descriptor.getMedia(); + + if (media.getMedia().equals("audio")) { + remoteAudioPort = media.getPort(); + } + } + + return remoteAudioPort; + } + + public static String getRemoteMediaAddress(SessionDescriptor remoteSdp) { + return (new Parser(remoteSdp.getConnection().toString())).skipString().skipString().getString(); + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipConnectInfo.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipConnectInfo.java new file mode 100755 index 0000000000000000000000000000000000000000..2098f4521168a613e35f01c9b06b6314e6e2c58e --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipConnectInfo.java @@ -0,0 +1,27 @@ +package org.bigbluebutton.voiceconf.sip; + +import java.net.DatagramSocket; + +public class SipConnectInfo { + private final String remoteAddr; + private final int remotePort; + private final DatagramSocket socket; + + SipConnectInfo(DatagramSocket socket, String remoteAddr, int remotePort) { + this.socket = socket; + this.remoteAddr = remoteAddr; + this.remotePort = remotePort; + } + + public DatagramSocket getSocket() { + return socket; + } + + public String getRemoteAddr() { + return remoteAddr; + } + + public int getRemotePort() { + return remotePort; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeer.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeer.java new file mode 100755 index 0000000000000000000000000000000000000000..2f9fbdc553ebd73f7bcc739a2aa7653bd431d23e --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeer.java @@ -0,0 +1,173 @@ +package org.bigbluebutton.voiceconf.sip; + +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.atomic.AtomicInteger; + +import org.zoolu.sip.provider.*; +import org.zoolu.net.SocketAddress; +import org.slf4j.Logger; +import org.bigbluebutton.voiceconf.red5.CallStreamFactory; +import org.bigbluebutton.voiceconf.red5.ClientConnectionManager; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.stream.IBroadcastStream; + +/** + * Class that is a peer to the sip server. This class will maintain + * all calls to it's peer server. + * @author Richard Alam + * + */ +public class SipPeer implements SipRegisterAgentListener { + private static Logger log = Red5LoggerFactory.getLogger(SipPeer.class, "sip"); + + private ClientConnectionManager clientConnManager; + private CallStreamFactory callStreamFactory; + + private CallManager callManager = new CallManager(); + + private SipProvider sipProvider; + private SipRegisterAgent registerAgent; + private final String id; + private final AudioConferenceProvider audioconfProvider; + + private boolean registered = false; + private SipPeerProfile registeredProfile; + + public SipPeer(String id, String host, int sipPort, int startAudioPort, int stopAudioPort) { + this.id = id; + audioconfProvider = new AudioConferenceProvider(host, sipPort, startAudioPort, stopAudioPort); + initSipProvider(host, sipPort); + } + + private void initSipProvider(String host, int sipPort) { + sipProvider = new SipProvider(host, sipPort); + sipProvider.setOutboundProxy(new SocketAddress(host)); + sipProvider.addSipProviderListener(new OptionMethodListener()); + } + + public void register(String username, String password) { + log.debug( "SIPUser register" ); + createRegisterUserProfile(username, password); + if (sipProvider != null) { + registerAgent = new SipRegisterAgent(sipProvider, registeredProfile.fromUrl, + registeredProfile.contactUrl, registeredProfile.username, + registeredProfile.realm, registeredProfile.passwd); + registerAgent.addListener(this); + registerAgent.register(registeredProfile.expires, registeredProfile.expires/2, registeredProfile.keepaliveTime); + } + } + + private void createRegisterUserProfile(String username, String password) { + registeredProfile = new SipPeerProfile(); + registeredProfile.audioPort = audioconfProvider.getStartAudioPort(); + + String fromURL = "\"" + username + "\" <sip:" + username + "@" + audioconfProvider.getHost() + ">"; + registeredProfile.username = username; + registeredProfile.passwd = password; + registeredProfile.realm = audioconfProvider.getHost(); + registeredProfile.fromUrl = fromURL; + registeredProfile.contactUrl = "sip:" + username + "@" + sipProvider.getViaAddress(); + if (sipProvider.getPort() != SipStack.default_port) { + registeredProfile.contactUrl += ":" + sipProvider.getPort(); + } + registeredProfile.keepaliveTime=8000; + registeredProfile.acceptTime=0; + registeredProfile.hangupTime=20; + + log.debug( "SIPUser register : {}", fromURL ); + log.debug( "SIPUser register : {}", registeredProfile.contactUrl ); + } + + + public void call(String clientId, String callerName, String destination) { + if (!registered) { + log.warn("Call request for {} but not registered.", id); + return; + } + + SipPeerProfile callerProfile = SipPeerProfile.copy(registeredProfile); + CallAgent ca = new CallAgent(sipProvider, callerProfile, audioconfProvider, clientId); + ca.setClientConnectionManager(clientConnManager); + ca.setCallStreamFactory(callStreamFactory); + callManager.add(ca); + ca.call(callerName, destination); + } + + public void close() { + log.debug("SIPUser close1"); + try { + unregister(); + } catch(Exception e) { + log.error("close: Exception:>\n" + e); + } + + log.debug("Stopping SipProvider"); + sipProvider.halt(); + } + + public void hangup(String clientId) { + log.debug( "SIPUser hangup" ); + + CallAgent ca = callManager.remove(clientId); + if (ca != null) { + ca.hangup(); + } + } + + public void unregister() { + log.debug( "SIPUser unregister" ); + + Collection<CallAgent> calls = callManager.getAll(); + for (Iterator<CallAgent> iter = calls.iterator(); iter.hasNext();) { + CallAgent ca = (CallAgent) iter.next(); + ca.hangup(); + } + + if (registerAgent != null) { + registerAgent.unregister(); + registerAgent = null; + } + } + + public void startTalkStream(String clientId, IBroadcastStream broadcastStream, IScope scope) { + CallAgent ca = callManager.get(clientId); + if (ca != null) { + ca.startTalkStream(broadcastStream, scope); + } + } + + public void stopTalkStream(String clientId, IBroadcastStream broadcastStream, IScope scope) { + CallAgent ca = callManager.get(clientId); + if (ca != null) { + ca.stopTalkStream(broadcastStream, scope); + } + } + + @Override + public void onRegistrationFailure(String result) { + log.error("Failed to register with Sip Server."); + registered = false; + } + + @Override + public void onRegistrationSuccess(String result) { + log.info("Successfully registered with Sip Server."); + registered = true; + } + + @Override + public void onUnregistedSuccess() { + log.info("Successfully unregistered with Sip Server"); + registered = false; + } + + public void setCallStreamFactory(CallStreamFactory csf) { + callStreamFactory = csf; + } + + public void setClientConnectionManager(ClientConnectionManager ccm) { + clientConnManager = ccm; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerManager.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerManager.java new file mode 100755 index 0000000000000000000000000000000000000000..b1bcbfeaf35757940130477780c0ef0ba873b663 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerManager.java @@ -0,0 +1,119 @@ +package org.bigbluebutton.voiceconf.sip; + +import org.slf4j.Logger; +import org.zoolu.sip.provider.SipStack; +import org.bigbluebutton.voiceconf.red5.CallStreamFactory; +import org.bigbluebutton.voiceconf.red5.ClientConnectionManager; +import org.red5.logging.Red5LoggerFactory; +import org.red5.server.api.IScope; +import org.red5.server.api.stream.IBroadcastStream; + +import java.util.*; + +/** + * Manages all connections to the sip voice conferencing server. + * @author Richard Alam + * + */ +public final class SipPeerManager { + private static final Logger log = Red5LoggerFactory.getLogger( SipPeerManager.class, "sip" ); + + private ClientConnectionManager clientConnManager; + private CallStreamFactory callStreamFactory; + + private Map<String, SipPeer> sipPeers; + private int sipStackDebugLevel = 8; + + public SipPeerManager() { + sipPeers = Collections.synchronizedMap(new HashMap<String, SipPeer>()); + } + + public void createSipPeer(String peerId, String host, int sipPort, int startRtpPort, int stopRtpPort) { + SipPeer sipPeer = new SipPeer(peerId, host, sipPort, startRtpPort, stopRtpPort); + sipPeer.setClientConnectionManager(clientConnManager); + sipPeer.setCallStreamFactory(callStreamFactory); + sipPeers.put(peerId, sipPeer); + } + + public void register(String peerId, String username, String password) throws PeerNotFoundException { + SipPeer sipPeer = sipPeers.get(peerId); + if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId); + sipPeer.register(username, password); + } + + public void call(String peerId, String clientId, String callerName, String destination) throws PeerNotFoundException { + SipPeer sipPeer = sipPeers.get(peerId); + if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId); + sipPeer.call(clientId, callerName, destination); + } + + public void unregister(String userid) { + SipPeer sipUser = sipPeers.get(userid); + if (sipUser != null) { + sipUser.unregister(); + } + } + + public void hangup(String peerId, String clientId) throws PeerNotFoundException { + SipPeer sipPeer = sipPeers.get(peerId); + if (sipPeer == null) throw new PeerNotFoundException("Can't find sip peer " + peerId); + sipPeer.hangup(clientId); + } + + + public void startTalkStream(String peerId, String clientId, IBroadcastStream broadcastStream, IScope scope) { + SipPeer sipUser = sipPeers.get(peerId); + if (sipUser != null) { + sipUser.startTalkStream(clientId, broadcastStream, scope); + } + } + + public void stopTalkStream(String peerId, String clientId, IBroadcastStream broadcastStream, IScope scope) { + SipPeer sipUser = sipPeers.get(peerId); + if (sipUser != null) { + sipUser.stopTalkStream(clientId, broadcastStream, scope); + } + } + + + private void remove(String userid) { + log.debug("Number of SipUsers in Manager before remove {}", sipPeers.size()); + sipPeers.remove(userid); + } + + public void close(String userid) { + SipPeer sipUser = sipPeers.get(userid); + if (sipUser != null) { + sipUser.close(); + remove(userid); + } + } + + public void destroyAllSessions() { + Collection<SipPeer> sipUsers = sipPeers.values(); + SipPeer sp; + + for (Iterator<SipPeer> iter = sipUsers.iterator(); iter.hasNext();) { + sp = (SipPeer) iter.next(); + sp.close(); + sp = null; + } + + sipPeers = new HashMap<String, SipPeer>(); + } + + public void setSipStackDebugLevel(int sipStackDebugLevel) { + this.sipStackDebugLevel = sipStackDebugLevel; + SipStack.init(); + SipStack.debug_level = sipStackDebugLevel; + SipStack.log_path = "log"; + } + + public void setCallStreamFactory(CallStreamFactory csf) { + callStreamFactory = csf; + } + + public void setClientConnectionManager(ClientConnectionManager ccm) { + clientConnManager = ccm; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerProfile.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerProfile.java new file mode 100755 index 0000000000000000000000000000000000000000..414b0bbed95330f0c2a9bf1cd23f8709c54ec605 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipPeerProfile.java @@ -0,0 +1,240 @@ +package org.bigbluebutton.voiceconf.sip; + +import org.red5.app.sip.codecs.Codec; +import org.zoolu.sip.address.*; +import org.zoolu.sip.provider.SipStack; +import org.zoolu.sip.provider.SipProvider; +import org.zoolu.tools.Configure; + +public class SipPeerProfile { + /** + * User's AOR (Address Of Record), used also as From URL. + * <p/> + * The AOR is the SIP address used to register with the user's registrar + * server (if requested). <br/> + * The address of the registrar is taken from the hostport field of the AOR, + * i.e. the value(s) host[:port] after the '@' character. + * <p/> + * If not defined (default), it equals the <i>contact_url</i> attribute. + */ + public String fromUrl = null; + + /** + * Contact URL. If not defined (default), it is formed by + * sip:local_user@host_address:host_port + */ + public String contactUrl = null; + + /** User's name (used to build the contact_url if not explitely defined) */ + public String username = null; + + /** User's realm. */ + public String realm = null; + + /** User's passwd. */ + public String passwd = null; + + /** + * Path for the 'contacts.lst' file where save and load the VisualUA + * contacts By default, it is used the "config/contacts.lst" folder + */ + public static String contactsFile = "contacts.lst"; + + /** Whether registering with the registrar server */ + public boolean doRegister = false; + + /** Whether unregistering the contact address */ + public boolean doUnregister = false; + + /** + * Whether unregistering all contacts beafore registering the contact + * address + */ + public boolean doUnregisterAll = false; + + /** Expires time (in seconds). */ + public int expires = 3600; + + /** + * Rate of keep-alive packets sent toward the registrar server (in + * milliseconds). Set keepalive_time=0 to disable the sending of keep-alive + * datagrams. + */ + public long keepaliveTime = 8000; + + /** + * Automatic call a remote user secified by the 'call_to' value. Use value + * 'NONE' for manual calls (or let it undefined). + */ + public String callTo = null; + + /** + * Automatic answer time in seconds; time<0 corresponds to manual answer + * mode. + */ + public int acceptTime = 0; + + /** + * Automatic hangup time (call duartion) in seconds; time<=0 corresponds to + * manual hangup mode. + */ + public int hangupTime = 20; + + /** + * Automatic call transfer time in seconds; time<0 corresponds to no auto + * transfer mode. + */ + public int transferTime = -1; + + /** + * Automatic re-inviting time in seconds; time<0 corresponds to no auto + * re-invite mode. + */ + public int reInviteTime = -1; + + /** + * Redirect incoming call to the secified url. Use value 'NONE' for not + * redirecting incoming calls (or let it undefined). + */ + public String redirectTo = null; + + /** + * Transfer calls to the secified url. Use value 'NONE' for not transferring + * calls (or let it undefined). + */ + public String transferTo = null; + + /** No offer in the invite */ + public boolean noOffer = false; + + /** Do not use prompt */ + public boolean noPrompt = false; + + /** Whether using audio */ + public boolean audio = true; + + /** Whether using video */ + public boolean video = false; + + /** Whether playing in receive only mode */ + public boolean recvOnly = false; + + /** Whether playing in send only mode */ + public boolean sendOnly = false; + + /** Whether playing a test tone in send only mode */ + public boolean sendTone = false; + + /** Audio file to be played */ + public String sendFile = null; + + /** Audio file to be recorded */ + public String recvFile = null; + + /** Audio port */ + public int audioPort = 21000; + + /** Audio packetization */ + public int audioDefaultPacketization = Codec.DEFAULT_PACKETIZATION; + + /** Video port */ + public int videoPort = 21070; + + /** Whether using JMF for audio/video streaming */ + public boolean useJMF = false; + + /** Whether using RAT (Robust Audio Tool) as audio sender/receiver */ + public boolean useRAT = false; + + /** Whether using VIC (Video Conferencing Tool) as video sender/receiver */ + public boolean useVIC = false; + + /** RAT command-line executable */ + public String binRAT = "rat"; + + /** VIC command-line executable */ + public String binVIC = "vic"; + + public String audioCodecsPrecedence = "8;18;0;110;111"; + //public String audioCodecsPrecedence = ""; + + + // ************************** Costructors ************************* + + /** Costructs a void UserProfile */ + public SipPeerProfile() { + + init(); + } + + + /** Inits the SIPUserAgentProfile */ + private void init() { + + if ( realm == null && contactUrl != null ) { + realm = new NameAddress( contactUrl ).getAddress().getHost(); + } + if ( username == null ) { + username = ( contactUrl != null ) ? new NameAddress( contactUrl ).getAddress().getUserName() : "user"; + } + if ( callTo != null && callTo.equalsIgnoreCase( Configure.NONE ) ) { + callTo = null; + } + if ( redirectTo != null && redirectTo.equalsIgnoreCase( Configure.NONE ) ) { + redirectTo = null; + } + if ( transferTo != null && transferTo.equalsIgnoreCase( Configure.NONE ) ) { + transferTo = null; + } + if ( sendFile != null && sendFile.equalsIgnoreCase( Configure.NONE ) ) { + sendFile = null; + } + if ( recvFile != null && recvFile.equalsIgnoreCase( Configure.NONE ) ) { + recvFile = null; + } + } + + + // ************************ Public methods ************************ + + /** + * Sets contact_url and from_url with transport information. + * <p/> + * This method actually sets contact_url and from_url only if they haven't + * still been explicitly initilized. + */ + public void initContactAddress( SipProvider sip_provider ) { // contact_url + + if ( contactUrl == null ) { + + contactUrl = "sip:" + username + "@" + sip_provider.getViaAddress(); + if ( sip_provider.getPort() != SipStack.default_port ) { + contactUrl += ":" + sip_provider.getPort(); + } + if ( !sip_provider.getDefaultTransport().equals( SipProvider.PROTO_UDP ) ) { + contactUrl += ";transport=" + sip_provider.getDefaultTransport(); + } + } + // from_url + if ( fromUrl == null ) { + fromUrl = contactUrl; + } + } + + public static SipPeerProfile copy(SipPeerProfile source) { + SipPeerProfile userProfile = new SipPeerProfile(); + + userProfile.audioPort = source.audioPort; + + String fromURL = "\"" + source.username + "\" <sip:" + source.username + "@" + source.realm + ">"; + userProfile.username = source.username; + userProfile.passwd = source.passwd; + userProfile.realm = source.realm; + userProfile.fromUrl = fromURL; + userProfile.contactUrl = source.contactUrl; + userProfile.keepaliveTime = source.keepaliveTime; + userProfile.acceptTime = source.acceptTime; + userProfile.hangupTime = source.hangupTime; + return userProfile; + } +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgent.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgent.java new file mode 100755 index 0000000000000000000000000000000000000000..c017f934585895de8fca140b0bca20a549a4efd5 --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgent.java @@ -0,0 +1,458 @@ +package org.bigbluebutton.voiceconf.sip; + +import local.net.KeepAliveSip; +import org.zoolu.net.SocketAddress; +import org.zoolu.sip.address.*; +import org.zoolu.sip.provider.SipStack; +import org.zoolu.sip.provider.SipProvider; +import org.zoolu.sip.header.*; +import org.zoolu.sip.message.*; +import org.zoolu.sip.transaction.TransactionClient; +import org.zoolu.sip.transaction.TransactionClientListener; +import org.zoolu.sip.authentication.DigestAuthentication; + +import org.slf4j.Logger; +import org.red5.logging.Red5LoggerFactory; + +import java.util.HashSet; +import java.util.Set; +import java.util.Vector; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * Register User Agent. It registers (one time or periodically) a contact + * address with a registrar server. + */ +public class SipRegisterAgent implements TransactionClientListener { + private static Logger log = Red5LoggerFactory.getLogger(SipRegisterAgent.class, "sip"); + + private final Executor exec = Executors.newSingleThreadExecutor(); + private Runnable registerProcess; + private volatile boolean continueRegistering = false; + + /** The CallerID and CSeq that should be used during REGISTER method */ + private CallIdHeader registerCallID; + private int registerCSeq; + + private static final int MAX_ATTEMPTS = 3; + private Status agentStatus = Status.UNREGISTERED; + private Request request; + + private enum Status { + REGISTERING(0), REGISTERED(1), RENEWING(2), UNREGISTERING(3), UNREGISTERED(4); + private final int state; + Status(int state) {this.state = state;} + private int getState() {return state;} + } + + private enum Request { + REGISTERING(0), RENEWING(1), UNREGISTERING(1); + private final int request; + + Request(int request) { + this.request = request; + } + + private int getRequest() { + return request; + } + } + + private Set<SipRegisterAgentListener> listeners = new HashSet<SipRegisterAgentListener>(); + + private SipProvider sipProvider; + private NameAddress target; // User's URI with the fully qualified domain + // name of the registrar server. + private String username; + private String realm; + private String passwd; + private String nextNonce; // Nonce for the next authentication. + private String qop; // Qop for the next authentication. + private NameAddress contact; // User's contact address. + private int expireTime; // Expiration time. + private int renewTime; + private int origRenewTime; // Change by lior. + private int minRenewTime = 20; + private boolean lastRegFailed = false; // Changed by Lior. + private boolean regInprocess = false; + private int attempts; // Number of registration attempts. + private KeepAliveSip keepAlive; // KeepAliveSip daemon. + + public SipRegisterAgent(SipProvider sipProvider, String targetUrl, String contactUrl) { + init(sipProvider, targetUrl, contactUrl); + } + + /** + * Creates a new RegisterAgent with authentication credentials (i.e. + * username, realm, and passwd). + */ + public SipRegisterAgent(SipProvider sipProvider, String targetUrl, + String contactUrl, String username, String realm, String passwd) { + + init(sipProvider, targetUrl, contactUrl); + + // Authentication. + this.username = username; + this.realm = realm; + this.passwd = passwd; + } + + private void init(SipProvider sipProvider, String targetUrl, String contactUrl) { + this.sipProvider = sipProvider; + this.target = new NameAddress(targetUrl); + this.contact = new NameAddress(contactUrl); + // this.expire_time=SipStack.default_expires; + this.expireTime = 600; + // Changed by Lior. + this.renewTime = 600; + this.origRenewTime = this.renewTime; + this.keepAlive = null; + // Authentication. + this.username = null; + this.realm = null; + this.passwd = null; + this.nextNonce = null; + this.qop = null; + this.attempts = 0; + this.minRenewTime = 20; + this.registerCallID = null; + this.registerCSeq = 0; + } + + public void addListener(SipRegisterAgentListener listener) { + listeners.add(listener); + } + + public void removeListener(SipRegisterAgentListener listener) { + listeners.remove(listener); + } + + private boolean isPeriodicallyRegistering() { + return continueRegistering; + } + + private void register() { + register(expireTime); + } + + /** Registers with the registrar server for <i>expire_time</i> seconds. */ + private void register(int expireTime) { + if (agentStatus == Status.REGISTERED) { + request = Request.RENEWING; + } else { + request = Request.REGISTERING; + } + + attempts = 0; + lastRegFailed = false; + regInprocess = true; + + if (expireTime > 0) this.expireTime = expireTime; + + Message req = MessageFactory.createRegisterRequest(sipProvider, target, target, contact); + + /* + * MY_FIX: registerCallID contains the CallerID randomly generated in + * the first REGISTER method. It will be reused for all successive + * REGISTER invocations + */ + if (this.registerCallID == null) { + this.registerCallID = req.getCallIdHeader(); + } else { + req.setCallIdHeader(this.registerCallID); + } + + /* + * MY_FIX: the registerCSeq must be unique for a given CallerID + */ + this.registerCSeq++; + req.setCSeqHeader(new CSeqHeader(this.registerCSeq, SipMethods.REGISTER)); + + req.setExpiresHeader(new ExpiresHeader(String.valueOf(expireTime))); + + if (nextNonce != null) { + AuthorizationHeader authHeader = new AuthorizationHeader("Digest"); + authHeader.addUsernameParam(username); + authHeader.addRealmParam(realm); + authHeader.addNonceParam(nextNonce); + authHeader.addUriParam(req.getRequestLine().getAddress().toString()); + authHeader.addQopParam(qop); + + String response = (new DigestAuthentication(SipMethods.REGISTER, + authHeader, null, passwd)).getResponse(); + authHeader.addResponseParam(response); + + req.setAuthorizationHeader(authHeader); + } + + if (expireTime > 0) + log.info("Registering contact " + contact + " (it expires in " + expireTime + " secs)"); + else + log.info("Unregistering contact " + contact); + + TransactionClient t = new TransactionClient(sipProvider, req, this); + t.request(); + } + + public void unregister() { + if (isPeriodicallyRegistering()) { + stopRegistering(); + } + unregisterWithServer(); + sipProvider = null; + } + + private void unregisterWithServer() { + attempts = 0; + request = Request.UNREGISTERING; + + Message req = MessageFactory.createRegisterRequest(sipProvider, target, target, null); + req.setExpiresHeader(new ExpiresHeader(String.valueOf(0))); + log.info("Unregistering all contacts"); + TransactionClient t = new TransactionClient(sipProvider, req, this); + t.request(); + } + + /** + * Periodically registers with the registrar server. + * + * @param expireTime + * expiration time in seconds + * @param renewTime + * renew time in seconds + */ + private void loopRegister(int expireTime, int renewTime) { + this.expireTime = expireTime; + this.renewTime = renewTime; + + if (!isPeriodicallyRegistering()) { + registerProcess = new Runnable() { + public void run() { + registerWithServer(); + } + }; + exec.execute(registerProcess); + } + } + + /** + * Periodically registers with the registrar server. + * + * @param expireTime + * expiration time in seconds + * @param renewTime + * renew time in seconds + * @param keepaliveTime + * keep-alive packet rate (inter-arrival time) in milliseconds + */ + public void register(int expireTime, int renewTime, long keepaliveTime) { + if (isPeriodicallyRegistering()) { + stopRegistering(); + } + + loopRegister(expireTime, renewTime); + // Keep-alive. + if (keepaliveTime > 0) { + SipURL targetUrl = target.getAddress(); + String targetHost = targetUrl.getHost(); + int targePort = targetUrl.getPort(); + if (targePort < 0) targePort = SipStack.default_port; + + SocketAddress socket = new SocketAddress(targetHost, targePort); + keepAlive = new KeepAliveSip(sipProvider, socket, null, keepaliveTime); + } + } + + public void stopRegistering() { + continueRegistering = false; + if (keepAlive != null) + keepAlive.halt(); + } + + // ***************************** run() ***************************** + + /** Run method */ + private void registerWithServer() { + continueRegistering = true; + try { + while (continueRegistering) { + register(); + // Changed by Lior. + long waitCnt = 0; + while (regInprocess) { + Thread.sleep(1000); + waitCnt += 1000; + } + + if (lastRegFailed) { + printLog("Failed Registration stop try."); + stopRegistering(); + } else + Thread.sleep(renewTime * 1000 - waitCnt); + } + } catch (Exception e) { + printException(e); + } + continueRegistering = false; + } + + // **************** Transaction callback functions ***************** + + /** Callback function called when client sends back a failure response. */ + + /** Callback function called when client sends back a provisional response. */ + public void onTransProvisionalResponse(TransactionClient transaction, Message resp) { + // do nothing... + } + + /** Callback function called when client sends back a success response. */ + public void onTransSuccessResponse(TransactionClient transaction, Message resp) { + if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { + if (resp.hasAuthenticationInfoHeader()) { + nextNonce = resp.getAuthenticationInfoHeader().getNextnonceParam(); + } + StatusLine status = resp.getStatusLine(); + String result = status.getCode() + " " + status.getReason(); + + // Update the renew_time. + // Changed by Lior. + int expires = 0; + + if (resp.hasExpiresHeader()) { + expires = resp.getExpiresHeader().getDeltaSeconds(); + } else if (resp.hasContactHeader()) { + // Look for the max expires - should be the latest. + Vector contacts = resp.getContacts().getHeaders(); + for (int i = 0; i < contacts.size(); i++) { + int exp_i = (new ContactHeader((Header) contacts.elementAt(i))).getExpires(); + if (exp_i / 2 > expires) + expires = exp_i / 2; + } + } + + if (expires > 0 && expires < renewTime) { + renewTime = expires; + if (renewTime < minRenewTime) { + printLog("Attempt to set renew time below min renew. Attempted=" + + renewTime + " min=" + minRenewTime + "\r\nResponse=" + resp.toString()); + renewTime = minRenewTime; + } + } else if (expires > origRenewTime) { + printLog("Attempt to set renew time above original renew. Attempted=" + + expires + " origrenew=" + origRenewTime + "\r\nResponse=" + resp.toString()); + } + + printLog("Registration success: " + result); + regInprocess = false; + + if (request == Request.REGISTERING) { + printLog("Notifying listeners of REGISTRATION success"); + agentStatus = Status.REGISTERED; + notifyListenersOfRegistrationSuccess("REGISTERED"); + } else if (request == Request.UNREGISTERING){ + printLog("Notifying listeners of UNREGISTRATION success"); + agentStatus = Status.UNREGISTERED; + notifyListenersOfRegistrationSuccess("UNREGISTERED"); + } else if (request == Request.RENEWING) { + agentStatus = Status.REGISTERED; + // Don't notify the listeners. + printLog("NOT Notifying listeners of RENEW success"); + } + } + } + + private void notifyListenersOfRegistrationSuccess(String result) { + for (SipRegisterAgentListener listener : listeners) { + listener.onRegistrationSuccess(result); + } + } + + /** Callback function called when client sends back a failure response. */ + public void onTransFailureResponse(TransactionClient transaction, Message resp) { + printLog("onTransFailureResponse start: "); + + if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { + StatusLine status = resp.getStatusLine(); + int code = status.getCode(); + if ((code == 401 && attempts < MAX_ATTEMPTS && resp.hasWwwAuthenticateHeader() + && resp.getWwwAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm)) + || (code == 407 && attempts < MAX_ATTEMPTS && resp.hasProxyAuthenticateHeader() + && resp.getProxyAuthenticateHeader().getRealmParam().equalsIgnoreCase(realm))) + { + printLog("onTransFailureResponse 401 or 407: "); + + attempts++; + Message req = transaction.getRequestMessage(); + req.setCSeqHeader(req.getCSeqHeader().incSequenceNumber()); + // * MY_FIX: registerCSeq counter must incremented. + this.registerCSeq++; + + WwwAuthenticateHeader wwwAuthHeader; + if (code == 401) + wwwAuthHeader = resp.getWwwAuthenticateHeader(); + else + wwwAuthHeader = resp.getProxyAuthenticateHeader(); + + String qopOptions = wwwAuthHeader.getQopOptionsParam(); + // qop=(qopOptions!=null)? "auth" : null; + + // Select a new branch - rfc3261 says should be new on each request. + ViaHeader via = req.getViaHeader(); + req.removeViaHeader(); + via.setBranch(SipProvider.pickBranch()); + req.addViaHeader(via); + qop = (qopOptions != null) ? "auth" : null; + + DigestAuthentication digest = new DigestAuthentication(SipMethods.REGISTER, + req.getRequestLine().getAddress().toString(), wwwAuthHeader, qop, null, username, passwd); + + AuthorizationHeader authHeader; + if (code == 401) + authHeader = digest.getAuthorizationHeader(); + else + authHeader = digest.getProxyAuthorizationHeader(); + + req.setAuthorizationHeader(authHeader); + TransactionClient t = new TransactionClient(sipProvider, req, this); + t.request(); + + } else { + String result = code + " " + status.getReason(); + lastRegFailed = true; + regInprocess = false; + + printLog("Registration failure: " + result); + notifyListenersOfRegistrationFailure(result); + } + } + } + + private void notifyListenersOfRegistrationFailure(String result) { + for (SipRegisterAgentListener listener : listeners) { + listener.onRegistrationFailure(result); + } + } + + /** Callback function called when client expires timeout. */ + public void onTransTimeout(TransactionClient transaction) { + if (transaction.getTransactionMethod().equals(SipMethods.REGISTER)) { + lastRegFailed = true; + regInprocess = false; + + printLog("Registration failure: No response from server."); + notifyListenersOfRegistrationFailure( "Timeout"); + } + } + + // ****************************** Logs ***************************** + + void printLog(String str) { + System.out.println("RegisterAgent: " + str); + } + + void printException(Exception e) { + System.out.println("RegisterAgent Exception: " + e); + } + +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgentListener.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgentListener.java new file mode 100755 index 0000000000000000000000000000000000000000..c01bca6aa280fd4e24107aa24e95e9812317985e --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipRegisterAgentListener.java @@ -0,0 +1,9 @@ +package org.bigbluebutton.voiceconf.sip; + +public interface SipRegisterAgentListener { + /** When a UA has been successfully (un)registered. */ + public void onRegistrationSuccess(String result); + /** When a UA failed on (un)registering. */ + public void onRegistrationFailure(String result); + public void onUnregistedSuccess(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipUserAgentListener.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipUserAgentListener.java new file mode 100755 index 0000000000000000000000000000000000000000..710a7109d1aa10562730e4f3cbeeadeabc54378c --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/sip/SipUserAgentListener.java @@ -0,0 +1,12 @@ +package org.bigbluebutton.voiceconf.sip; + +public interface SipUserAgentListener { + public void onCallConnected(String talkStreamName, String listenStreamName); + public void onNewIncomingCall(String source, String sourceName, String destination, String destinationName); + public void onOutgoingCallRemoteRinging(); + public void onOutgoingCallAccepted(); + public void onCallTrasferred(); + public void onIncomingCallCancelled(); + public void onOutgoingCallFailed(); + public void onCallClosed(); +} diff --git a/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/util/StackTraceUtil.java b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/util/StackTraceUtil.java new file mode 100755 index 0000000000000000000000000000000000000000..51dee5033878b79e7de44205d31769362a6389fe --- /dev/null +++ b/bbb-voice/src/main/java/org/bigbluebutton/voiceconf/util/StackTraceUtil.java @@ -0,0 +1,12 @@ +package org.bigbluebutton.voiceconf.util; + +import java.io.*; + +public final class StackTraceUtil { + public static String getStackTrace(Throwable aThrowable) { + final Writer result = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(result); + aThrowable.printStackTrace(printWriter); + return result.toString(); + } +} diff --git a/bbb-voice/src/main/java/org/red5/app/sip/stream/RtpStreamReceiver.java b/bbb-voice/src/main/java/org/red5/app/sip/stream/RtpStreamReceiver.java old mode 100644 new mode 100755 index b519f9ec8b5ce639e6a4b4187236291ff9d6619e..f0a87576d92131da426507dcabf03b2c6269ca4a --- a/bbb-voice/src/main/java/org/red5/app/sip/stream/RtpStreamReceiver.java +++ b/bbb-voice/src/main/java/org/red5/app/sip/stream/RtpStreamReceiver.java @@ -29,8 +29,6 @@ public class RtpStreamReceiver { this.packetProcessor = packetProcessor; this.payloadLength = payloadLength; rtpSocket = new RtpSocket(socket); - - // initializeSocket(); } public void setRtpStreamReceiverListener(RtpStreamReceiverListener listener) { diff --git a/bbb-voice/src/main/java/org/zoolu/sdp/SdpParser.java b/bbb-voice/src/main/java/org/zoolu/sdp/SdpParser.java old mode 100644 new mode 100755 index e8abd7d6a7d8967309fcd4df0b71e81d5ee24a2a..efaf89d49e9b8f5c2783812d0c34a4a086bbc0b2 --- a/bbb-voice/src/main/java/org/zoolu/sdp/SdpParser.java +++ b/bbb-voice/src/main/java/org/zoolu/sdp/SdpParser.java @@ -35,7 +35,8 @@ class SdpParser extends Parser { /** Creates a SdpParser based on String <i>s</i> */ public SdpParser(String s) - { super(s); + { + super(s); } /** Creates a SdpParser based on String <i>s</i> and starting from position <i>i</i> */ diff --git a/bbb-voice/src/main/java/org/zoolu/sip/call/CallListenerAdapter.java b/bbb-voice/src/main/java/org/zoolu/sip/call/CallListenerAdapter.java old mode 100644 new mode 100755 index 99857f1d4b854fd484e3dcd47765e3c0a414ada9..9dc7efbfa3319884d6ac45fd6a3e1490816152aa --- a/bbb-voice/src/main/java/org/zoolu/sip/call/CallListenerAdapter.java +++ b/bbb-voice/src/main/java/org/zoolu/sip/call/CallListenerAdapter.java @@ -23,16 +23,9 @@ package org.zoolu.sip.call; - -import org.zoolu.sip.call.*; -import org.zoolu.sip.provider.SipStack; import org.zoolu.sip.address.NameAddress; import org.zoolu.sip.message.Message; -import org.zoolu.tools.Log; -import org.zoolu.tools.LogLevel; import org.zoolu.sdp.*; -//import java.util.Iterator; -import java.util.Enumeration; import java.util.Vector; @@ -62,14 +55,16 @@ public abstract class CallListenerAdapter implements ExtendedCallListener /** Changes the current session descriptor specifing the receiving RTP/UDP port number, the AVP format, the codec, and the clock rate */ /*public static String audioSession(int port, int avp, String codec, int rate) - { SessionDescriptor sdp=new SessionDescriptor(); - sdp.addMedia(new MediaField("audio ",port,0,"RTP/AVP",String.valueOf(avp)),new AttributeField("rtpmap",avp+" "+codec+"/"+rate)); - return sdp.toString(); + { + SessionDescriptor sdp=new SessionDescriptor(); + sdp.addMedia(new MediaField("audio ",port,0,"RTP/AVP",String.valueOf(avp)),new AttributeField("rtpmap",avp+" "+codec+"/"+rate)); + return sdp.toString(); }*/ /** Changes the current session descriptor specifing the receiving RTP/UDP port number, the AVP format, the codec, and the clock rate */ /*public static String audioSession(int port) - { return audioSession(port,0,"PCMU",8000); + { + return audioSession(port,0,"PCMU",8000); }*/ @@ -78,17 +73,19 @@ public abstract class CallListenerAdapter implements ExtendedCallListener /** Accepts an incoming call. * Callback function called when arriving a new INVITE method (incoming call) */ public void onCallIncoming(Call call, NameAddress callee, NameAddress caller, String sdp, Message invite) - { //printLog("INCOMING"); - call.ring(); - String local_session; - if (sdp!=null && sdp.length()>0) - { SessionDescriptor remote_sdp=new SessionDescriptor(sdp); - SessionDescriptor local_sdp=new SessionDescriptor(call.getLocalSessionDescriptor()); - SessionDescriptor new_sdp=new SessionDescriptor(remote_sdp.getOrigin(),remote_sdp.getSessionName(),local_sdp.getConnection(),local_sdp.getTime()); - new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors()); - new_sdp=SdpTools.sdpMediaProduct(new_sdp,remote_sdp.getMediaDescriptors()); - new_sdp=SdpTools.sdpAttirbuteSelection(new_sdp,"rtpmap"); - local_session=new_sdp.toString(); + { + //printLog("INCOMING"); + call.ring(); + String local_session; + if (sdp!=null && sdp.length()>0) + { + SessionDescriptor remote_sdp = new SessionDescriptor(sdp); + SessionDescriptor local_sdp = new SessionDescriptor(call.getLocalSessionDescriptor()); + SessionDescriptor new_sdp = new SessionDescriptor(remote_sdp.getOrigin(),remote_sdp.getSessionName(),local_sdp.getConnection(),local_sdp.getTime()); + new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors()); + new_sdp = SdpTools.sdpMediaProduct(new_sdp,remote_sdp.getMediaDescriptors()); + new_sdp = SdpTools.sdpAttirbuteSelection(new_sdp,"rtpmap"); + local_session = new_sdp.toString(); } else local_session=call.getLocalSessionDescriptor(); // accept immediatly @@ -98,16 +95,18 @@ public abstract class CallListenerAdapter implements ExtendedCallListener /** Changes the call when remotly requested. * Callback function called when arriving a new Re-INVITE method (re-inviting/call modify) */ public void onCallModifying(Call call, String sdp, Message invite) - { //printLog("RE-INVITE/MODIFY"); - String local_session; - if (sdp!=null && sdp.length()>0) - { SessionDescriptor remote_sdp=new SessionDescriptor(sdp); - SessionDescriptor local_sdp=new SessionDescriptor(call.getLocalSessionDescriptor()); - SessionDescriptor new_sdp=new SessionDescriptor(remote_sdp.getOrigin(),remote_sdp.getSessionName(),local_sdp.getConnection(),local_sdp.getTime()); - new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors()); - new_sdp=SdpTools.sdpMediaProduct(new_sdp,remote_sdp.getMediaDescriptors()); - new_sdp=SdpTools.sdpAttirbuteSelection(new_sdp,"rtpmap"); - local_session=new_sdp.toString(); + { + //printLog("RE-INVITE/MODIFY"); + String local_session; + if (sdp!=null && sdp.length()>0) + { + SessionDescriptor remote_sdp = new SessionDescriptor(sdp); + SessionDescriptor local_sdp = new SessionDescriptor(call.getLocalSessionDescriptor()); + SessionDescriptor new_sdp = new SessionDescriptor(remote_sdp.getOrigin(),remote_sdp.getSessionName(),local_sdp.getConnection(),local_sdp.getTime()); + new_sdp.addMediaDescriptors(local_sdp.getMediaDescriptors()); + new_sdp = SdpTools.sdpMediaProduct(new_sdp,remote_sdp.getMediaDescriptors()); + new_sdp = SdpTools.sdpAttirbuteSelection(new_sdp,"rtpmap"); + local_session = new_sdp.toString(); } else local_session=call.getLocalSessionDescriptor(); // accept immediatly diff --git a/bbb-voice/src/main/java/org/zoolu/sip/provider/SipProvider.java b/bbb-voice/src/main/java/org/zoolu/sip/provider/SipProvider.java old mode 100644 new mode 100755 index dd8cc4fcaca44b30767ed0396aef8d9a5267452c..86182f05351cb5804d153378fbde2f9428a1716c --- a/bbb-voice/src/main/java/org/zoolu/sip/provider/SipProvider.java +++ b/bbb-voice/src/main/java/org/zoolu/sip/provider/SipProvider.java @@ -111,19 +111,19 @@ public class SipProvider implements Configurable, TransportListener, TcpServerLi // **************************** Constants **************************** /** UDP protocol type */ - public static final String PROTO_UDP="udp"; + public static final String PROTO_UDP = "udp"; /** TCP protocol type */ - public static final String PROTO_TCP="tcp"; + public static final String PROTO_TCP = "tcp"; /** TLS protocol type */ - public static final String PROTO_TLS="tls"; + public static final String PROTO_TLS = "tls"; /** SCTP protocol type */ - public static final String PROTO_SCTP="sctp"; + public static final String PROTO_SCTP = "sctp"; /** String value "auto-configuration" used for auto configuration of the host address. */ - public static final String AUTO_CONFIGURATION="AUTO-CONFIGURATION"; + public static final String AUTO_CONFIGURATION = "AUTO-CONFIGURATION"; /** String value "auto-configuration" used for auto configuration of the host address. */ - public static final String ALL_INTERFACES="ALL-INTERFACES"; + public static final String ALL_INTERFACES = "ALL-INTERFACES"; /** String value "NO-OUTBOUND" used for setting no outbound proxy. */ //public static final String NO_OUTBOUND="NO-OUTBOUND"; @@ -132,7 +132,7 @@ public class SipProvider implements Configurable, TransportListener, TcpServerLi * that does not match any active method_id, transaction_id, nor dialog_id. * <br> In this context, "active" means that there is a active listener * for that specific method, transaction, or dialog. */ - public static final Identifier ANY=new Identifier("ANY"); + public static final Identifier ANY = new Identifier("ANY"); /** Identifier used as listener id for capturing any incoming messages in PROMISQUE mode, * that means that messages are passed to the present listener regardless of @@ -140,90 +140,85 @@ public class SipProvider implements Configurable, TransportListener, TcpServerLi * <p/> * More than one SipProviderListener can be added and be active concurrently * for capturing messages in PROMISQUE mode. */ - public static final Identifier PROMISQUE=new Identifier("PROMISQUE"); + public static final Identifier PROMISQUE = new Identifier("PROMISQUE"); - public static final Identifier INVITE=new Identifier("INVITE"); + public static final Identifier INVITE = new Identifier("INVITE"); /** Minimum length for a valid SIP message. */ - private static final int MIN_MESSAGE_LENGTH=12; + private static final int MIN_MESSAGE_LENGTH = 12; // ***************** Readable/configurable attributes ***************** - String sipBusyUrl=null; + String sipBusyUrl = null; /** Via address/name. * Use 'auto-configuration' for auto detection, or let it undefined. */ - String via_addr=null; + String via_addr = null; /** Local SIP port */ - int host_port=0; + int host_port = 0; /** Network interface (IP address) used by SIP. * Use 'ALL-INTERFACES' for binding SIP to all interfaces (or let it undefined). */ - String host_ifaddr=null; + String host_ifaddr = null; /** Transport protocols (the first protocol is used as default) */ - String[] transport_protocols=null; + String[] transport_protocols = null; /** Max number of (contemporary) open connections */ - int nmax_connections=0; + int nmax_connections = 0; /** Outbound proxy (host_addr[:host_port]). * Use 'NONE' for not using an outbound proxy (or let it undefined). */ - SocketAddress outbound_proxy=null; + SocketAddress outbound_proxy = null; /** Whether logging all packets (including non-SIP keepalive tokens). */ - boolean log_all_packets=false; + boolean log_all_packets = false; - private String inviteLock=""; + private String inviteLock = ""; // new params to control address handling - private boolean sendResponseUsingOutboundProxy=false; - private boolean useViaReceived=true; - private boolean useViaRport=true; + private boolean sendResponseUsingOutboundProxy = false; + private boolean useViaReceived = true; + private boolean useViaRport = true; // for backward compatibility: /** Outbound proxy addr (for backward compatibility). */ - private String outbound_addr=null; + private String outbound_addr = null; /** Outbound proxy port (for backward compatibility). */ - private int outbound_port=-1; - - private OptionHandler optionHandler=null; - - - - + private int outbound_port = -1; + private OptionHandler optionHandler = null; // ********************* Non-readable attributes ********************* /** Event Loger */ - protected Log event_log=null; + protected Log event_log = null; /** Message Loger */ - protected Log message_log=null; + protected Log message_log = null; /** Network interface (IP address) used by SIP. */ - IpAddress host_ipaddr=null; + IpAddress host_ipaddr = null; /** Default transport */ - String default_transport=null; + String default_transport = null; - static long breaker=0; + static long breaker = 0; - private boolean initComplete=false; + private boolean initComplete = false; /** Whether using UDP as transport protocol */ - boolean transport_udp=false; + boolean transport_udp = false; /** Whether using TCP as transport protocol */ - boolean transport_tcp=false; + boolean transport_tcp = false; /** Whether using TLS as transport protocol */ - boolean transport_tls=false; + boolean transport_tls = false; /** Whether using SCTP as transport protocol */ - boolean transport_sctp=false; + boolean transport_sctp = false; /** Whether adding 'rport' parameter on outgoing requests. */ boolean rport=true; @@ -258,42 +253,51 @@ public class SipProvider implements Configurable, TransportListener, TcpServerLi /** Creates a new SipProvider. */ public SipProvider(String via_addr, int port) - { init(via_addr,port,null,null); - initlog(); - startTrasport(); + { + init(via_addr,port,null,null); + initlog(); + startTrasport(); } /** Creates a new SipProvider. * Costructs the SipProvider, initializing the SipProviderListeners, the transport protocols, and other attributes. */ public SipProvider(String via_addr, int port, String[] protocols, String ifaddr) - { init(via_addr,port,protocols,ifaddr); - initlog(); - startTrasport(); + { + init(via_addr,port,protocols,ifaddr); + initlog(); + startTrasport(); } /** Creates a new SipProvider. * The SipProvider attributres are read from file. */ public SipProvider(String file) - { if (!SipStack.isInit()) SipStack.init(file); - new Configure(this,file); - init(via_addr,host_port,transport_protocols,host_ifaddr); - initlog(); - startTrasport(); + { + if (!SipStack.isInit()) SipStack.init(file); + new Configure(this,file); + init(via_addr,host_port,transport_protocols,host_ifaddr); + initlog(); + startTrasport(); } /** Inits the SipProvider, initializing the SipProviderListeners, the transport protocols, the outbound proxy, and other attributes. */ private void init(String viaddr, int port, String[] protocols, String ifaddr) - { if (!SipStack.isInit()) SipStack.init(); - via_addr=viaddr; - if (via_addr==null || via_addr.equalsIgnoreCase(AUTO_CONFIGURATION)) via_addr=IpAddress.getLocalHostAddress().toString(); - host_port=port; - if (host_port<=0) host_port=SipStack.default_port; - host_ipaddr=null; - if (ifaddr!=null && !ifaddr.equalsIgnoreCase(ALL_INTERFACES)) - { try { host_ipaddr=IpAddress.getByName(ifaddr); } catch (IOException e) { e.printStackTrace(); host_ipaddr=null; } + { + if (!SipStack.isInit()) SipStack.init(); + via_addr = viaddr; + if (via_addr==null || via_addr.equalsIgnoreCase(AUTO_CONFIGURATION)) via_addr=IpAddress.getLocalHostAddress().toString(); + host_port = port; + if (host_port<=0) host_port=SipStack.default_port; + host_ipaddr=null; + if (ifaddr!=null && !ifaddr.equalsIgnoreCase(ALL_INTERFACES)) + { + try { + host_ipaddr=IpAddress.getByName(ifaddr); + } catch (IOException e) { + e.printStackTrace(); host_ipaddr=null; + } } transport_protocols=protocols; if (transport_protocols==null) transport_protocols=SipStack.default_transport_protocols; @@ -1339,26 +1343,27 @@ public class SipProvider implements Configurable, TransportListener, TcpServerLi * a SIP URL is costructed using the outbound proxy as host address if present, * otherwise the local via address is used. */ public NameAddress completeNameAddress(String str) - { if (str.indexOf("<sip:")>=0) return new NameAddress(str); - else - { SipURL url=completeSipURL(str); - return new NameAddress(url); + { + if (str.indexOf("<sip:") >= 0) return new NameAddress(str); + else { + SipURL url = completeSipURL(str); + return new NameAddress(url); } } /** Constructs a SipURL based on an input string. */ private SipURL completeSipURL(String str) - { // in case it is passed only the 'user' field, add '@'<outbound_proxy>[':'<outbound_port>] - if (!str.startsWith("sip:") && str.indexOf("@")<0 && str.indexOf(".")<0 && str.indexOf(":")<0) - { // may be it is just the user name.. - String url="sip:"+str+"@"; - if (outbound_proxy!=null) - { url+=outbound_proxy.getAddress().toString(); - int port=outbound_proxy.getPort(); - if (port>0 && port!=SipStack.default_port) url+=":"+port; - } - else - { url+=via_addr; - if (host_port>0 && host_port!=SipStack.default_port) url+=":"+host_port; + { + // in case it is passed only the 'user' field, add '@'<outbound_proxy>[':'<outbound_port>] + if (!str.startsWith("sip:") && str.indexOf("@")<0 && str.indexOf(".") < 0 && str.indexOf(":") < 0) { + // may be it is just the user name.. + String url = "sip:" + str + "@"; + if (outbound_proxy != null) { + url += outbound_proxy.getAddress().toString(); + int port = outbound_proxy.getPort(); + if (port > 0 && port != SipStack.default_port) url += ":" + port; + } else { + url += via_addr; + if (host_port > 0 && host_port != SipStack.default_port) url += ":" + host_port; } return new SipURL(url); } diff --git a/bbb-voice/src/main/java/org/zoolu/tools/Parser.java b/bbb-voice/src/main/java/org/zoolu/tools/Parser.java old mode 100644 new mode 100755 index 222307f4f78755e64fc7522aa129f75b566ba3ee..2576ff9ffed9dd0afe80fce58d7538347b3638d6 --- a/bbb-voice/src/main/java/org/zoolu/tools/Parser.java +++ b/bbb-voice/src/main/java/org/zoolu/tools/Parser.java @@ -49,34 +49,44 @@ public class Parser /** Creates the Parser from the String <i>s</i> and point to the beginning of the string.*/ public Parser(String s) - { if (s==null) throw (new RuntimeException("Tried to costruct a new Parser with a null String")); - str=s; - index=0; + { + if (s == null) throw (new RuntimeException("Tried to costruct a new Parser with a null String")); + str = s; + index = 0; } + /** Creates the Parser from the String <i>s</i> and point to the position <i>i</i>. */ public Parser(String s, int i) - { if (s==null) throw (new RuntimeException("Tried to costruct a new Parser with a null String")); - str=s; - index=i; + { + if (s == null) throw (new RuntimeException("Tried to costruct a new Parser with a null String")); + str = s; + index = i; } + /** Creates the Parser from the StringBuffer <i>sb</i> and point to the beginning of the string.*/ public Parser(StringBuffer sb) - { if (sb==null) throw (new RuntimeException("Tried to costruct a new Parser with a null StringBuffer")); - str=sb.toString(); - index=0; + { + if (sb == null) throw (new RuntimeException("Tried to costruct a new Parser with a null StringBuffer")); + str = sb.toString(); + index = 0; } /** Creates the Parser from the StringBuffer <i>sb</i> and point to the position <i>i</i>. */ public Parser(StringBuffer sb, int i) - { if (sb==null) throw (new RuntimeException("Tried to costruct a new Parser with a null StringBuffer")); - str=sb.toString(); - index=i; + { + if (sb == null) throw (new RuntimeException("Tried to costruct a new Parser with a null StringBuffer")); + str = sb.toString(); + index = i; } /** Gets the current index position. */ - public int getPos() { return index; } + public int getPos() { + return index; + } /** Gets the entire string */ - public String getWholeString() { return str; } + public String getWholeString() { + return str; + } /** Gets the rest of the (unparsed) string. */ public String getRemainingString() { return str.substring(index); } diff --git a/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties b/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties old mode 100644 new mode 100755 index e0a730f19178875fbb3f4bcd2a4a048b90805d2f..7df07a8590cf9b5231424d3435239e8b8e2de94c --- a/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties +++ b/bbb-voice/src/main/webapp/WEB-INF/bigbluebutton-sip.properties @@ -1,15 +1,9 @@ # The address of your asterisk server #sip.server.host=192.168.0.182 sip.server.host=127.0.0.1 - -# The start/stop SIP ports that the application is going -# to use to connect to the asterisk server. -# These are unassigned ports as defined here -# (http://www.iana.org/assignments/port-numbers) -# We are setting up 100 default SIP users (see RTP ports below) -# so we need one port per user. -startSIPPort=34480 -stopSIPPort=34579 +sip.server.port=5070 +sip.server.username=bbbuser +sip.server.password=secret # The start/stop RTP port the application is going to use # for the media stream. @@ -18,8 +12,8 @@ stopSIPPort=34579 # in your bbb_sip.conf. # See http://code.google.com/p/bigbluebutton/source/browse/#svn/trunk/bbb-voice-conference/config/asterisk # create-sip-users.sh script to create the users. -startRTPPort=34380 -stopRTPPort=34479 +startAudioPort=34380 +stopAudioPort=34479 # An extension pattern, in case your asterisk extensions.conf # uses a naming convetion for your meeting rooms diff --git a/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml b/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml old mode 100644 new mode 100755 index 1e428dd7a6f1c2c6367068feeeee5a8a127c8b51..56e6886b63434b8b7af1b9b9ba5636d58bc762a7 --- a/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml +++ b/bbb-voice/src/main/webapp/WEB-INF/red5-web.xml @@ -28,17 +28,25 @@ <property name="virtualHosts" value="${webapp.virtualHosts}" /> </bean> - <bean id="web.handler" class="org.red5.server.webapp.sip.VoiceConferenceApplication"> - <property name="asteriskHost" value="${sip.server.host}" /> - <property name="startSIPPort" value="${startSIPPort}" /> - <property name="stopSIPPort" value="${stopSIPPort}" /> - <property name="startRTPPort" value="${startRTPPort}" /> - <property name="stopRTPPort" value="${stopRTPPort}" /> - <property name="callExtensionPattern" value="${callExtensionPattern}" /> - <property name="sipUserManager" ref="sipUserManager"/> + <bean id="web.handler" class="org.bigbluebutton.voiceconf.red5.Application"> + <property name="sipServerHost" value="${sip.server.host}" /> + <property name="sipPort" value="${sip.server.port}" /> + <property name="username" value="${sip.server.username}" /> + <property name="password" value="${sip.server.password}" /> + <property name="startAudioPort" value="${startAudioPort}" /> + <property name="stopAudioPort" value="${stopAudioPort}" /> + <property name="sipPeerManager" ref="sipPeerManager"/> + <property name="clientConnectionManager" ref="clientConnectionManager"/> </bean> - - <bean id="sipUserManager" class="org.red5.app.sip.SipUserManager"> + + <bean id="voiceconf.service" class="org.bigbluebutton.voiceconf.red5.Service"> + <property name="sipPeerManager" ref="sipPeerManager"/> + </bean> + + <bean id="sipPeerManager" class="org.bigbluebutton.voiceconf.sip.SipPeerManager"> <property name="sipStackDebugLevel" value="${sipStackDebugLevel}"/> </bean> + + <bean id="clientConnectionManager" class="org.bigbluebutton.voiceconf.red5.ClientConnectionManager"/> + </beans> diff --git a/bigbluebutton-client/resources/bbb-deskshare-applet-0.64.jar b/bigbluebutton-client/resources/bbb-deskshare-applet-0.64.jar index d20063f5a982ac807fff2d4cbdb7d35ee6c10df0..3444481f072241bd1a7cdefcd3428dda684a4efe 100644 Binary files a/bigbluebutton-client/resources/bbb-deskshare-applet-0.64.jar and b/bigbluebutton-client/resources/bbb-deskshare-applet-0.64.jar differ diff --git a/bigbluebutton-client/src/ViewersModule.mxml b/bigbluebutton-client/src/ViewersModule.mxml old mode 100644 new mode 100755 index eb161b1eedc5d9d605202f9931821da8c027fb62..b6fe5c8f91515042acc874afd3cfbf0bd5baa08b --- a/bigbluebutton-client/src/ViewersModule.mxml +++ b/bigbluebutton-client/src/ViewersModule.mxml @@ -127,6 +127,14 @@ public function get mode():String { return _attributes.mode; } + + public function set webvoiceconf(v:String):void { + _attributes.webvoiceconf = v; + } + + public function get webvoiceconf():String { + return _attributes.webvoiceconf; + } public function set voicebridge(v:String):void { _attributes.voicebridge = v; diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/BbbModuleManager.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/BbbModuleManager.as old mode 100644 new mode 100755 index abb423028e031458d12b90d1e027a09d923d35c2..c36e45dd6dd5313f987f61f41fbc540a2cfbbd09 --- a/bigbluebutton-client/src/org/bigbluebutton/main/model/BbbModuleManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/BbbModuleManager.as @@ -132,7 +132,7 @@ package org.bigbluebutton.main.model * */ public function loggedInUser(user:Object):void { - LogUtil.debug('loggedin user ' + user.username); + LogUtil.debug('loggedin user ' + user.webvoiceconf); _user = new Object(); _user.conference = user.conference; _user.username = user.username; @@ -142,6 +142,7 @@ package org.bigbluebutton.main.model _user.userid = user.userid; _user.mode = user.mode; _user.voicebridge = user.voicebridge; + _user.webvoiceconf = user.webvoiceconf; _user.connection = user.connection; _user.playbackRoom = user.playbackRoom; _user.record = user.record; @@ -197,6 +198,7 @@ package org.bigbluebutton.main.model m.addAttribute("mode", _user.mode); m.addAttribute("connection", _user.connection); m.addAttribute("voicebridge", _user.voicebridge); + m.addAttribute("webvoiceconf", _user.webvoiceconf); m.addAttribute("playbackRoom", _user.playbackRoom); m.addAttribute("record", _user.record); m.addAttribute("welcome", _user.welcome); diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/deskShare/services/DeskshareService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/deskShare/services/DeskshareService.as old mode 100644 new mode 100755 index 660d1e7bd37fc66816a509f197fffcf776bc7e3c..eb8dc837aee63ba570af937a346e581cea87e827 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/deskShare/services/DeskshareService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/deskShare/services/DeskshareService.as @@ -200,7 +200,6 @@ package org.bigbluebutton.modules.deskShare.services } public function mouseLocationCallback(x:Number, y:Number):void { - LogUtil.debug("Mouse location " + x + "," + y); var event:CursorEvent = new CursorEvent(CursorEvent.UPDATE_CURSOR_LOC_EVENT); event.x = x; event.y = y; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/ConnectionManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/ConnectionManager.as old mode 100644 new mode 100755 index 5dc3c23c2f6ba02c2642818fe5f65a043730062f..a9df9f2a0a3744deddb950fd5e1b92b5a9bb09ca --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/ConnectionManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/ConnectionManager.as @@ -127,38 +127,21 @@ package org.bigbluebutton.modules.phone.managers { // // CallBack Methods from Red5 // - //******************************************************************************************** - - public function registrationSucess(msg:String):* { - LogUtil.debug("REGISTRATION to the SIP server Succeeded. " + msg); - if (msg == "REGISTERED") { - registered = true; - var regSuccessEvent:RegistrationSuccessEvent = new RegistrationSuccessEvent(); - localDispatcher.dispatchEvent(regSuccessEvent); - } else { - LogUtil.debug("Got unregister message" + msg); - } - } - - public function registrationFailure(msg:String):* { - trace("REGISTRATION to the SIP server failed."); - var regFailedEvent:RegistrationFailedEvent = new RegistrationFailedEvent(); - localDispatcher.dispatchEvent(regFailedEvent); - } - - public function callState(msg:String):* { - LogUtil.debug("RED5Manager callState " + msg); - - if (msg == "onUaCallClosed" || msg == "onUaCallFailed") { - trace("Call has been disconnected."); - isConnected = false; - var event:CallDisconnectedEvent = new CallDisconnectedEvent(); - localDispatcher.dispatchEvent(event); - } + //******************************************************************************************** + public function failedToJoinVoiceConferenceCallback(msg:String):* { + LogUtil.debug("failedToJoinVoiceConferenceCallback " + msg); + var event:CallDisconnectedEvent = new CallDisconnectedEvent(); + localDispatcher.dispatchEvent(event); } + + public function disconnectedFromJoinVoiceConferenceCallback(msg:String):* { + LogUtil.debug("disconnectedFromJoinVoiceConferenceCallback " + msg); + var event:CallDisconnectedEvent = new CallDisconnectedEvent(); + localDispatcher.dispatchEvent(event); + } - public function connected(publishName:String, playName:String):* { - trace("Call has been connected"); + public function successfullyJoinedVoiceConferenceCallback(publishName:String, playName:String):* { + LogUtil.debug("successfullyJoinedVoiceConferenceCallback " + publishName + " : " + playName); isConnected = true; var event:CallConnectedEvent = new CallConnectedEvent(); event.publishStreamName = publishName; @@ -170,47 +153,17 @@ package org.bigbluebutton.modules.phone.managers { // // SIP Actions // - //******************************************************************************************** - - public function register():void { - trace("Open " + username); - netConnection.call("open", null, uid, username); - } - + //******************************************************************************************** public function doCall(dialStr:String):void { - trace("Calling " + dialStr); - netConnection.call("call", null, uid, dialStr); + LogUtil.debug("Calling " + dialStr); + netConnection.call("voiceconf.call", null, "default", username, dialStr); } - - public function doCallChar(chr:String):void { - if (isConnected) { - netConnection.call("dtmf", null, uid, chr); - } - } - + public function doHangUp():void { if (isConnected) { - netConnection.call("hangup", null, uid); + netConnection.call("voiceconf.hangup", null, "default"); isConnected = false; } } - - public function doAccept():void { - netConnection.call("accept", null, uid); - } - - public function doStreamStatus(status:String):void { - netConnection.call("streamStatus", null, uid, status); - } - - public function doClose():void { - if (isRegistered()) { - netConnection.call("unregister", null, uid); - } - } - - public function isRegistered():Boolean { - return registered; - } } } \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as index 01b2473f8ac487571fdedeb267d7a6161507f75e..920fed7adaf0fa8231d39b431009aee04724ed2d 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/PhoneManager.as @@ -20,20 +20,19 @@ package org.bigbluebutton.modules.phone.managers { import flash.events.IEventDispatcher; + import org.bigbluebutton.modules.phone.events.CallConnectedEvent; import org.bigbluebutton.modules.phone.events.JoinVoiceConferenceEvent; - public class PhoneManager - { + public class PhoneManager { private var localDispatcher:IEventDispatcher; private var connectionManager:ConnectionManager; private var streamManager:StreamManager; - + private var onCall:Boolean = false; private var attributes:Object; - public function PhoneManager(dispatcher:IEventDispatcher) - { + public function PhoneManager(dispatcher:IEventDispatcher) { localDispatcher = dispatcher; connectionManager = new ConnectionManager(dispatcher); streamManager = new StreamManager(dispatcher); @@ -43,41 +42,42 @@ package org.bigbluebutton.modules.phone.managers this.attributes = attributes; } - public function setupMic(useMic:Boolean):void { + private function setupMic(useMic:Boolean):void { if (useMic) streamManager.initMicrophone(); else streamManager.initWithNoMicrophone(); } - public function setupConnection():void { + private function setupConnection():void { streamManager.setConnection(connectionManager.getConnection()); } + public function join(e:JoinVoiceConferenceEvent):void { setupMic(e.useMicrophone); var uid:String = String( Math.floor( new Date().getTime() ) ); connectionManager.connect(uid, attributes.username, attributes.room, attributes.uri); } - - public function register():void { - setupConnection(); - trace("Registering...."); - connectionManager.register(); - } - + public function dialConference():void { - trace("Dialing...." + attributes.voicebridge); - connectionManager.doCall(attributes.voicebridge); + LogUtil.debug("Dialing...." + attributes.webvoiceconf); + connectionManager.doCall(attributes.webvoiceconf); } public function callConnected(event:CallConnectedEvent):void { + LogUtil.debug("Call connected..."); + setupConnection(); streamManager.callConnected(event.playStreamName, event.publishStreamName); + onCall = true; } public function hangup():void { LogUtil.debug("PhoneManager hangup"); - connectionManager.doHangUp(); - connectionManager.doClose(); + if (onCall) { + streamManager.stopStreams(); + connectionManager.doHangUp(); + onCall = false; + } } } } \ No newline at end of file diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/StreamManager.as b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/StreamManager.as index 561987803857cfa386b84748342ca112e1c6bc02..6f48d53fa806d4ffdae2c7066a839559131e0ca5 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/StreamManager.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/managers/StreamManager.as @@ -69,7 +69,7 @@ package org.bigbluebutton.modules.phone.managers } public function initWithNoMicrophone(): void { - trace("No available microphone"); + LogUtil.debug("No available microphone"); var event:MicrophoneUnavailEvent = new MicrophoneUnavailEvent(); localDispatcher.dispatchEvent(event); } @@ -90,7 +90,7 @@ package org.bigbluebutton.modules.phone.managers localDispatcher.dispatchEvent(unmutedEvent); break; default: - trace("unknown micStatusHandler event: " + event); + LogUtil.debug("unknown micStatusHandler event: " + event); } } @@ -130,10 +130,12 @@ package org.bigbluebutton.modules.phone.managers } private function play(playStreamName:String):void { + incomingStream.play(playStreamName); } private function publish(publishStreamName:String):void { + LogUtil.debug("Publishing stream " + publishStreamName); outgoingStream.publish(publishStreamName, "live"); } @@ -157,17 +159,16 @@ package org.bigbluebutton.modules.phone.managers outgoingStream.client = custom_obj; } - public function callDisconnected():void { + public function stopStreams():void { if(incomingStream != null) { incomingStream.play(false); } if(outgoingStream != null) { outgoingStream.attachAudio(null); - outgoingStream.publish(null); } - isCallConnected = false; + isCallConnected = false; } private function netStatus (evt:NetStatusEvent ):void { @@ -185,12 +186,10 @@ package org.bigbluebutton.modules.phone.managers break; case "NetStream.Play.Start": -// red5Manager.doStreamStatus("start"); event.status = PlayStreamStatusEvent.START; break; case "NetStream.Play.Stop": -// red5Manager.doStreamStatus("stop"); event.status = PlayStreamStatusEvent.STOP; break; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/maps/PhoneLocalEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/maps/PhoneLocalEventMap.mxml old mode 100644 new mode 100755 index d07fef2ca6c2e75ef3f5f841db365cbc62c88b82..282a1e49690c544df66151ab2e1bc696d7fffe57 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/phone/maps/PhoneLocalEventMap.mxml +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/phone/maps/PhoneLocalEventMap.mxml @@ -80,21 +80,17 @@ <MethodInvoker generator="{PhoneManager}" method="join" arguments="{event}"/> </EventHandlers> - <EventHandlers type="{RegistrationSuccessEvent.REGISTRATION_SUCCESS_EVENT}"> - <MethodInvoker generator="{PhoneManager}" method="dialConference"/> - </EventHandlers> - <EventHandlers type="{CallConnectedEvent.CALL_CONNECTED_EVENT}"> <MethodInvoker generator="{PhoneLocalEventMapDelegate}" method="disableToolbarButton"/> <MethodInvoker generator="{PhoneManager}" method="callConnected" arguments="{event}"/> </EventHandlers> - <EventHandlers type="{CallDisconnectedEvent.CALL_DISCONNECTED_EVENT}"> + <EventHandlers type="{CallDisconnectedEvent.CALL_DISCONNECTED_EVENT}"> <MethodInvoker generator="{PhoneLocalEventMapDelegate}" method="enableToolbarButton"/> </EventHandlers> <EventHandlers type="{ConnectionStatusEvent.CONNECTION_STATUS_EVENT}"> - <MethodInvoker generator="{PhoneManager}" method="register"/> + <MethodInvoker generator="{PhoneManager}" method="dialConference"/> </EventHandlers> </LocalEventMap> diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/ViewersEndpointMediator.as b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/ViewersEndpointMediator.as old mode 100644 new mode 100755 index e44daf742e8b1fef432319804302fef675c62db7..9de203069e1ca64c32a027b77e4de1b51d4bc890 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/ViewersEndpointMediator.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/ViewersEndpointMediator.as @@ -89,6 +89,7 @@ package org.bigbluebutton.modules.viewers userrole:_module.role, room:_module.room, authToken:_module.authToken, userid:_module.userid, connection:proxy.connection, mode:_module.mode, voicebridge:_module.voicebridge, + webvoiceconf:_module.webvoiceconf, record:_module.record, welcome:_module.welcome, meetingID:_module.meetingID, externUserID:_module.externUserID, playbackRoom:_module.playbackRoom}; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/ViewersProxy.as b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/ViewersProxy.as index c87a2abe61c326618dfd26900ce6c12bbbb70735..480e86a3d891f35e4ae4a962d9f05f77e0656ba8 100755 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/ViewersProxy.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/ViewersProxy.as @@ -71,7 +71,6 @@ package org.bigbluebutton.modules.viewers.model joinService = new JoinService(); joinService.addJoinResultListener(joinListener); joinService.load(module.host); - } private function joinListener(success:Boolean, result:Object):void { @@ -90,6 +89,7 @@ package org.bigbluebutton.modules.viewers.model module.room = _participants.me.room; module.authToken = _participants.me.authToken; module.mode = result.mode; + module.webvoiceconf = result.webvoiceconf; module.voicebridge = result.voicebridge; module.conferenceName = result.conferenceName; module.welcome = result.welcome; diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/services/JoinService.as b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/services/JoinService.as old mode 100644 new mode 100755 index 0e4888c09545d04e51327fc7a74c06f73786e671..852194f814ea9294fbdf1dec43a1a70340413117 --- a/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/services/JoinService.as +++ b/bigbluebutton-client/src/org/bigbluebutton/modules/viewers/model/services/JoinService.as @@ -53,21 +53,19 @@ package org.bigbluebutton.modules.viewers.model.services _resultListener = listener; } - private function handleComplete(e:Event):void{ - + private function handleComplete(e:Event):void { var xml:XML = new XML(e.target.data) - LogUtil.debug("Join complete: " + xml); - + var returncode:String = xml.returncode; if (returncode == 'FAILED') { - LogUtil.debug("Join Result = " + returncode + " " + xml.message); + LogUtil.debug("Join FAILED = " + xml); _resultListener(false, {message:xml.message}); } else if (returncode == 'SUCCESS') { - LogUtil.debug(xml.returncode + " " + xml.fullname + " " + xml.conference + " " + xml.role - + " " + xml.room + " " + xml.voicebridge + " " + xml.mode); + LogUtil.debug("Join SUCESS = " + xml); var user:Object = {username:xml.fullname, conference:xml.conference, conferenceName:xml.confname, meetingID:xml.meetingID, externUserID:xml.externUserID, role:xml.role, room:xml.room, authToken:xml.room, record:xml.record, + webvoiceconf:xml.webvoiceconf, voicebridge:xml.voicebridge, mode:xml.mode, welcome:xml.welcome}; _resultListener(true, user); } diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index 8d743adc05c6d69fc602a2becbd86bddac464f88..19510d8f970896fd386798a34987153eecf60027 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -29,6 +29,7 @@ # 2010-01-18 FFD Added resetting of environment back to using packages # 2010-03-02 JRT Added trunk checkout options / fixed bbb-apps instructions # 2010-04-02 FFD Updated for 0.64 +# 2010-06-21 SEB Cleaned up some code / Updated for 0.70 #set -x @@ -36,7 +37,7 @@ # This ensures that we checkout the 0.63 release of BigBlueButton # Note that this can be overridden with the --trunk option # -RELEASE="-r 4125" +RELEASE="-r 4126" # # SVNPROTO is http if read-only, or https if you specify a Google code username @@ -590,7 +591,7 @@ if [ $RESET_DEV ]; then fi getIPOfDomain() { - ipOfDomain=$(host $1 | cut -d\ -f 4) + ipOfDomain=$(ifconfig | grep -v '127.0.0.1' | grep -m 1 'inet addr:' | cut -d: -f2 | awk '{ print $1}') if [ $ipOfDomain != "out;" ]; then echo $ipOfDomain @@ -701,7 +702,7 @@ $RED5_DIRECTORY/webapps/sip/WEB-INF/bigbluebutton-sip.properties" NGINX_IP=$(cat /etc/nginx/sites-available/bigbluebutton | sed -n '/server_name/{s/.*name[ ]*//;s/;//;p}') if [ "$HOST_IP" != "$NGINX_IP" ]; then - NGINX_DOMAIN_IP=$(getIPOfDomain $NGINX_IP) + NGINX_DOMAIN_IP=$($getIPOfDomain $NGINX_IP) if [ "$HOST_IP" != "$NGINX_DOMAIN_IP" ]; then echo "Host IP does not match BigBlueButton: ${HOST_IP} != ${NGINX_IP}" diff --git a/bigbluebutton-web/grails-app/conf/BootStrap.groovy b/bigbluebutton-web/grails-app/conf/BootStrap.groovy old mode 100644 new mode 100755 index 4fce9d2d3afd419f1a677887e7e27797144f45ff..25a922bb4267368426e4d1e07af8f87cf6c50a9b --- a/bigbluebutton-web/grails-app/conf/BootStrap.groovy +++ b/bigbluebutton-web/grails-app/conf/BootStrap.groovy @@ -30,59 +30,6 @@ class BootStrap { def init = { servletContext -> log.debug "Bootstrapping bbb-web" - // Administrator user and role. - log.debug "Creating administrator" - def adminRole = new Role(name: "Administrator").save() - def adminUser = new User(username: "admin@test.com", passwordHash: new Sha1Hash("admin").toHex(), - fullName: "Admin") - adminUser.save(flush:true) - new UserRoleRel(user: adminUser, role: adminRole).save(flush:true) - - def userRole = new Role(name: "User").save() - - String createdBy = adminUser.fullName - String modifiedBy = adminUser.fullName - - log.debug "Creating default conference" - def defaultConference = new Conference( - name:"Default Conference", conferenceNumber:new Integer(85115), - user:adminUser, createdBy:createdBy, updatedBy:modifiedBy).save() -// defaultConference.save() - - log.debug "Creating a Default session for the Default Conference" - - String name = "Default Conference Session" - String description = "Default session -- use password (no quotes): 'mp' for Moderator, 'ap' for Attendee." - String sessionId = UUID.randomUUID() - String tokenId = UUID.randomUUID() - Integer numberOfAttendees = new Integer(3) - Boolean timeLimited = true - Date startDateTime = new Date() - - // Set the endDate for this session to Dec. 31, 2010 - java.util.Calendar calendar = java.util.Calendar.getInstance(); - calendar.set(java.util.Calendar.MONTH, java.util.Calendar.DECEMBER); - calendar.set(java.util.Calendar.DAY_OF_MONTH, 31); - calendar.set(java.util.Calendar.YEAR, 2010) - - Date endDateTime = calendar.getTime() - - Boolean record = false - Boolean passwordProtect = true - String hostPassword = 'hp' - String moderatorPassword = 'mp' - String attendeePassword = 'ap' - String voiceConferenceBridge = '85115' - - def defaultConfSession = new ScheduledSession( - name:name, description:description, - createdBy:createdBy, modifiedBy:modifiedBy, sessionId:sessionId, tokenId:tokenId, - numberOfAttendees:numberOfAttendees, timeLimited:timeLimited, startDateTime:startDateTime, - endDateTime:endDateTime, record:record, passwordProtect:passwordProtect, hostPassword:hostPassword, - moderatorPassword:moderatorPassword, attendeePassword:attendeePassword, - voiceConferenceBridge:voiceConferenceBridge, conference:defaultConference - ).save() -// defaultConfSession.save() } def destroy = { diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy old mode 100644 new mode 100755 index 58ca4fe7eb74d10aa2fb35abb632b89dad74eb7d..e10d43c93c67bd4ef5e5edd6ed2146c4e7500784 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -193,6 +193,7 @@ class ApiController { return } + String webVoice = params.webVoiceConf String mtgID = params.meetingID String attPW = params.password boolean redirectImm = parseBoolean(params.redirectImmediately) @@ -223,18 +224,19 @@ class ApiController { invalidPassword("You either did not supply a password or the password supplied is neither the attendee or moderator password for this conference."); return; } + conf.setWebVoiceConf(webVoice == null || webVoice == "" ? conf.voiceBridge : webVoice) // TODO: success.... log.debug "join successful - setting session parameters and redirecting to join" session["conferencename"] = conf.meetingID session["meetingID"] = conf.meetingID session["externUserID"] = externUserID - session["fullname"] = fullName session["role"] = role session["conference"] = conf.getMeetingToken() session["room"] = conf.getMeetingToken() session["voicebridge"] = conf.getVoiceBridge() + session["webvoiceconf"] = conf.getWebVoiceConf() session["mode"] = "LIVE" session["record"] = false session['welcome'] = conf.welcome diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PublicScheduledSessionController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PublicScheduledSessionController.groovy old mode 100644 new mode 100755 index 7ae3c793e2ff91c0a6865921a61239781d73ca38..82e22aba36e5b1152087adefe554147561d9c27d --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PublicScheduledSessionController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/PublicScheduledSessionController.groovy @@ -228,7 +228,8 @@ class PublicScheduledSessionController { def rl = session["role"] def cnf = session["conference"] def rm = session["room"] - def vb = session["voicebridge"] + def vb = session["voicebridge"] + def wbv = session["webvoiceconf"] def rec = session["record"] def md = session["mode"] def confName = session["conferencename"] @@ -262,7 +263,8 @@ class PublicScheduledSessionController { role("$rl") conference("$cnf") room("$rm") - voicebridge("${vb}") + voicebridge("${vb}") + webvoiceconf("${wbv}") mode("$md") record("$rec") welcome("$welcomeMsg") diff --git a/bigbluebutton-web/src/groovy/org/bigbluebutton/api/domain/DynamicConference.groovy b/bigbluebutton-web/src/groovy/org/bigbluebutton/api/domain/DynamicConference.groovy old mode 100644 new mode 100755 index eae28a380a3c7a9d4bdb4eb68f0c2265bac12c1e..30f3442cc908e6553cbf7c8aa37b8d2f24685873 --- a/bigbluebutton-web/src/groovy/org/bigbluebutton/api/domain/DynamicConference.groovy +++ b/bigbluebutton-web/src/groovy/org/bigbluebutton/api/domain/DynamicConference.groovy @@ -37,7 +37,7 @@ public class DynamicConference extends Conference { String meetingID String meetingToken String voiceBridge - + String webVoiceConf String moderatorPassword String attendeePassword String welcome diff --git a/deskshare/.gitignore b/deskshare/.gitignore index 8de66347f2054a11f3b3754938f856370def8376..247df7ed7d6044861966091da3f6264f96cef2c0 100644 --- a/deskshare/.gitignore +++ b/deskshare/.gitignore @@ -1,3 +1,4 @@ +.manager .scala_dependencies .classpath .gradle/ diff --git a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java old mode 100644 new mode 100755 index 61cfb3a3d88ea60dc5c4285e608916292a0dcc24..de72a2ff059e0b4b3ba4cce654042a30ada2148b --- a/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java +++ b/deskshare/app/src/main/java/org/bigbluebutton/deskshare/server/socket/BlockStreamProtocolDecoder.java @@ -89,6 +89,7 @@ public class BlockStreamProtocolDecoder extends CumulativeProtocolDecoder { break; case MOUSE_LOCATION_EVENT: decodeMouseLocationEvent(session, in, out); + break; default: log.error("Unknown event: " + event); }