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);
     	}