diff --git a/configure.ac b/configure.ac
index 7932b835ca..bc3cafa0bd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3,9 +3,9 @@
 
 # Must change all of the below together
 # For a release, set revision for that tagged release as well and uncomment
-AC_INIT([freeswitch], [1.7.0], bugs@freeswitch.org)
+AC_INIT([freeswitch], [1.9.0], bugs@freeswitch.org)
 AC_SUBST(SWITCH_VERSION_MAJOR, [1])
-AC_SUBST(SWITCH_VERSION_MINOR, [7])
+AC_SUBST(SWITCH_VERSION_MINOR, [9])
 AC_SUBST(SWITCH_VERSION_MICRO, [0])
 AC_SUBST(SWITCH_VERSION_REVISION, [])
 AC_SUBST(SWITCH_VERSION_REVISION_HUMAN, [])
diff --git a/html5/verto/js/src/jquery.verto.js b/html5/verto/js/src/jquery.verto.js
index d0c3563c12..0e0e38bf38 100644
--- a/html5/verto/js/src/jquery.verto.js
+++ b/html5/verto/js/src/jquery.verto.js
@@ -2062,12 +2062,18 @@
         obj.dialogParams = {};
 
         for (var i in dialog.params) {
-            if (i == "sdp" && method != "verto.invite" && method != "verto.attach") {
+	    if (i == "sdp" && method != "verto.invite" && method != "verto.attach") {
                 continue;
-            }
+	    }
+	    
+	    if ((obj.noDialogParams && i != "callID")) {
+		continue;
+	    }
 
-            obj.dialogParams[i] = dialog.params[i];
+	    obj.dialogParams[i] = dialog.params[i];
         }
+	
+	delete obj.noDialogParams;
 
         dialog.verto.rpcClient.call(method, obj,
 
@@ -2371,6 +2377,26 @@
         }
     };
 
+    $.verto.dialog.prototype.rtt = function(obj) {
+        var dialog = this;
+	var pobj = {};
+
+	if (!obj) {
+	    return false;
+	}
+
+	pobj.code = obj.code;
+	pobj.chars = obj.chars;
+
+
+        if (pobj.chars || pobj.code) {
+            dialog.sendMethod("verto.info", {
+                txt: obj,
+		noDialogParams: true
+            });
+        }
+    };
+
     $.verto.dialog.prototype.transfer = function(dest, params) {
         var dialog = this;
         if (dest) {
@@ -2517,7 +2543,8 @@
     $.verto.dialog.prototype.handleInfo = function(params) {
         var dialog = this;
 
-        dialog.sendMessage($.verto.enum.message.info, params.msg);
+        dialog.sendMessage($.verto.enum.message.info, params);
+
     };
 
     $.verto.dialog.prototype.handleDisplay = function(params) {
diff --git a/html5/verto/video_demo/index.html b/html5/verto/video_demo/index.html
index 229275e9cf..edd80e27f2 100644
--- a/html5/verto/video_demo/index.html
+++ b/html5/verto/video_demo/index.html
@@ -244,6 +244,14 @@ div#preload { display: none; }
 
 
       <video id="webcam" autoplay="autoplay" style="width:100%;height:100%;object-fit:inherit;"></video>
+<br><br>
+	<table><tr><td>
+      <div style="width:500px;max-height:300px;overflow-y:scroll;display:block;unicode-bidi:embed;font-family:tahoma;white-space:pre;size:14pt;text-align:left" id="rtt_in"></div>
+</td></tr>
+<tr><td>
+      <textarea style="width:500px;height:200px" id="rtt"></textarea>
+</td></tr>
+</table>
       <!--<video id="local_webcam" autoplay="autoplay" style="width:100%;height:100%;object-fit:inherit;"></video>-->
 
 </div><!-- rows -->
diff --git a/html5/verto/video_demo/js/verto-min.js b/html5/verto/video_demo/js/verto-min.js
index dbe0017137..95d5cf1111 100644
--- a/html5/verto/video_demo/js/verto-min.js
+++ b/html5/verto/video_demo/js/verto-min.js
@@ -255,8 +255,9 @@ if(!dialog.params.remote_caller_id_number){dialog.params.remote_caller_id_number
 RTCcallbacks.onMessage=function(rtc,msg){console.debug(msg);};RTCcallbacks.onAnswerSDP=function(rtc,sdp){console.error("answer sdp",sdp);};}else{dialog.params.remote_caller_id_name="Outbound Call";dialog.params.remote_caller_id_number=dialog.params.destination_number;}
 RTCcallbacks.onICESDP=function(rtc){console.log("RECV "+rtc.type+" SDP",rtc.mediaData.SDP);if(dialog.state==$.verto.enum.state.requesting||dialog.state==$.verto.enum.state.answering||dialog.state==$.verto.enum.state.active){location.reload();return;}
 if(rtc.type=="offer"){if(dialog.state==$.verto.enum.state.active){dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.attach",{sdp:rtc.mediaData.SDP});}else{dialog.setState($.verto.enum.state.requesting);dialog.sendMethod("verto.invite",{sdp:rtc.mediaData.SDP});}}else{dialog.setState($.verto.enum.state.answering);dialog.sendMethod(dialog.attach?"verto.attach":"verto.answer",{sdp:dialog.rtc.mediaData.SDP});}};RTCcallbacks.onICE=function(rtc){if(rtc.type=="offer"){console.log("offer",rtc.mediaData.candidate);return;}};RTCcallbacks.onStream=function(rtc,stream){console.log("stream started");};RTCcallbacks.onError=function(e){console.error("ERROR:",e);dialog.hangup({cause:"Device or Permission Error"});};dialog.rtc=new $.FSRTC({callbacks:RTCcallbacks,localVideo:dialog.screenShare?null:dialog.localVideo,useVideo:dialog.params.useVideo?dialog.videoStream:null,useAudio:dialog.audioStream,useStereo:dialog.params.useStereo,videoParams:dialog.params.videoParams,audioParams:verto.options.audioParams,iceServers:verto.options.iceServers,screenShare:dialog.screenShare,useCamera:dialog.useCamera,useMic:dialog.useMic,useSpeak:dialog.useSpeak});dialog.rtc.verto=dialog.verto;if(dialog.direction==$.verto.enum.direction.inbound){if(dialog.attach){dialog.answer();}else{dialog.ring();}}};$.verto.dialog.prototype.invite=function(){var dialog=this;dialog.rtc.call();};$.verto.dialog.prototype.sendMethod=function(method,obj){var dialog=this;obj.dialogParams={};for(var i in dialog.params){if(i=="sdp"&&method!="verto.invite"&&method!="verto.attach"){continue;}
+if((obj.noDialogParams&&i!="callID")){continue;}
 obj.dialogParams[i]=dialog.params[i];}
-dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;}
+delete obj.noDialogParams;dialog.verto.rpcClient.call(method,obj,function(e){dialog.processReply(method,true,e);},function(e){dialog.processReply(method,false,e);});};function checkStateChange(oldS,newS){if(newS==$.verto.enum.state.purge||$.verto.enum.states[oldS.name][newS.name]){return true;}
 return false;}
 function find_name(id){for(var i in $.verto.audioOutDevices){var source=$.verto.audioOutDevices[i];if(source.id===id){return(source.label);}}
 return id;}
@@ -280,7 +281,8 @@ if(success){}
 break;default:break;}};$.verto.dialog.prototype.hangup=function(params){var dialog=this;if(params){if(params.causeCode){dialog.causeCode=params.causeCode;}
 if(params.cause){dialog.cause=params.cause;}}
 if(dialog.state.val>=$.verto.enum.state.new.val&&dialog.state.val<$.verto.enum.state.hangup.val){dialog.setState($.verto.enum.state.hangup);}else if(dialog.state.val<$.verto.enum.state.destroy){dialog.setState($.verto.enum.state.destroy);}};$.verto.dialog.prototype.stopRinging=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.stop();}};$.verto.dialog.prototype.indicateRing=function(){var dialog=this;if(dialog.verto.ringer){dialog.verto.ringer.attr("src",dialog.verto.options.ringFile)[0].play();setTimeout(function(){dialog.stopRinging();if(dialog.state==$.verto.enum.state.ringing){dialog.indicateRing();}},dialog.verto.options.ringSleep);}};$.verto.dialog.prototype.ring=function(){var dialog=this;dialog.setState($.verto.enum.state.ringing);dialog.indicateRing();};$.verto.dialog.prototype.useVideo=function(on){var dialog=this;dialog.params.useVideo=on;if(on){dialog.videoStream=dialog.audioStream;}else{dialog.videoStream=null;}
-dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
+dialog.rtc.useVideo(dialog.videoStream,dialog.localVideo);};$.verto.dialog.prototype.setMute=function(what){var dialog=this;return dialog.rtc.setMute(what);};$.verto.dialog.prototype.getMute=function(){var dialog=this;return dialog.rtc.getMute();};$.verto.dialog.prototype.setVideoMute=function(what){var dialog=this;return dialog.rtc.setVideoMute(what);};$.verto.dialog.prototype.getVideoMute=function(){var dialog=this;return dialog.rtc.getVideoMute();};$.verto.dialog.prototype.useStereo=function(on){var dialog=this;dialog.params.useStereo=on;dialog.rtc.useStereo(on);};$.verto.dialog.prototype.dtmf=function(digits){var dialog=this;if(digits){dialog.sendMethod("verto.info",{dtmf:digits});}};$.verto.dialog.prototype.rtt=function(obj){var dialog=this;var pobj={};if(!obj){return false;}
+pobj.code=obj.code;pobj.chars=obj.chars;if(pobj.chars||pobj.code){dialog.sendMethod("verto.info",{txt:obj,noDialogParams:true});}};$.verto.dialog.prototype.transfer=function(dest,params){var dialog=this;if(dest){dialog.sendMethod("verto.modify",{action:"transfer",destination:dest,params:params});}};$.verto.dialog.prototype.hold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"hold",params:params});};$.verto.dialog.prototype.unhold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"unhold",params:params});};$.verto.dialog.prototype.toggleHold=function(params){var dialog=this;dialog.sendMethod("verto.modify",{action:"toggleHold",params:params});};$.verto.dialog.prototype.message=function(msg){var dialog=this;var err=0;msg.from=dialog.params.login;if(!msg.to){console.error("Missing To");err++;}
 if(!msg.body){console.error("Missing Body");err++;}
 if(err){return false;}
 dialog.sendMethod("verto.info",{msg:msg});return true;};$.verto.dialog.prototype.answer=function(params){var dialog=this;if(!dialog.answered){if(!params){params={};}
@@ -289,7 +291,7 @@ dialog.params.callee_id_name=params.callee_id_name;dialog.params.callee_id_numbe
 if(params.useMic){dialog.useMic=params.useMic;}
 if(params.useSpeak){dialog.useSpeak=params.useSpeak;}}
 dialog.rtc.createAnswer(params);dialog.answered=true;}};$.verto.dialog.prototype.handleAnswer=function(params){var dialog=this;dialog.gotAnswer=true;if(dialog.state.val>=$.verto.enum.state.active.val){return;}
-if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params.msg);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
+if(dialog.state.val>=$.verto.enum.state.early.val){dialog.setState($.verto.enum.state.active);}else{if(dialog.gotEarly){console.log("Dialog "+dialog.callID+" Got answer while still establishing early media, delaying...");}else{console.log("Dialog "+dialog.callID+" Answering Channel");dialog.rtc.answer(params.sdp,function(){dialog.setState($.verto.enum.state.active);},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"ANSWER SDP",params.sdp);}}};$.verto.dialog.prototype.cidString=function(enc){var dialog=this;var party=dialog.params.remote_caller_id_name+(enc?" &lt;":" <")+dialog.params.remote_caller_id_number+(enc?"&gt;":">");return party;};$.verto.dialog.prototype.sendMessage=function(msg,params){var dialog=this;if(dialog.callbacks.onMessage){dialog.callbacks.onMessage(dialog.verto,dialog,msg,params);}};$.verto.dialog.prototype.handleInfo=function(params){var dialog=this;dialog.sendMessage($.verto.enum.message.info,params);};$.verto.dialog.prototype.handleDisplay=function(params){var dialog=this;if(params.display_name){dialog.params.remote_caller_id_name=params.display_name;}
 if(params.display_number){dialog.params.remote_caller_id_number=params.display_number;}
 dialog.sendMessage($.verto.enum.message.display,{});};$.verto.dialog.prototype.handleMedia=function(params){var dialog=this;if(dialog.state.val>=$.verto.enum.state.early.val){return;}
 dialog.gotEarly=true;dialog.rtc.answer(params.sdp,function(){console.log("Dialog "+dialog.callID+"Establishing early media");dialog.setState($.verto.enum.state.early);if(dialog.gotAnswer){console.log("Dialog "+dialog.callID+"Answering Channel");dialog.setState($.verto.enum.state.active);}},function(e){console.error(e);dialog.hangup();});console.log("Dialog "+dialog.callID+"EARLY SDP",params.sdp);};$.verto.ENUM=function(s){var i=0,o={};s.split(" ").map(function(x){o[x]={name:x,val:i++};});return Object.freeze(o);};$.verto.enum={};$.verto.enum.states=Object.freeze({new:{requesting:1,recovering:1,ringing:1,destroy:1,answering:1,hangup:1},requesting:{trying:1,hangup:1,active:1},recovering:{answering:1,hangup:1},trying:{active:1,early:1,hangup:1},ringing:{answering:1,hangup:1},answering:{active:1,hangup:1},active:{answering:1,requesting:1,hangup:1,held:1},held:{hangup:1,active:1},early:{hangup:1,active:1},hangup:{destroy:1},destroy:{},purge:{destroy:1}});$.verto.enum.state=$.verto.ENUM("new requesting trying recovering ringing answering early active held hangup destroy purge");$.verto.enum.direction=$.verto.ENUM("inbound outbound");$.verto.enum.message=$.verto.ENUM("display info pvtEvent");$.verto.enum=Object.freeze($.verto.enum);$.verto.saved=[];$.verto.unloadJobs=[];$(window).bind('beforeunload',function(){for(var f in $.verto.unloadJobs){$.verto.unloadJobs[f]();}
diff --git a/html5/verto/video_demo/verto.js b/html5/verto/video_demo/verto.js
index e0ee4aec19..3b06234439 100644
--- a/html5/verto/video_demo/verto.js
+++ b/html5/verto/video_demo/verto.js
@@ -403,41 +403,67 @@ var callbacks = {
             }
             break;
         case $.verto.enum.message.info:
-	    var body = data.body;
-
+	    if (data.msg) {
+		data = data.msg;
+		var body = data.body;
+		
 		/*
 		// This section has been replaced with messageTextToJQ function
 
-	    if (body.match(/\.gif|\.jpg|\.jpeg|\.png/)) {
+		if (body.match(/\.gif|\.jpg|\.jpeg|\.png/)) {
 		var mod = "";
 		if (body.match(/dropbox.com/)) {
-		    mod = "?dl=1";
+		mod = "?dl=1";
 		}
 		body = body.replace(/(http[s]{0,1}:\/\/\S+)/g, "<a target='_blank' href='$1'>$1<br><img border='0' class='chatimg' src='$1'" + mod + "><\/a>");
-	    } else {
+		} else {
 		body = body.replace(/(http[s]{0,1}:\/\/\S+)/g, "<a target='_blank' href='$1'>$1<\/a>");
-	    }
+		}
 
-	    if (body.slice(-1) !== "\n") {
+		if (body.slice(-1) !== "\n") {
 		body += "\n";
-	    }
-	    body = body.replace(/(?:\r\n|\r|\n)/g, '<br />');
-	    
-    	    var from = data.from_msg_name || data.from;
+		}
+		body = body.replace(/(?:\r\n|\r|\n)/g, '<br />');
+		
+    		var from = data.from_msg_name || data.from;
 
-            $("#chatwin").append("<span class=chatuid>" + from + ":</span><br>" + body);
-	    $('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast");
+		$("#chatwin").append("<span class=chatuid>" + from + ":</span><br>" + body);
+		$('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast");
 		*/
 		
-			var from = data.from_msg_name || data.from;
-			
-			$('#chatwin')
-				.append($('<span class="chatuid" />').text(from + ':'))
-				.append($('<br />'))
-				.append(messageTextToJQ(body))
-				.append($('<br />'));
-			$('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast");
+		var from = data.from_msg_name || data.from;
 		
+		$('#chatwin')
+		    .append($('<span class="chatuid" />').text(from + ':'))
+		    .append($('<br />'))
+		    .append(messageTextToJQ(body))
+		    .append($('<br />'));
+		$('#chatwin').animate({"scrollTop": $('#chatwin')[0].scrollHeight}, "fast");
+	    }
+
+	    if (data.txt) {
+		console.log(data.txt);
+		if (data.txt.chars) {
+		    var a = [...data.txt.chars];
+		    //console.log(a);
+		    for (var x in a) {
+			if(a[x] == "\r") {
+			    $("#rtt_in").append("\n");
+			    continue;
+			} else if (a[x] == "\b") {
+			    $("#rtt_in").text($("#rtt_in").text().slice(0, -1));
+			    continue;
+			}
+			console.log("[" + a[x] + "]");
+			$("#rtt_in").append(a[x]);
+		    }
+
+		    var psconsole = $('#rtt_in');
+		    if(psconsole.length)
+			psconsole.scrollTop(psconsole[0].scrollHeight - psconsole.height());
+		}
+	    }
+
             break;
         case $.verto.enum.message.display:
             var party = dialog.params.remote_caller_id_name + "<" + dialog.params.remote_caller_id_number + ">";
@@ -1558,6 +1584,30 @@ function init() {
 
     setupChat();
 
+    $("#rtt").val("");
+    $("#rtt_in").text("");
+
+
+    $("#rtt").keyup(function (event) {
+	console.error(event);
+	console.log("KEY (" + event.which + ")\n");
+
+	if (event.which == 8) {
+	    cur_call.rtt({code: event.which});
+	}
+
+	if (event.which == 13) {
+	    $("#rtt").val("");
+	}
+
+    });
+
+    $("#rtt").keypress(function (event) {
+	console.error(event);
+	console.log("TEXT (" + event.which + ")\n");
+	cur_call.rtt({code: event.which});
+    });
+
     $("#ext").keyup(function (event) {
 	if (event.keyCode == 13) {
 	    $( "#callbtn" ).trigger( "click" );   
diff --git a/libs/esl/src/esl_event.c b/libs/esl/src/esl_event.c
index 24f7163976..d097f74569 100644
--- a/libs/esl/src/esl_event.c
+++ b/libs/esl/src/esl_event.c
@@ -147,6 +147,7 @@ static const char *EVENT_NAMES[] = {
 	"CALL_SETUP_RESULT",
 	"CALL_DETAIL",
 	"DEVICE_STATE",
+	"REAL_TIME_TEXT",
 	"ALL"
 };
 
diff --git a/libs/esl/src/include/esl_event.h b/libs/esl/src/include/esl_event.h
index 47c38b6cf7..1380f68092 100644
--- a/libs/esl/src/include/esl_event.h
+++ b/libs/esl/src/include/esl_event.h
@@ -137,6 +137,7 @@ typedef enum {
 	ESL_EVENT_CALL_SETUP_RESULT,
 	ESL_EVENT_CALL_DETAIL,
 	ESL_EVENT_DEVICE_STATE,
+	ESL_EVENT_REAL_TIME_TEXT,
 	ESL_EVENT_ALL
 } esl_event_types_t;
 
diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf
index 8a2b08ab7c..42281c1ec2 100644
--- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf
+++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp.bnf
@@ -85,7 +85,7 @@
 
    media =               1*(alpha-numeric)
                          ;typically "audio", "video", "application"
-                         ;or "data"
+                         ;or "data" or "text"
 
    fmt =                 1*(alpha-numeric)
                          ;typically an RTP payload type for audio
diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c
index 887e4e818a..3b1190ee5f 100644
--- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c
+++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_parse.c
@@ -1275,7 +1275,7 @@ static void parse_media(sdp_parser_t *p, char *r, sdp_media_t **result)
 
    media =               token
                          ;typically "audio", "video", "application"
-                         ;or "data"
+                         ;or "data" or "text"
 
    fmt =                 token
                          ;typically an RTP payload type for audio
@@ -1378,6 +1378,8 @@ void sdp_media_type(sdp_media_t *m, char const *s)
     m->m_type = sdp_media_image, m->m_type_name = "image";
   else if (su_casematch(s, "red"))
     m->m_type = sdp_media_red, m->m_type_name = "red";
+  else if (su_casematch(s, "text"))
+    m->m_type = sdp_media_text, m->m_type_name = "text";
   else
     m->m_type = sdp_media_x, m->m_type_name = s;
 }
diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c
index 0f8e390ebf..767556d7ac 100644
--- a/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c
+++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sdp_print.c
@@ -583,6 +583,7 @@ static void print_media(sdp_printer_t *p,
     case sdp_media_control:     media = "control"; break;
     case sdp_media_message:     media = "message"; break;
     case sdp_media_image  :     media = "image"; break;
+    case sdp_media_text   :     media = "text"; break;
     default:                    media = m->m_type_name;
     }
 
diff --git a/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h b/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h
index 6034a7a37a..bf0741e044 100644
--- a/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h
+++ b/libs/sofia-sip/libsofia-sip-ua/sdp/sofia-sip/sdp.h
@@ -232,7 +232,8 @@ typedef enum
   sdp_media_message,			/**< Messaging sessions*/
   sdp_media_image,			/**< Image browsing sessions,
 					 *   e.g., JPIP or T.38. */
-  sdp_media_red				/**< Redundancy. @NEW_1_12_4. */
+  sdp_media_red,			/**< Redundancy. @NEW_1_12_4. */
+  sdp_media_text,			/**< Realtime Text */
 } sdp_media_e;
 
 /** Media transport protocol. */
diff --git a/src/include/private/switch_core_pvt.h b/src/include/private/switch_core_pvt.h
index 4204fcd0d5..bf4dd351e1 100644
--- a/src/include/private/switch_core_pvt.h
+++ b/src/include/private/switch_core_pvt.h
@@ -189,7 +189,13 @@ struct switch_core_session {
 	uint32_t decoder_errors;
 	switch_core_video_thread_callback_func_t video_read_callback;
 	void *video_read_user_data;
+	switch_core_video_thread_callback_func_t text_read_callback;
+	void *text_read_user_data;
+	switch_io_routines_t *io_override;
 	switch_slin_data_t *sdata;
+
+	switch_buffer_t *text_buffer;
+	switch_mutex_t *text_mutex;
 };
 
 struct switch_media_bug {
@@ -228,6 +234,11 @@ struct switch_media_bug {
 	switch_image_t *spy_img[2];
 	switch_vid_spy_fmt_t spy_fmt;
 	switch_thread_t *video_bug_thread;
+	
+	switch_buffer_t *text_buffer;
+	char *text_framedata;
+	uint32_t text_framesize;
+	
 	struct switch_media_bug *next;
 };
 
diff --git a/src/include/switch_buffer.h b/src/include/switch_buffer.h
index a3c5032343..e5af0baf0a 100644
--- a/src/include/switch_buffer.h
+++ b/src/include/switch_buffer.h
@@ -162,6 +162,8 @@ SWITCH_DECLARE(void) switch_buffer_destroy(switch_buffer_t **buffer);
 SWITCH_DECLARE(switch_size_t) switch_buffer_zwrite(_In_ switch_buffer_t *buffer, _In_bytecount_(datalen)
 												   const void *data, _In_ switch_size_t datalen);
 
+SWITCH_DECLARE(void *) switch_buffer_get_head_pointer(switch_buffer_t *buffer);
+
 /** @} */
 
 SWITCH_END_EXTERN_C
diff --git a/src/include/switch_channel.h b/src/include/switch_channel.h
index d3267ae122..b1f4b2d219 100644
--- a/src/include/switch_channel.h
+++ b/src/include/switch_channel.h
@@ -317,6 +317,11 @@ SWITCH_DECLARE(switch_status_t) switch_channel_get_variables(switch_channel_t *c
 
 SWITCH_DECLARE(switch_status_t) switch_channel_pass_callee_id(switch_channel_t *channel, switch_channel_t *other_channel);
 
+
+static inline int switch_channel_var_true(switch_channel_t *channel, const char *variable) {
+	return switch_true(switch_channel_get_variable_dup(channel, variable, SWITCH_FALSE, -1));
+}
+
 /*!
  * \brief Start iterating over the entries in the channel variable list.
  * \param channel the channel to iterate the variables for
diff --git a/src/include/switch_core.h b/src/include/switch_core.h
index 86763f9713..3d8b2e8988 100644
--- a/src/include/switch_core.h
+++ b/src/include/switch_core.h
@@ -351,6 +351,8 @@ SWITCH_DECLARE(void) switch_core_media_bug_set_read_demux_frame(_In_ switch_medi
 */
 SWITCH_DECLARE(switch_core_session_t *) switch_core_media_bug_get_session(_In_ switch_media_bug_t *bug);
 
+SWITCH_DECLARE(const char *) switch_core_media_bug_get_text(switch_media_bug_t *bug);
+
 /*!
   \brief Test for the existance of a flag on an media bug
   \param bug the object to test
@@ -1163,6 +1165,8 @@ SWITCH_DECLARE(void *) switch_core_session_get_stream(_In_ switch_core_session_t
 */
 SWITCH_DECLARE(int) switch_core_session_get_stream_count(_In_ switch_core_session_t *session);
 
+SWITCH_DECLARE(const char *) switch_core_session_get_text_buffer(switch_core_session_t *session);
+
 /*! 
   \brief Launch a thread designed to exist within the scope of a given session
   \param session a session to allocate the thread from
@@ -2741,6 +2745,8 @@ SWITCH_DECLARE(int) switch_stream_system(const char *cmd, switch_stream_handle_t
 SWITCH_DECLARE(switch_call_direction_t) switch_ice_direction(switch_core_session_t *session);
 SWITCH_DECLARE(void) switch_core_session_debug_pool(switch_stream_handle_t *stream);
 
+SWITCH_DECLARE(switch_status_t) switch_core_session_override_io_routines(switch_core_session_t *session, switch_io_routines_t *ior);
+
 SWITCH_DECLARE(const char *)switch_version_major(void);
 SWITCH_DECLARE(const char *)switch_version_minor(void);
 SWITCH_DECLARE(const char *)switch_version_micro(void);
@@ -2752,6 +2758,9 @@ SWITCH_DECLARE(const char *)switch_version_full_human(void);
 
 SWITCH_DECLARE(void) switch_core_autobind_cpu(void);
 
+SWITCH_DECLARE(switch_status_t) switch_core_session_start_text_thread(switch_core_session_t *session);
+
+
 SWITCH_END_EXTERN_C
 #endif
 /* For Emacs:
diff --git a/src/include/switch_core_event_hook.h b/src/include/switch_core_event_hook.h
index 26dfb0c87d..f991955bd4 100644
--- a/src/include/switch_core_event_hook.h
+++ b/src/include/switch_core_event_hook.h
@@ -41,6 +41,8 @@ typedef struct switch_io_event_hook_read_frame switch_io_event_hook_read_frame_t
 typedef struct switch_io_event_hook_video_read_frame switch_io_event_hook_video_read_frame_t;
 typedef struct switch_io_event_hook_write_frame switch_io_event_hook_write_frame_t;
 typedef struct switch_io_event_hook_video_write_frame switch_io_event_hook_video_write_frame_t;
+typedef struct switch_io_event_hook_text_read_frame switch_io_event_hook_text_read_frame_t;
+typedef struct switch_io_event_hook_text_write_frame switch_io_event_hook_text_write_frame_t;
 typedef struct switch_io_event_hook_kill_channel switch_io_event_hook_kill_channel_t;
 typedef struct switch_io_event_hook_send_dtmf switch_io_event_hook_send_dtmf_t;
 typedef struct switch_io_event_hook_recv_dtmf switch_io_event_hook_recv_dtmf_t;
@@ -54,6 +56,8 @@ typedef switch_status_t (*switch_read_frame_hook_t) (switch_core_session_t *, sw
 typedef switch_status_t (*switch_video_read_frame_hook_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int);
 typedef switch_status_t (*switch_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int);
 typedef switch_status_t (*switch_video_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int);
+typedef switch_status_t (*switch_text_read_frame_hook_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int);
+typedef switch_status_t (*switch_text_write_frame_hook_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int);
 typedef switch_status_t (*switch_kill_channel_hook_t) (switch_core_session_t *, int);
 typedef switch_status_t (*switch_send_dtmf_hook_t) (switch_core_session_t *, const switch_dtmf_t *, switch_dtmf_direction_t direction);
 typedef switch_status_t (*switch_recv_dtmf_hook_t) (switch_core_session_t *, const switch_dtmf_t *, switch_dtmf_direction_t direction);
@@ -108,6 +112,20 @@ struct switch_io_event_hook_video_write_frame {
 	struct switch_io_event_hook_video_write_frame *next;
 };
 
+/*! \brief Node in which to store custom read frame channel callback hooks */
+struct switch_io_event_hook_text_read_frame {
+	/*! the read frame channel callback hook */
+	switch_read_frame_hook_t text_read_frame;
+	struct switch_io_event_hook_text_read_frame *next;
+};
+
+/*! \brief Node in which to store custom video_write_frame channel callback hooks */
+struct switch_io_event_hook_text_write_frame {
+	/*! the video_write_frame channel callback hook */
+	switch_video_write_frame_hook_t text_write_frame;
+	struct switch_io_event_hook_text_write_frame *next;
+};
+
 /*! \brief Node in which to store custom kill channel callback hooks */
 struct switch_io_event_hook_kill_channel {
 	/*! the kill channel callback hook */
@@ -158,8 +176,12 @@ struct switch_io_event_hooks {
 	switch_io_event_hook_video_read_frame_t *video_read_frame;
 	/*! a list of write frame hooks */
 	switch_io_event_hook_write_frame_t *write_frame;
-	/*! a list of video write frame hooks */
+	/*! a list of text write frame hooks */
 	switch_io_event_hook_video_write_frame_t *video_write_frame;
+	/*! a list of text write frame hooks */
+	switch_io_event_hook_text_write_frame_t *text_write_frame;
+	/*! a list of text read frame hooks */
+	switch_io_event_hook_text_read_frame_t *text_read_frame;
 	/*! a list of kill channel hooks */
 	switch_io_event_hook_kill_channel_t *kill_channel;
 	/*! a list of send dtmf hooks */
@@ -225,6 +247,8 @@ NEW_HOOK_DECL_ADD_P(read_frame);
 NEW_HOOK_DECL_ADD_P(write_frame);
 NEW_HOOK_DECL_ADD_P(video_read_frame);
 NEW_HOOK_DECL_ADD_P(video_write_frame);
+NEW_HOOK_DECL_ADD_P(text_read_frame);
+NEW_HOOK_DECL_ADD_P(text_write_frame);
 NEW_HOOK_DECL_ADD_P(kill_channel);
 NEW_HOOK_DECL_ADD_P(send_dtmf);
 NEW_HOOK_DECL_ADD_P(recv_dtmf);
@@ -238,6 +262,8 @@ NEW_HOOK_DECL_REM_P(read_frame);
 NEW_HOOK_DECL_REM_P(write_frame);
 NEW_HOOK_DECL_REM_P(video_read_frame);
 NEW_HOOK_DECL_REM_P(video_write_frame);
+NEW_HOOK_DECL_REM_P(text_read_frame);
+NEW_HOOK_DECL_REM_P(text_write_frame);
 NEW_HOOK_DECL_REM_P(kill_channel);
 NEW_HOOK_DECL_REM_P(send_dtmf);
 NEW_HOOK_DECL_REM_P(recv_dtmf);
diff --git a/src/include/switch_core_media.h b/src/include/switch_core_media.h
index 096bc815fc..5bca68d6c0 100644
--- a/src/include/switch_core_media.h
+++ b/src/include/switch_core_media.h
@@ -122,9 +122,11 @@ typedef struct switch_core_media_params_s {
 
 	switch_rtp_bug_flag_t manual_rtp_bugs;
 	switch_rtp_bug_flag_t manual_video_rtp_bugs;
+	switch_rtp_bug_flag_t manual_text_rtp_bugs;
 
 	char *rtcp_audio_interval_msec;
 	char *rtcp_video_interval_msec;
+	char *rtcp_text_interval_msec;
 
 
 	char *extrtpip;
@@ -331,10 +333,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_codec_control(switch_core_sess
 
 
 SWITCH_DECLARE(switch_timer_t *) switch_core_media_get_timer(switch_core_session_t *session, switch_media_type_t mtype);
-SWITCH_DECLARE(void) switch_core_media_start_video_function(switch_core_session_t *session, switch_video_function_t video_function, void *user_data);
-SWITCH_DECLARE(void) switch_core_media_end_video_function(switch_core_session_t *session);
+SWITCH_DECLARE(void) switch_core_media_start_engine_function(switch_core_session_t *session, switch_media_type_t type, switch_engine_function_t engine_function, void *user_data);
+SWITCH_DECLARE(void) switch_core_media_end_engine_function(switch_core_session_t *session, switch_media_type_t type);
 SWITCH_DECLARE(switch_status_t) switch_core_session_start_video_thread(switch_core_session_t *session);
-SWITCH_DECLARE(int) switch_core_media_check_video_function(switch_core_session_t *session);
+SWITCH_DECLARE(int) switch_core_media_check_engine_function(switch_core_session_t *session, switch_media_type_t type);
 SWITCH_DECLARE(void) switch_core_session_video_reinit(switch_core_session_t *session);
 SWITCH_DECLARE(switch_status_t) switch_core_media_read_lock_unlock(switch_core_session_t *session, switch_media_type_t type, switch_bool_t lock);
 
@@ -353,7 +355,23 @@ SWITCH_DECLARE(switch_bool_t) switch_core_media_check_dtls(switch_core_session_t
 SWITCH_DECLARE(switch_status_t) switch_core_media_set_outgoing_bitrate(switch_core_session_t *session, switch_media_type_t type, uint32_t bitrate);
 SWITCH_DECLARE(switch_status_t) switch_core_media_reset_jb(switch_core_session_t *session, switch_media_type_t type);
 SWITCH_DECLARE(switch_status_t) switch_core_session_wait_for_video_input_params(switch_core_session_t *session, uint32_t timeout_ms);
-																
+
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_set_text_read_callback(switch_core_session_t *session, 
+																		   switch_core_text_thread_callback_func_t func, void *user_data);
+SWITCH_DECLARE(switch_status_t) switch_core_session_text_read_callback(switch_core_session_t *session, switch_frame_t *frame);
+SWITCH_DECLARE(switch_status_t) switch_core_session_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags,
+																	int stream_id);
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags,
+																	 int stream_id);
+
+SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_create(switch_rtp_text_factory_t **tfP, switch_memory_pool_t *pool);
+SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_destroy(switch_rtp_text_factory_t **tfP);															
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t *session, const char *data);
+SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...);
+	
 SWITCH_END_EXTERN_C
 #endif
 /* For Emacs:
diff --git a/src/include/switch_ivr.h b/src/include/switch_ivr.h
index 06fa8c137a..712179d783 100644
--- a/src/include/switch_ivr.h
+++ b/src/include/switch_ivr.h
@@ -1020,6 +1020,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_stop_video_write_overlay_session(swit
 SWITCH_DECLARE(switch_status_t) switch_ivr_video_write_overlay_session(switch_core_session_t *session, const char *img_path, 
 																	   switch_img_position_t pos, uint8_t alpha);
 
+SWITCH_DECLARE(switch_status_t) switch_ivr_capture_text(switch_core_session_t *session, switch_bool_t on);
 
 /** @} */
 
diff --git a/src/include/switch_jitterbuffer.h b/src/include/switch_jitterbuffer.h
index de423fd7fd..e55bcbec28 100644
--- a/src/include/switch_jitterbuffer.h
+++ b/src/include/switch_jitterbuffer.h
@@ -39,7 +39,8 @@ typedef enum {
 
 typedef enum {
 	SJB_VIDEO = 0,
-	SJB_AUDIO
+	SJB_AUDIO,
+	SJB_TEXT
 } switch_jb_type_t;
 
 
diff --git a/src/include/switch_module_interfaces.h b/src/include/switch_module_interfaces.h
index 1802f872e3..0997bbeb05 100644
--- a/src/include/switch_module_interfaces.h
+++ b/src/include/switch_module_interfaces.h
@@ -119,6 +119,8 @@ typedef switch_status_t (*switch_io_state_change_t) (switch_core_session_t *);
 typedef switch_status_t (*switch_io_state_run_t) (switch_core_session_t *);
 typedef switch_status_t (*switch_io_read_video_frame_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int);
 typedef switch_status_t (*switch_io_write_video_frame_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int);
+typedef switch_status_t (*switch_io_read_text_frame_t) (switch_core_session_t *, switch_frame_t **, switch_io_flag_t, int);
+typedef switch_status_t (*switch_io_write_text_frame_t) (switch_core_session_t *, switch_frame_t *, switch_io_flag_t, int);
 typedef switch_jb_t *(*switch_io_get_jb_t) (switch_core_session_t *, switch_media_type_t);
 
 typedef enum {
@@ -132,6 +134,8 @@ typedef enum {
 	SWITCH_IO_STATE_CHANGE,
 	SWITCH_IO_READ_VIDEO_FRAME,
 	SWITCH_IO_WRITE_VIDEO_FRAME,
+	SWITCH_IO_READ_TEXT_FRAME,
+	SWITCH_IO_WRITE_TEXT_FRAME,
 	SWITCH_IO_GET_JB,
 } switch_io_routine_name_t;
 
@@ -157,6 +161,10 @@ struct switch_io_routines {
 	switch_io_read_video_frame_t read_video_frame;
 	/*! write a video frame to a session */
 	switch_io_write_video_frame_t write_video_frame;
+	/*! read a video frame from a session */
+	switch_io_read_text_frame_t read_text_frame;
+	/*! write a video frame to a session */
+	switch_io_write_text_frame_t write_text_frame;
 	/*! change a sessions channel run state */
 	switch_io_state_run_t state_run;
 	/*! get sessions jitterbuffer */
diff --git a/src/include/switch_types.h b/src/include/switch_types.h
index cc8539085e..7b81a07e8f 100644
--- a/src/include/switch_types.h
+++ b/src/include/switch_types.h
@@ -213,6 +213,8 @@ SWITCH_BEGIN_EXTERN_C
 #define SWITCH_REMOTE_VIDEO_PORT_VARIABLE "remote_video_port"
 #define SWITCH_LOCAL_VIDEO_IP_VARIABLE "local_video_ip"
 #define SWITCH_LOCAL_VIDEO_PORT_VARIABLE "local_video_port"
+#define SWITCH_LOCAL_TEXT_IP_VARIABLE "local_text_ip"
+#define SWITCH_LOCAL_TEXT_PORT_VARIABLE "local_text_port"
 #define SWITCH_HANGUP_AFTER_BRIDGE_VARIABLE "hangup_after_bridge"
 #define SWITCH_PARK_AFTER_BRIDGE_VARIABLE "park_after_bridge"
 #define SWITCH_PARK_AFTER_EARLY_BRIDGE_VARIABLE "park_after_early_bridge"
@@ -234,6 +236,7 @@ SWITCH_BEGIN_EXTERN_C
 #define SWITCH_RTCP_AUDIO_INTERVAL_MSEC "5000"
 #define SWITCH_RTCP_VIDEO_INTERVAL_MSEC "2000"
 
+#define TEXT_UNICODE_LINEFEED {0xe2, 0x80, 0xa8}
 #define MAX_FMTP_LEN 256
 
 /* Jitter */
@@ -496,7 +499,8 @@ typedef enum {
 	SWITCH_ABC_TYPE_READ_VIDEO_PING,
 	SWITCH_ABC_TYPE_WRITE_VIDEO_PING,
 	SWITCH_ABC_TYPE_STREAM_VIDEO_PING,
-	SWITCH_ABC_TYPE_VIDEO_PATCH
+	SWITCH_ABC_TYPE_VIDEO_PATCH,
+	SWITCH_ABC_TYPE_READ_TEXT
 } switch_abc_type_t;
 
 typedef struct {
@@ -769,6 +773,7 @@ typedef enum {
 	SWITCH_RTP_FLAG_TMMBR,
 	SWITCH_RTP_FLAG_GEN_TS_DELTA,
 	SWITCH_RTP_FLAG_DETECT_SSRC,
+	SWITCH_RTP_FLAG_TEXT,
 	SWITCH_RTP_FLAG_INVALID
 } switch_rtp_flag_t;
 
@@ -1369,6 +1374,8 @@ typedef enum {
 	CC_JITTERBUFFER,
 	CC_FS_RTP,
 	CC_QUEUEABLE_DTMF_DELAY,
+	CC_IO_OVERRIDE,
+	CC_RTP_RTT,
 	/* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */
 	CC_FLAG_MAX
 } switch_channel_cap_t;
@@ -1514,6 +1521,13 @@ typedef enum {
 	CF_3P_NOMEDIA_REQUESTED_BLEG,
 	CF_IMAGE_SDP,
 	CF_VIDEO_SDP_RECVD,
+	CF_TEXT_SDP_RECVD,
+	CF_TEXT,
+	CF_TEXT_POSSIBLE,
+	CF_TEXT_PASSIVE,
+	CF_TEXT_ECHO,
+	CF_TEXT_ACTIVE,
+	CF_TEXT_IDLE,
 	/* WARNING: DO NOT ADD ANY FLAGS BELOW THIS LINE */
 	/* IF YOU ADD NEW ONES CHECK IF THEY SHOULD PERSIST OR ZERO THEM IN switch_core_session.c switch_core_session_request_xml() */
 	CF_FLAG_MAX
@@ -1570,7 +1584,8 @@ typedef enum {
 	SFF_PICTURE_RESET = (1 << 14),
 	SFF_SAME_IMAGE = (1 << 15),
 	SFF_USE_VIDEO_TIMESTAMP = (1 << 16),
-	SFF_ENCODED = (1 << 17)
+	SFF_ENCODED = (1 << 17),
+	SFF_TEXT_LINE_BREAK = (1 << 18)
 } switch_frame_flag_enum_t;
 typedef uint32_t switch_frame_flag_t;
 
@@ -1714,9 +1729,10 @@ typedef enum {
 
 typedef enum {
 	SWITCH_MEDIA_TYPE_AUDIO,
-	SWITCH_MEDIA_TYPE_VIDEO
+	SWITCH_MEDIA_TYPE_VIDEO,
+	SWITCH_MEDIA_TYPE_TEXT
 } switch_media_type_t;
-#define SWITCH_MEDIA_TYPE_TOTAL 2
+#define SWITCH_MEDIA_TYPE_TOTAL 3
 
 
 /*!
@@ -1775,7 +1791,8 @@ typedef enum {
 	SMBF_VIDEO_PATCH = (1 << 21),
 	SMBF_SPY_VIDEO_STREAM = (1 << 22),
 	SMBF_SPY_VIDEO_STREAM_BLEG = (1 << 23),
-	SMBF_READ_VIDEO_PATCH = (1 << 24)
+	SMBF_READ_VIDEO_PATCH = (1 << 24),
+	SMBF_READ_TEXT_STREAM = (1 << 25)
 } switch_media_bug_flag_enum_t;
 typedef uint32_t switch_media_bug_flag_t;
 
@@ -2021,6 +2038,7 @@ typedef enum {
 	SWITCH_EVENT_CALL_SETUP_RESULT,
 	SWITCH_EVENT_CALL_DETAIL,
 	SWITCH_EVENT_DEVICE_STATE,
+	SWITCH_EVENT_REAL_TIME_TEXT,
 	SWITCH_EVENT_ALL
 } switch_event_types_t;
 
@@ -2236,13 +2254,15 @@ typedef struct switch_console_callback_match switch_console_callback_match_t;
 typedef void (*switch_media_bug_exec_cb_t)(switch_media_bug_t *bug, void *user_data);
 
 typedef switch_status_t (*switch_core_video_thread_callback_func_t) (switch_core_session_t *session, switch_frame_t *frame, void *user_data);
+typedef switch_status_t (*switch_core_text_thread_callback_func_t) (switch_core_session_t *session, switch_frame_t *frame, void *user_data);
 typedef void (*switch_cap_callback_t) (const char *var, const char *val, void *user_data);
 typedef switch_status_t (*switch_console_complete_callback_t) (const char *, const char *, switch_console_callback_match_t **matches);
 typedef switch_bool_t (*switch_media_bug_callback_t) (switch_media_bug_t *, void *, switch_abc_type_t);
 typedef switch_bool_t (*switch_tone_detect_callback_t) (switch_core_session_t *, const char *, const char *);
 typedef struct switch_xml_binding switch_xml_binding_t;
 
-typedef void (*switch_video_function_t) (switch_core_session_t *session, void *user_data);
+typedef void (*switch_engine_function_t) (switch_core_session_t *session, void *user_data);
+
 
 typedef switch_status_t (*switch_core_codec_encode_func_t) (switch_codec_t *codec,
 															switch_codec_t *other_codec,
@@ -2609,6 +2629,10 @@ typedef enum {
 	SCFC_PAUSE_READ
 } switch_file_command_t;
 
+
+struct switch_rtp_text_factory_s;
+typedef struct switch_rtp_text_factory_s  switch_rtp_text_factory_t;
+
 SWITCH_END_EXTERN_C
 #endif
 /* For Emacs:
diff --git a/src/include/switch_utils.h b/src/include/switch_utils.h
index 691395f362..1c423b202b 100644
--- a/src/include/switch_utils.h
+++ b/src/include/switch_utils.h
@@ -373,6 +373,33 @@ SWITCH_DECLARE(switch_status_t) switch_b64_encode(unsigned char *in, switch_size
 SWITCH_DECLARE(switch_size_t) switch_b64_decode(char *in, char *out, switch_size_t olen);
 SWITCH_DECLARE(char *) switch_amp_encode(char *s, char *buf, switch_size_t len);
 
+
+
+static inline char *switch_print_bits(const unsigned char *byte, char *buf, switch_size_t buflen)
+{
+
+	int i, j = 0, k = 0, l = 0;
+
+	while(k < buflen) {
+		l = 0;
+		for (i = 7; i >= 0; i--) {
+			buf[j++] = (*byte & (1 << i)) ? '1' : '0';
+			if (++l % 4 == 0) {
+				buf[j++] = ' ';
+			}
+		}
+		k++;
+		byte++;
+	}
+	
+	if (buf[j-1] == ' ') j--;
+	buf[j++] = '\0';
+	return buf;
+}
+
+
+
+
 static inline switch_bool_t switch_is_digit_string(const char *s)
 {
 
diff --git a/src/mod/applications/mod_av/avformat.c b/src/mod/applications/mod_av/avformat.c
index 883a5be51f..04d35096e1 100644
--- a/src/mod/applications/mod_av/avformat.c
+++ b/src/mod/applications/mod_av/avformat.c
@@ -1140,7 +1140,7 @@ SWITCH_STANDARD_APP(record_av_function)
 		switch_core_timer_destroy(&timer);
 	}
 
-	switch_core_media_end_video_function(session);
+	switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO);
 	switch_core_session_set_read_codec(session, NULL);
 	switch_core_codec_destroy(&codec);
 
diff --git a/src/mod/applications/mod_commands/mod_commands.c b/src/mod/applications/mod_commands/mod_commands.c
index 39bffd58ab..f44b4ab55b 100644
--- a/src/mod/applications/mod_commands/mod_commands.c
+++ b/src/mod/applications/mod_commands/mod_commands.c
@@ -3066,6 +3066,61 @@ SWITCH_STANDARD_API(uuid_chat)
 	return SWITCH_STATUS_SUCCESS;
 }
 
+#define UUID_CAPTURE_TEXT_SYNTAX "<uuid> <on|off>"
+SWITCH_STANDARD_API(uuid_capture_text)
+{
+	switch_core_session_t *tsession = NULL;
+	char *uuid = NULL, *onoff = NULL;
+
+	if (!zstr(cmd) && (uuid = strdup(cmd))) {
+		if ((onoff = strchr(uuid, ' '))) {
+			*onoff++ = '\0';
+		}
+	}
+
+	if (zstr(uuid) || zstr(onoff)) {
+		stream->write_function(stream, "-USAGE: %s\n", UUID_CAPTURE_TEXT_SYNTAX);
+	} else {
+		if ((tsession = switch_core_session_locate(uuid))) {
+			switch_ivr_capture_text(tsession, switch_true(onoff));
+		} else {
+			stream->write_function(stream, "-ERR No such channel %s!\n", uuid);
+		}
+	}
+
+	switch_safe_free(uuid);
+	return SWITCH_STATUS_SUCCESS;
+}
+
+
+#define UUID_SEND_TEXT_SYNTAX "<uuid> <text>"
+SWITCH_STANDARD_API(uuid_send_text)
+{
+	switch_core_session_t *tsession = NULL;
+	char *uuid = NULL, *text = NULL;
+
+	if (!zstr(cmd) && (uuid = strdup(cmd))) {
+		if ((text = strchr(uuid, ' '))) {
+			*text++ = '\0';
+		}
+	}
+
+	if (zstr(uuid) || zstr(text)) {
+		stream->write_function(stream, "-USAGE: %s\n", UUID_SEND_TEXT_SYNTAX);
+	} else {
+		if ((tsession = switch_core_session_locate(uuid))) {
+			switch_core_session_print(tsession, text);
+			switch_core_session_print(tsession, "\r\n");
+			switch_core_session_rwunlock(tsession);
+		} else {
+			stream->write_function(stream, "-ERR No such channel %s!\n", uuid);
+		}
+	}
+
+	switch_safe_free(uuid);
+	return SWITCH_STATUS_SUCCESS;
+}
+
 #define UUID_DROP_DTMF_SYNTAX "<uuid> [on | off ] [ mask_digits <digits> | mask_file <file>]"
 SWITCH_STANDARD_API(uuid_drop_dtmf)
 {
@@ -7197,6 +7252,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
 	SWITCH_ADD_API(commands_api_interface, "uuid_broadcast", "Execute dialplan application", uuid_broadcast_function, BROADCAST_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "uuid_buglist", "List media bugs on a session", uuid_buglist_function, BUGLIST_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "uuid_chat", "Send a chat message", uuid_chat, UUID_CHAT_SYNTAX);
+	SWITCH_ADD_API(commands_api_interface, "uuid_send_text", "Send text in real-time", uuid_send_text, UUID_SEND_TEXT_SYNTAX);
+	SWITCH_ADD_API(commands_api_interface, "uuid_capture_text", "start/stop capture_text", uuid_capture_text, UUID_CAPTURE_TEXT_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "uuid_codec_debug", "Send codec a debug message", uuid_codec_debug_function, CODEC_DEBUG_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "uuid_codec_param", "Send codec a param", uuid_codec_param_function, CODEC_PARAM_SYNTAX);
 	SWITCH_ADD_API(commands_api_interface, "uuid_debug_media", "Debug media", uuid_debug_media_function, DEBUG_MEDIA_SYNTAX);
@@ -7376,6 +7433,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_commands_load)
 	switch_console_set_complete("add uuid_broadcast ::console::list_uuid");
 	switch_console_set_complete("add uuid_buglist ::console::list_uuid");
 	switch_console_set_complete("add uuid_chat ::console::list_uuid");
+	switch_console_set_complete("add uuid_send_text ::console::list_uuid");
+	switch_console_set_complete("add uuid_capture_text ::console::list_uuid");
 	switch_console_set_complete("add uuid_codec_debug ::console::list_uuid audio");
 	switch_console_set_complete("add uuid_codec_debug ::console::list_uuid video");
 	switch_console_set_complete("add uuid_codec_param ::console::list_uuid audio read");
diff --git a/src/mod/applications/mod_conference/conference_member.c b/src/mod/applications/mod_conference/conference_member.c
index b90ab477c5..064d12edac 100644
--- a/src/mod/applications/mod_conference/conference_member.c
+++ b/src/mod/applications/mod_conference/conference_member.c
@@ -1148,6 +1148,13 @@ switch_status_t conference_member_del(conference_obj_t *conference, conference_m
 	lock_member(member);
 	conference_utils_member_clear_flag(member, MFLAG_INTREE);
 
+	
+	switch_safe_free(member->text_framedata);
+	member->text_framesize = 0;
+	if (member->text_buffer) {
+		switch_buffer_destroy(&member->text_buffer);
+	}
+
 	if (member->rec) {
 		conference->recording_members--;
 	}
diff --git a/src/mod/applications/mod_conference/mod_conference.c b/src/mod/applications/mod_conference/mod_conference.c
index dbcb80299a..75b2e3b27e 100644
--- a/src/mod/applications/mod_conference/mod_conference.c
+++ b/src/mod/applications/mod_conference/mod_conference.c
@@ -249,6 +249,42 @@ void *SWITCH_THREAD_FUNC conference_thread_run(switch_thread_t *thread, void *ob
 
 		floor_holder = conference->floor_holder;
 
+		for (imember = conference->members; imember; imember = imember->next) {
+			if (!zstr(imember->text_framedata)) {
+				switch_frame_t frame = { 0 };
+				char *framedata;
+				uint32_t framedatalen;
+				const char *caller_id_name = switch_channel_get_variable(imember->channel, "caller_id_name");
+				unsigned char CR[3] = TEXT_UNICODE_LINEFEED;
+
+				
+				switch_mutex_lock(imember->text_mutex);
+
+				framedatalen = strlen(imember->text_framedata) + strlen(caller_id_name) + 6;
+
+				switch_zmalloc(framedata, framedatalen);
+				
+				switch_snprintf(framedata, framedatalen, "%s::\n%s", caller_id_name, imember->text_framedata);
+				memcpy(framedata + strlen(framedata), CR, sizeof(CR));
+				
+
+				frame.data = framedata;
+				frame.datalen = framedatalen;
+
+				for (omember = conference->members; omember; omember = omember->next) {
+					if (omember != imember) {
+						switch_core_session_write_text_frame(omember->session, &frame, 0, 0);
+					}
+				}
+				
+				free(framedata);
+				
+				imember->text_framedata[0] = '\0';
+
+				switch_mutex_unlock(imember->text_mutex);
+			}
+		}
+
 		/* Read one frame of audio from each member channel and save it for redistribution */
 		for (imember = conference->members; imember; imember = imember->next) {
 			uint32_t buf_read = 0;
@@ -1610,6 +1646,65 @@ SWITCH_STANDARD_APP(conference_auto_function)
 }
 
 
+switch_status_t conference_text_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data)
+{
+	conference_member_t *member = (conference_member_t *)user_data;
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+	switch_size_t inuse = 0;
+
+	if (!member) return SWITCH_STATUS_FALSE;
+
+
+	switch_mutex_lock(member->text_mutex);
+	if (!member->text_buffer) {
+		switch_buffer_create_dynamic(&member->text_buffer, 512, 1024, 0);
+		switch_zmalloc(member->text_framedata, 1024);
+		member->text_framesize = 1024;
+	}
+
+	if (frame->data && frame->datalen && !(frame->flags & SFF_CNG)) {
+		switch_buffer_write(member->text_buffer, frame->data, frame->datalen);
+	}
+
+	inuse = switch_buffer_inuse(member->text_buffer);
+
+	if (zstr(member->text_framedata) && inuse && (switch_channel_test_flag(channel, CF_TEXT_IDLE) || switch_test_flag(frame, SFF_TEXT_LINE_BREAK))) {
+		int bytes = 0, ok = 0;
+		char *p;
+
+		if (inuse + 1 > member->text_framesize) {
+			void *tmp = malloc(inuse + 1024);
+			memcpy(tmp, member->text_framedata, member->text_framesize);
+
+			switch_assert(tmp);
+			
+			member->text_framesize = inuse + 1024;
+			
+			free(member->text_framedata);
+			member->text_framedata = tmp;
+
+		}
+
+		bytes = switch_buffer_read(member->text_buffer, member->text_framedata, inuse);
+		*(member->text_framedata + bytes) = '\0'; 
+
+		for(p = member->text_framedata; p && *p; p++) {
+			if (*p > 32 && *p < 127) {
+				ok++;
+			}
+		}
+
+		if (!ok) {
+			member->text_framedata[0] = '\0';
+		}
+
+	}
+	
+	switch_mutex_unlock(member->text_mutex);
+
+	return SWITCH_STATUS_SUCCESS;
+}
+
 /* Application interface function that is called from the dialplan to join the channel to a conference */
 SWITCH_STANDARD_APP(conference_function)
 {
@@ -2123,6 +2218,7 @@ SWITCH_STANDARD_APP(conference_function)
 	switch_mutex_init(&member.fnode_mutex, SWITCH_MUTEX_NESTED, member.pool);
 	switch_mutex_init(&member.audio_in_mutex, SWITCH_MUTEX_NESTED, member.pool);
 	switch_mutex_init(&member.audio_out_mutex, SWITCH_MUTEX_NESTED, member.pool);
+	switch_mutex_init(&member.text_mutex, SWITCH_MUTEX_NESTED, member.pool);
 	switch_thread_rwlock_create(&member.rwlock, member.pool);
 
 	if (conference_member_setup_media(&member, conference)) {
@@ -2197,6 +2293,7 @@ SWITCH_STANDARD_APP(conference_function)
 
 	/* Chime in the core video thread */
 	switch_core_session_set_video_read_callback(session, conference_video_thread_callback, (void *)&member);
+	switch_core_session_set_text_read_callback(session, conference_text_thread_callback, (void *)&member);
 
 	if (switch_channel_test_flag(channel, CF_VIDEO_ONLY)) {
 		while(conference_utils_member_test_flag((&member), MFLAG_RUNNING) && switch_channel_ready(channel)) {
@@ -2211,6 +2308,7 @@ SWITCH_STANDARD_APP(conference_function)
 	}
 
 	switch_core_session_set_video_read_callback(session, NULL, NULL);
+	switch_core_session_set_text_read_callback(session, NULL, NULL);
 
 	switch_channel_set_private(channel, "_conference_autocall_list_", NULL);
 
diff --git a/src/mod/applications/mod_conference/mod_conference.h b/src/mod/applications/mod_conference/mod_conference.h
index 02503f6110..a4aca869fc 100644
--- a/src/mod/applications/mod_conference/mod_conference.h
+++ b/src/mod/applications/mod_conference/mod_conference.h
@@ -790,6 +790,13 @@ struct conference_member {
 	int reset_media;
 	int flip;
 	int flip_count;
+
+	switch_mutex_t *text_mutex;
+	switch_buffer_t *text_buffer;
+	char *text_framedata;
+	uint32_t text_framesize;
+
+
 };
 
 typedef enum {
@@ -971,6 +978,7 @@ void conference_video_fnode_check(conference_file_node_t *fnode, int canvas_id);
 switch_status_t conference_video_set_canvas_bgimg(mcu_canvas_t *canvas, const char *img_path);
 switch_status_t conference_al_parse_position(al_handle_t *al, const char *data);
 switch_status_t conference_video_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data);
+switch_status_t conference_text_thread_callback(switch_core_session_t *session, switch_frame_t *frame, void *user_data);
 void *SWITCH_THREAD_FUNC conference_video_muxing_write_thread_run(switch_thread_t *thread, void *obj);
 void conference_member_check_agc_levels(conference_member_t *member);
 void conference_member_clear_avg(conference_member_t *member);
diff --git a/src/mod/applications/mod_dptools/mod_dptools.c b/src/mod/applications/mod_dptools/mod_dptools.c
index 0545ac07c4..d3f26e33d2 100644
--- a/src/mod/applications/mod_dptools/mod_dptools.c
+++ b/src/mod/applications/mod_dptools/mod_dptools.c
@@ -1012,6 +1012,10 @@ SWITCH_STANDARD_APP(set_mute_function)
 
 }
 
+SWITCH_STANDARD_APP(capture_text_function)
+{
+	switch_ivr_capture_text(session, switch_true((char *)data));
+}
 
 SWITCH_STANDARD_APP(ring_ready_function)
 {
@@ -6127,6 +6131,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_dptools_load)
 	SWITCH_ADD_CHAT(chat_interface, "event", event_chat_send);
 	SWITCH_ADD_CHAT(chat_interface, "api", api_chat_send);
 
+
 	SWITCH_ADD_API(api_interface, "strepoch", "Convert a date string into epoch time", strepoch_api_function, "<string>");
 	SWITCH_ADD_API(api_interface, "page", "Send a file as a page", page_api_function, "(var1=val1,var2=val2)<var1=val1,var2=val2><chan1>[:_:<chanN>]");
 	SWITCH_ADD_API(api_interface, "strmicroepoch", "Convert a date string into micoepoch time", strmicroepoch_api_function, "<string>");
@@ -6216,6 +6221,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_dptools_load)
 	SWITCH_ADD_APP(app_interface, "multiunset", "Unset many channel variables", SET_LONG_DESC, multiunset_function, "[^^<delim>]<varname> <var2> <var3>",
 				   SAF_SUPPORT_NOMEDIA | SAF_ROUTING_EXEC | SAF_ZOMBIE_EXEC);
 
+	SWITCH_ADD_APP(app_interface, "capture_text", "capture text", "capture text", capture_text_function, "", SAF_NONE);
 	SWITCH_ADD_APP(app_interface, "ring_ready", "Indicate Ring_Ready", "Indicate Ring_Ready on a channel.", ring_ready_function, "", SAF_SUPPORT_NOMEDIA);
 	SWITCH_ADD_APP(app_interface, "remove_bugs", "Remove media bugs", "Remove all media bugs from a channel.", remove_bugs_function, "[<function>]", SAF_NONE);
 	SWITCH_ADD_APP(app_interface, "break", "Break", "Set the break flag.", break_function, "", SAF_SUPPORT_NOMEDIA);
diff --git a/src/mod/applications/mod_fsv/mod_fsv.c b/src/mod/applications/mod_fsv/mod_fsv.c
index e2c6f5af4d..e99022cee8 100644
--- a/src/mod/applications/mod_fsv/mod_fsv.c
+++ b/src/mod/applications/mod_fsv/mod_fsv.c
@@ -183,7 +183,7 @@ SWITCH_STANDARD_APP(record_fsv_function)
 		switch_mutex_init(&mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
 		eh.mutex = mutex;
 		eh.fd = fd;
-		switch_core_media_start_video_function(session, record_video_thread, &eh);
+		switch_core_media_start_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO, record_video_thread, &eh);
 	}
 
 
@@ -257,7 +257,7 @@ SWITCH_STANDARD_APP(record_fsv_function)
 		close(fd);
 	}
 
-	switch_core_media_end_video_function(session);
+	switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO);
 	switch_core_session_set_read_codec(session, NULL);
 	switch_core_codec_destroy(&codec);
 
@@ -751,7 +751,7 @@ SWITCH_STANDARD_APP(decode_video_function)
 
 	switch_channel_set_flag(channel, CF_VIDEO_DECODED_READ);
 
-	switch_core_media_start_video_function(session, decode_video_thread, &max_pictures);
+	switch_core_media_start_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO, decode_video_thread, &max_pictures);
 
 	switch_ivr_play_file(session, NULL, moh, NULL);
 
@@ -762,7 +762,7 @@ SWITCH_STANDARD_APP(decode_video_function)
 	switch_channel_set_variable(channel, SWITCH_CURRENT_APPLICATION_RESPONSE_VARIABLE, "OK");
 
 
-	switch_core_media_end_video_function(session);
+	switch_core_media_end_engine_function(session, SWITCH_MEDIA_TYPE_VIDEO);
 	switch_core_session_video_reset(session);
 }
 
diff --git a/src/mod/endpoints/mod_rtc/mod_rtc.c b/src/mod/endpoints/mod_rtc/mod_rtc.c
index 4f964827c8..2ef2bac87f 100644
--- a/src/mod/endpoints/mod_rtc/mod_rtc.c
+++ b/src/mod/endpoints/mod_rtc/mod_rtc.c
@@ -287,6 +287,8 @@ switch_io_routines_t rtc_io_routines = {
 	/*.state_change */ NULL,
 	/*.read_video_frame */ rtc_read_video_frame,
 	/*.write_video_frame */ rtc_write_video_frame,
+	/*.read_text_frame */ NULL,
+	/*.write_text_frame */ NULL,
 	/*.state_run*/ NULL,
 	/*.get_jb*/ rtc_get_jb
 };
@@ -330,6 +332,7 @@ void rtc_attach_private(switch_core_session_t *session, private_object_t *tech_p
 	switch_core_media_check_dtmf_type(session);
 	switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER);
 	switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP);
+	switch_channel_set_cap(tech_pvt->channel, CC_IO_OVERRIDE);
 	switch_media_handle_create(&tech_pvt->media_handle, session, &tech_pvt->mparams);
 	switch_core_session_set_private(session, tech_pvt);
 
diff --git a/src/mod/endpoints/mod_sofia/mod_sofia.c b/src/mod/endpoints/mod_sofia/mod_sofia.c
index 36ef8b06b9..017150d6ed 100644
--- a/src/mod/endpoints/mod_sofia/mod_sofia.c
+++ b/src/mod/endpoints/mod_sofia/mod_sofia.c
@@ -62,6 +62,8 @@ static switch_status_t sofia_read_frame(switch_core_session_t *session, switch_f
 static switch_status_t sofia_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id);
 static switch_status_t sofia_read_video_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id);
 static switch_status_t sofia_write_video_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id);
+static switch_status_t sofia_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id);
+static switch_status_t sofia_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id);
 static switch_status_t sofia_kill_channel(switch_core_session_t *session, int sig);
 
 /* BODY OF THE MODULE */
@@ -918,6 +920,16 @@ static switch_status_t sofia_answer_channel(switch_core_session_t *session)
 	return SWITCH_STATUS_SUCCESS;
 }
 
+static switch_status_t sofia_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id)
+{
+	return switch_core_media_read_frame(session, frame, flags, stream_id, SWITCH_MEDIA_TYPE_TEXT);
+}
+
+static switch_status_t sofia_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id)
+{
+	return switch_core_media_write_frame(session, frame, flags, stream_id, SWITCH_MEDIA_TYPE_TEXT);
+}
+
 static switch_status_t sofia_read_video_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id)
 {
 	private_object_t *tech_pvt = (private_object_t *) switch_core_session_get_private(session);
@@ -4279,6 +4291,8 @@ switch_io_routines_t sofia_io_routines = {
 	/*.state_change */ NULL,
 	/*.read_video_frame */ sofia_read_video_frame,
 	/*.write_video_frame */ sofia_write_video_frame,
+	/*.read_text_frame */ sofia_read_text_frame,
+	/*.write_text_frame */ sofia_write_text_frame,
 	/*.state_run*/ NULL,
 	/*.get_jb*/ sofia_get_jb
 };
diff --git a/src/mod/endpoints/mod_sofia/rtp.c b/src/mod/endpoints/mod_sofia/rtp.c
index ce42dc1875..eafde46716 100644
--- a/src/mod/endpoints/mod_sofia/rtp.c
+++ b/src/mod/endpoints/mod_sofia/rtp.c
@@ -122,6 +122,8 @@ switch_io_routines_t crtp_io_routines = {
 	/*state_change*/ NULL,
 	/*read_video_frame*/ NULL,
 	/*write_video_frame*/ NULL,
+	/*read_text_frame*/ NULL,
+	/*write_text_frame*/ NULL,
 	/*state_run*/ NULL
 
 
diff --git a/src/mod/endpoints/mod_sofia/sofia_glue.c b/src/mod/endpoints/mod_sofia/sofia_glue.c
index 10fe226649..9c493bf9a9 100644
--- a/src/mod/endpoints/mod_sofia/sofia_glue.c
+++ b/src/mod/endpoints/mod_sofia/sofia_glue.c
@@ -159,6 +159,7 @@ void sofia_glue_attach_private(switch_core_session_t *session, sofia_profile_t *
 	switch_channel_set_cap(tech_pvt->channel, CC_PROXY_MEDIA);
 	switch_channel_set_cap(tech_pvt->channel, CC_JITTERBUFFER);
 	switch_channel_set_cap(tech_pvt->channel, CC_FS_RTP);
+	switch_channel_set_cap(tech_pvt->channel, CC_RTP_RTT);
 	switch_channel_set_cap(tech_pvt->channel, CC_QUEUEABLE_DTMF_DELAY);
 
 
diff --git a/src/mod/endpoints/mod_verto/mcast/mcast.c b/src/mod/endpoints/mod_verto/mcast/mcast.c
index f15ff6efc5..984d1d4f22 100644
--- a/src/mod/endpoints/mod_verto/mcast/mcast.c
+++ b/src/mod/endpoints/mod_verto/mcast/mcast.c
@@ -53,7 +53,7 @@
 #include <poll.h>
 #define closesocket(x) close(x)
 #endif
-#include <switch_utils.h>
+#include <switch.h>
 #include "mcast.h"
 
 
diff --git a/src/mod/endpoints/mod_verto/mod_verto.c b/src/mod/endpoints/mod_verto/mod_verto.c
index b839660082..15a5d6825d 100644
--- a/src/mod/endpoints/mod_verto/mod_verto.c
+++ b/src/mod/endpoints/mod_verto/mod_verto.c
@@ -130,6 +130,10 @@ static switch_bool_t check_name(const char *name)
 }
 
 
+static switch_status_t verto_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id);
+static switch_status_t verto_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id);
+static void set_text_funcs(switch_core_session_t *session);
+
 static verto_profile_t *find_profile(const char *name);
 static jsock_t *get_jsock(const char *uuid);
 
@@ -2116,6 +2120,11 @@ switch_endpoint_interface_t *verto_endpoint_interface = NULL;
 
 static switch_status_t verto_on_destroy(switch_core_session_t *session)
 {
+	verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY);
+
+	switch_buffer_destroy(&tech_pvt->text_read_buffer);
+	switch_buffer_destroy(&tech_pvt->text_write_buffer);
+
 	UNPROTECT_INTERFACE(verto_endpoint_interface);
 	return SWITCH_STATUS_SUCCESS;
 }
@@ -2576,6 +2585,7 @@ static int verto_recover_callback(switch_core_session_t *session)
 	}
 
 	tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt));
+	tech_pvt->pool = switch_core_session_get_pool(session);
 	tech_pvt->session = session;
 	tech_pvt->channel = channel;
 	tech_pvt->jsock_uuid = (char *) jsock_uuid_str;
@@ -3261,7 +3271,7 @@ static void parse_user_vars(cJSON *obj, switch_core_session_t *session)
 
 static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response)
 {
-	cJSON *msg = NULL, *dialog = NULL;
+	cJSON *msg = NULL, *dialog = NULL, *txt = NULL;
 	const char *call_id = NULL, *dtmf = NULL;
 	switch_bool_t r = SWITCH_TRUE;
 	char *proto = VERTO_CHAT_PROTO;
@@ -3298,6 +3308,43 @@ static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t
 		}
 	}
 	
+	if ((txt = cJSON_GetObjectItem(params, "txt"))) {
+		switch_core_session_t *session;
+
+		if ((session = switch_core_session_locate(call_id))) {
+			verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY);
+			char charbuf[2] = "";
+			char *chardata = NULL;
+			cJSON *data;
+			
+			if ((data = cJSON_GetObjectItem(txt, "code"))) {
+				charbuf[0] = data->valueint;
+				chardata = charbuf;
+			} else if ((data = cJSON_GetObjectItem(txt, "chars"))) {
+				if (data->valuestring) {
+					chardata = data->valuestring;
+				} else if (data->valueint) {
+					charbuf[0] = data->valueint;
+					chardata = charbuf;
+				}
+			}
+
+
+			if (chardata) {
+				switch_mutex_lock(tech_pvt->text_read_mutex);
+				switch_buffer_write(tech_pvt->text_read_buffer, chardata, strlen(chardata));
+				switch_mutex_unlock(tech_pvt->text_read_mutex);
+			
+				if ((switch_mutex_trylock(tech_pvt->text_cond_mutex) == SWITCH_STATUS_SUCCESS)) {
+					switch_thread_cond_signal(tech_pvt->text_cond);
+					switch_mutex_unlock(tech_pvt->text_cond_mutex);
+				}
+			}
+			switch_core_session_rwunlock(session);
+		}
+		
+	}
+
 	if ((msg = cJSON_GetObjectItem(params, "msg"))) {
 		switch_event_t *event;
 		char *to = (char *) cJSON_GetObjectCstr(msg, "to");
@@ -3380,6 +3427,8 @@ static switch_bool_t verto__info_func(const char *method, cJSON *params, jsock_t
 	return r;
 }
 
+
+
 static switch_bool_t verto__invite_func(const char *method, cJSON *params, jsock_t *jsock, cJSON **response)
 {
 	cJSON *obj = cJSON_CreateObject(), *screenShare = NULL, *dedEnc = NULL, *mirrorInput, *bandwidth = NULL, *canvas = NULL;
@@ -3431,12 +3480,13 @@ static switch_bool_t verto__invite_func(const char *method, cJSON *params, jsock
 
 	tech_pvt = switch_core_session_alloc(session, sizeof(*tech_pvt));
 	tech_pvt->session = session;
+	tech_pvt->pool = switch_core_session_get_pool(session);
 	tech_pvt->channel = channel;
 	tech_pvt->jsock_uuid = switch_core_session_strdup(session, jsock->uuid_str);
 	tech_pvt->r_sdp = switch_core_session_strdup(session, sdp);
 	switch_core_media_set_sdp_codec_string(session, sdp, SDP_TYPE_REQUEST);
 	switch_core_session_set_private_class(session, tech_pvt, SWITCH_PVT_SECONDARY);
-
+	set_text_funcs(session);
 
 	tech_pvt->call_id = switch_core_session_strdup(session, call_id);
 	if ((tech_pvt->smh = switch_core_session_get_media_handle(session))) {
@@ -5042,6 +5092,115 @@ switch_io_routines_t verto_io_routines = {
 	/*.outgoing_channel */ verto_outgoing_channel
 };
 
+
+switch_io_routines_t verto_io_override = {
+	/*.outgoing_channel */ NULL,
+	/*.read_frame */ NULL,
+	/*.write_frame */ NULL,
+	/*.kill_channel */ NULL,
+	/*.send_dtmf */ NULL,
+	/*.receive_message */ NULL,
+	/*.receive_event */ NULL,
+	/*.state_change */ NULL,
+	/*.read_video_frame */ NULL,
+	/*.write_video_frame */ NULL,
+	/*.read_text_frame */ verto_read_text_frame,
+	/*.write_text_frame */ verto_write_text_frame,
+	/*.state_run*/ NULL,
+	/*.get_jb*/ NULL
+};
+
+
+static switch_status_t verto_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags, int stream_id)
+{
+	verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY);
+	switch_status_t status;
+	
+	switch_mutex_lock(tech_pvt->text_cond_mutex);
+
+	status = switch_thread_cond_timedwait(tech_pvt->text_cond, tech_pvt->text_cond_mutex, 100000);
+	switch_mutex_unlock(tech_pvt->text_cond_mutex);
+
+	*frame = &tech_pvt->text_read_frame;
+	(*frame)->flags = 0;
+
+	switch_mutex_lock(tech_pvt->text_read_mutex);
+	if (switch_buffer_inuse(tech_pvt->text_read_buffer)) {
+		status = SWITCH_STATUS_SUCCESS;
+		tech_pvt->text_read_frame.datalen = switch_buffer_read(tech_pvt->text_read_buffer, tech_pvt->text_read_frame.data, 100);
+	} else {
+		(*frame)->flags |= SFF_CNG;
+		tech_pvt->text_read_frame.datalen = 2;
+		status = SWITCH_STATUS_BREAK;
+	}
+	switch_mutex_unlock(tech_pvt->text_read_mutex);
+
+
+
+	return status;
+}
+
+static switch_status_t verto_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id)
+{
+	verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY);
+
+	switch_mutex_lock(tech_pvt->text_write_mutex);
+
+	
+	if (frame) {
+		switch_buffer_write(tech_pvt->text_write_buffer, frame->data, frame->datalen);
+	}
+
+	if (switch_buffer_inuse(tech_pvt->text_write_buffer)) {
+		uint32_t datalen;
+		switch_byte_t data[SWITCH_RTP_MAX_BUF_LEN] = "";
+
+		if ((datalen = switch_buffer_read(tech_pvt->text_write_buffer, data, 100))) {
+			cJSON *obj = NULL, *txt = NULL, *params = NULL;
+			jsock_t *jsock;
+
+			obj = jrpc_new_req("verto.info", tech_pvt->call_id, &params);
+			txt = json_add_child_obj(params, "txt", NULL);
+			cJSON_AddItemToObject(txt, "chars", cJSON_CreateString((char *)data));
+
+			if ((jsock = get_jsock(tech_pvt->jsock_uuid))) {
+				jsock_queue_event(jsock, &obj, SWITCH_TRUE);
+				switch_thread_rwlock_unlock(jsock->rwlock);
+			} else {
+				cJSON_Delete(obj);
+			}
+		}
+	}
+	
+
+	switch_mutex_unlock(tech_pvt->text_write_mutex);
+
+	return SWITCH_STATUS_SUCCESS;
+}
+
+
+
+static void set_text_funcs(switch_core_session_t *session)
+{
+	if ((switch_core_session_override_io_routines(session, &verto_io_override) == SWITCH_STATUS_SUCCESS)) {
+		verto_pvt_t *tech_pvt = switch_core_session_get_private_class(session, SWITCH_PVT_SECONDARY);
+
+		tech_pvt->text_read_frame.data = tech_pvt->text_read_frame_data;
+
+		switch_mutex_init(&tech_pvt->text_read_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool);
+		switch_mutex_init(&tech_pvt->text_write_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool);
+		switch_mutex_init(&tech_pvt->text_cond_mutex, SWITCH_MUTEX_NESTED, tech_pvt->pool);
+		switch_thread_cond_create(&tech_pvt->text_cond, tech_pvt->pool);
+
+		switch_buffer_create_dynamic(&tech_pvt->text_read_buffer, 512, 1024, 0);
+		switch_buffer_create_dynamic(&tech_pvt->text_write_buffer, 512, 1024, 0);
+
+		switch_channel_set_flag(switch_core_session_get_channel(session), CF_TEXT);
+		switch_core_session_start_text_thread(session);
+	}
+}
+
+
 static char *verto_get_dial_string(const char *uid, switch_stream_handle_t *rstream)
 {
 	jsock_t *jsock;
@@ -5187,11 +5346,14 @@ static switch_call_cause_t verto_outgoing_channel(switch_core_session_t *session
 		char name[512];
 		
 		tech_pvt = switch_core_session_alloc(*new_session, sizeof(*tech_pvt));
+		tech_pvt->pool = switch_core_session_get_pool(*new_session);
 		tech_pvt->session = *new_session;
 		tech_pvt->channel = channel;
 		tech_pvt->jsock_uuid = switch_core_session_strdup(*new_session, jsock_uuid_str);
+
 		switch_core_session_set_private_class(*new_session, tech_pvt, SWITCH_PVT_SECONDARY);
-		
+		set_text_funcs(*new_session);
+
 		if (session) {
 			switch_channel_t *ochannel = switch_core_session_get_channel(session);
 			
diff --git a/src/mod/endpoints/mod_verto/mod_verto.h b/src/mod/endpoints/mod_verto/mod_verto.h
index c39c74d681..404529e0bf 100644
--- a/src/mod/endpoints/mod_verto/mod_verto.h
+++ b/src/mod/endpoints/mod_verto/mod_verto.h
@@ -173,6 +173,7 @@ typedef enum {
 } tflag_t;
 
 typedef struct verto_pvt_s {
+	switch_memory_pool_t *pool;
 	char *jsock_uuid;
 	char *call_id;
 	char *r_sdp;
@@ -184,6 +185,17 @@ typedef struct verto_pvt_s {
 	switch_call_cause_t remote_hangup_cause;
 	time_t detach_time;
 	struct verto_pvt_s *next;
+	switch_byte_t text_read_frame_data[SWITCH_RTP_MAX_BUF_LEN];
+	switch_frame_t text_read_frame;
+
+	switch_thread_cond_t *text_cond;
+	switch_mutex_t *text_cond_mutex; 
+	switch_mutex_t *text_read_mutex; 
+	switch_mutex_t *text_write_mutex; 
+
+	switch_buffer_t *text_read_buffer;
+	switch_buffer_t *text_write_buffer;
+
 } verto_pvt_t;
 
 typedef struct verto_vhost_s {
diff --git a/src/switch_buffer.c b/src/switch_buffer.c
index b2fd1482fe..01745cc6b1 100644
--- a/src/switch_buffer.c
+++ b/src/switch_buffer.c
@@ -53,6 +53,12 @@ struct switch_buffer {
 	int32_t loops;
 };
 
+
+SWITCH_DECLARE(void *) switch_buffer_get_head_pointer(switch_buffer_t *buffer)
+{
+	return buffer->head;
+}
+
 SWITCH_DECLARE(switch_status_t) switch_buffer_reset_partition_data(switch_buffer_t *buffer)
 {
 	if (!switch_test_flag(buffer, SWITCH_BUFFER_FLAG_PARTITION)) {
diff --git a/src/switch_core_media.c b/src/switch_core_media.c
index 121c12bef0..06b4d312f8 100644
--- a/src/switch_core_media.c
+++ b/src/switch_core_media.c
@@ -47,9 +47,16 @@ static void gen_ice(switch_core_session_t *session, switch_media_type_t type, co
 #define RTCP_MUX
 #define MAX_CODEC_CHECK_FRAMES 50//x:mod_sofia.h
 #define MAX_MISMATCH_FRAMES 5//x:mod_sofia.h
-#define type2str(type) type == SWITCH_MEDIA_TYPE_VIDEO ? "video" : "audio"
+#define type2str(type) type == SWITCH_MEDIA_TYPE_VIDEO ? "video" : (type == SWITCH_MEDIA_TYPE_AUDIO ? "audio" : "text")
 #define VIDEO_REFRESH_FREQ 1000000
 
+#define TEXT_TIMER_MS 100
+#define TEXT_TIMER_SAMPLES 10
+#define TEXT_PERIOD_TIMEOUT 3000
+#define MAX_RED_FRAMES 25
+#define RED_PACKET_SIZE 100
+
+
 typedef enum {
 	SMF_INIT = (1 << 0),
 	SMF_READY = (1 << 1),
@@ -92,6 +99,24 @@ typedef enum {
 	CRYPTO_MODE_FORBIDDEN
 } switch_rtp_crypto_mode_t;
 
+struct switch_rtp_text_factory_s {
+	switch_memory_pool_t *pool;
+	switch_frame_t text_frame;
+	int red_level;
+	switch_byte_t *text_write_frame_data;
+	switch_frame_t text_write_frame;
+	switch_buffer_t *write_buffer;
+	int write_empty;
+	switch_byte_t *red_buf[MAX_RED_FRAMES];
+	int red_bufsize;
+	int red_buflen[MAX_RED_FRAMES];
+	uint32_t red_ts[MAX_RED_FRAMES];
+	int red_pos;
+	int red_max;
+	switch_timer_t timer;
+};
+
+
 typedef struct switch_rtp_engine_s {
 	switch_secure_settings_t ssec[CRYPTO_INVALID+1];
 	switch_rtp_crypto_key_type_t crypto_type;
@@ -176,6 +201,14 @@ typedef struct switch_rtp_engine_s {
 	uint8_t new_dtls;
 	uint32_t sdp_bw;
 	uint8_t reject_avp;
+	int t140_pt;
+	int red_pt;
+	switch_rtp_text_factory_t *tf;
+
+	switch_engine_function_t engine_function;
+	void *engine_user_data;
+	int8_t engine_function_running;
+
 } switch_rtp_engine_t;
 
 struct switch_media_handle_s {
@@ -184,8 +217,8 @@ struct switch_media_handle_s {
 	switch_core_media_flag_t media_flags[SCMF_MAX];
 	smh_flag_t flags;
 	switch_rtp_engine_t engines[SWITCH_MEDIA_TYPE_TOTAL];
-	switch_mutex_t *read_mutex[2];
-	switch_mutex_t *write_mutex[2];
+	switch_mutex_t *read_mutex[SWITCH_MEDIA_TYPE_TOTAL];
+	switch_mutex_t *write_mutex[SWITCH_MEDIA_TYPE_TOTAL];
 	char *codec_order[SWITCH_MAX_CODECS];
 	int codec_order_last;
 	const switch_codec_implementation_t *codecs[SWITCH_MAX_CODECS];
@@ -223,9 +256,8 @@ struct switch_media_handle_s {
 	switch_time_t last_codec_refresh;
 	switch_time_t last_video_refresh_req;
 	switch_timer_t video_timer;
-	switch_video_function_t video_function;
-	void *video_user_data;
-	int8_t video_function_running;
+
+
 	switch_vid_params_t vid_params; 
 	switch_file_handle_t *video_read_fh;
 	switch_file_handle_t *video_write_fh;
@@ -236,6 +268,9 @@ struct switch_media_handle_s {
 
 	switch_thread_t *video_write_thread;
 	int video_write_thread_running;
+
+	switch_time_t last_text_frame;
+
 };
 
 static switch_srtp_crypto_suite_t SUITES[CRYPTO_INVALID] = {
@@ -381,6 +416,7 @@ SWITCH_DECLARE(void) switch_core_media_pass_zrtp_hash2(switch_core_session_t *al
 {
 	_switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_AUDIO);
 	_switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_VIDEO);
+	_switch_core_media_pass_zrtp_hash2(aleg_session, bleg_session, SWITCH_MEDIA_TYPE_TEXT);
 }
 
 
@@ -424,19 +460,21 @@ static void switch_core_media_find_zrtp_hash(switch_core_session_t *session, sdp
 	switch_channel_t *channel = switch_core_session_get_channel(session);
 	switch_rtp_engine_t *audio_engine;
 	switch_rtp_engine_t *video_engine;
+	switch_rtp_engine_t *text_engine;
 	sdp_media_t *m;
 	sdp_attribute_t *attr;
-	int got_audio = 0, got_video = 0;
+	int got_audio = 0, got_video = 0, got_text = 0;
 
 	if (!session->media_handle) return;
 
 	audio_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	video_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	text_engine = &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO];
 
 
 	switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_DEBUG1, "Looking for zrtp-hash\n");
 	for (m = sdp->sdp_media; m; m = m->m_next) {
-		if (got_audio && got_video) break;
+		if (got_audio && got_video && got_text) break;
 		if (m->m_port && ((m->m_type == sdp_media_audio && !got_audio)
 						  || (m->m_type == sdp_media_video && !got_video))) {
 			for (attr = m->m_attributes; attr; attr = attr->a_next) {
@@ -454,6 +492,12 @@ static void switch_core_media_find_zrtp_hash(switch_core_session_t *session, sdp
 					switch_channel_set_variable(channel, "r_sdp_video_zrtp_hash", attr->a_value);
 					video_engine->remote_sdp_zrtp_hash = switch_core_session_strdup(session, attr->a_value);
 					got_video++;
+				} else if (m->m_type == sdp_media_text) {
+					switch_log_printf(SWITCH_CHANNEL_CHANNEL_LOG(channel), SWITCH_LOG_DEBUG,
+									  "Found text zrtp-hash; setting r_sdp_video_zrtp_hash=%s\n", attr->a_value);
+					switch_channel_set_variable(channel, "r_sdp_text_zrtp_hash", attr->a_value);
+					text_engine->remote_sdp_zrtp_hash = switch_core_session_strdup(session, attr->a_value);
+					got_text++;
 				}
 				switch_channel_set_flag(channel, CF_ZRTP_HASH);
 				break;
@@ -534,6 +578,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess
 {
 	switch_rtp_engine_t *a_engine;
 	switch_rtp_engine_t *v_engine;
+	switch_rtp_engine_t *t_engine;
 	switch_media_handle_t *smh;
 	const char *val;
 	int x = 0;
@@ -546,6 +591,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];	
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];	
 	
 	if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) &&
 		!((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && 
@@ -561,6 +607,11 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_check_autoadj(switch_core_sess
 			switch_rtp_set_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
 			x++;
 		}
+
+		if (t_engine->rtp_session) {
+			switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
+			x++;
+		}
 	}
 
 	return x ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE;
@@ -758,11 +809,15 @@ SWITCH_DECLARE(payload_map_t *) switch_core_media_add_payload_map(switch_core_se
 	switch_mutex_lock(smh->sdp_mutex);
 
 	for (pmap = engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) {
-		exists = (!strcasecmp(name, pmap->iananame) && pmap->pt == pt && (!pmap->rate || rate == pmap->rate) && (!pmap->ptime || pmap->ptime == ptime));
+
+		if (type == SWITCH_MEDIA_TYPE_TEXT) {
+			exists = (type == pmap->type && !strcasecmp(name, pmap->iananame) && pmap->pt == pt);
+		} else {
+			exists = (type == pmap->type && !strcasecmp(name, pmap->iananame) && pmap->pt == pt && (!pmap->rate || rate == pmap->rate) && (!pmap->ptime || pmap->ptime == ptime));
+		}
 
 		if (exists) {
-			
-			if (!zstr(fmtp) && !zstr(pmap->rm_fmtp)) {
+			if (type != SWITCH_MEDIA_TYPE_TEXT && !zstr(fmtp) && !zstr(pmap->rm_fmtp)) {
 				if (strcmp(pmap->rm_fmtp, fmtp)) {
 					exists = 0;
 					local_pt = pmap->pt;
@@ -774,7 +829,6 @@ SWITCH_DECLARE(payload_map_t *) switch_core_media_add_payload_map(switch_core_se
 		}
 	}
 
-
 	if (!exists) {
 		switch_ssize_t hlen = -1;
 
@@ -888,6 +942,9 @@ SWITCH_DECLARE(void) switch_core_session_clear_crypto(switch_core_session_t *ses
 						   "srtp_remote_video_crypto_key",
 						   "srtp_remote_video_crypto_tag",
 						   "srtp_remote_video_crypto_type",
+						   "srtp_remote_text_crypto_key",
+						   "srtp_remote_text_crypto_tag",
+						   "srtp_remote_text_crypto_type",
 						   "rtp_secure_media",
 						   "rtp_secure_media_inbound",
 						   "rtp_secure_media_outbound",
@@ -903,6 +960,7 @@ SWITCH_DECLARE(void) switch_core_session_clear_crypto(switch_core_session_t *ses
 	for (i = 0; i < CRYPTO_INVALID; i++) {
 		memset(&smh->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i]));
 		memset(&smh->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i]));
+		memset(&smh->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i], 0, sizeof(smh->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i]));
 	}
 
 }
@@ -1177,11 +1235,15 @@ static void switch_core_session_get_recovery_crypto_key(switch_core_session_t *s
 		keyvar = "srtp_remote_audio_crypto_key";
 		tagvar = "srtp_remote_audio_crypto_tag";
 		ctypevar = "srtp_remote_audio_crypto_type";
-	} else {
+	} else if (type == SWITCH_MEDIA_TYPE_VIDEO) {
 		keyvar = "srtp_remote_video_crypto_key";
 		tagvar = "srtp_remote_video_crypto_tag";
 		ctypevar = "srtp_remote_video_crypto_type";
-	}
+	} else if (type == SWITCH_MEDIA_TYPE_TEXT) {
+		keyvar = "srtp_remote_text_crypto_key";
+		tagvar = "srtp_remote_text_crypto_tag";
+		ctypevar = "srtp_remote_text_crypto_type";
+	} else return;
 
 	if ((tmp = switch_channel_get_variable(session->channel, keyvar))) {
 		if ((tmp = switch_channel_get_variable(session->channel, ctypevar))) {
@@ -1209,8 +1271,12 @@ static void switch_core_session_apply_crypto(switch_core_session_t *session, swi
 
 	if (type == SWITCH_MEDIA_TYPE_AUDIO) {
 		varname = "rtp_secure_audio_confirmed";
-	} else {
+	} else if (type == SWITCH_MEDIA_TYPE_VIDEO) {
 		varname = "rtp_secure_video_confirmed";
+	} else if (type == SWITCH_MEDIA_TYPE_TEXT) {
+		varname = "rtp_secure_text_confirmed";
+	} else {
+		return;
 	}
 
 	if (!session->media_handle) return;
@@ -1402,6 +1468,10 @@ SWITCH_DECLARE(int) switch_core_session_check_incoming_crypto(switch_core_sessio
 					switch_channel_set_variable(session->channel, "srtp_remote_video_crypto_key", crypto);
 					switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_tag", "%d", crypto_tag);
 					switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_type", "%s", switch_core_media_crypto_type2str(ctype));
+				} else if (engine->type == SWITCH_MEDIA_TYPE_TEXT) {
+					switch_channel_set_variable(session->channel, "srtp_remote_text_crypto_key", crypto);
+					switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_tag", "%d", crypto_tag);
+					switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_type", "%s", switch_core_media_crypto_type2str(ctype));
 				}
 
 				engine->ssec[engine->crypto_type].crypto_tag = crypto_tag;
@@ -1434,6 +1504,9 @@ SWITCH_DECLARE(int) switch_core_session_check_incoming_crypto(switch_core_sessio
 		} else if (engine->type == SWITCH_MEDIA_TYPE_VIDEO) {
 			switch_channel_set_variable(session->channel, "srtp_remote_video_crypto_key", crypto);
 			switch_channel_set_variable_printf(session->channel, "srtp_remote_video_crypto_type", "%s", switch_core_media_crypto_type2str(ctype));
+		} else if (engine->type == SWITCH_MEDIA_TYPE_TEXT) {
+			switch_channel_set_variable(session->channel, "srtp_remote_text_crypto_key", crypto);
+			switch_channel_set_variable_printf(session->channel, "srtp_remote_text_crypto_type", "%s", switch_core_media_crypto_type2str(ctype));
 		}
 
 		engine->ssec[engine->crypto_type].crypto_tag = crypto_tag;
@@ -1483,6 +1556,9 @@ SWITCH_DECLARE(void) switch_core_session_check_outgoing_crypto(switch_core_sessi
 
 		switch_core_media_build_crypto(session->media_handle,
 									   SWITCH_MEDIA_TYPE_VIDEO, SWITCH_NO_CRYPTO_TAG, smh->crypto_suite_order[i], SWITCH_RTP_CRYPTO_SEND, 0);
+
+		switch_core_media_build_crypto(session->media_handle,
+									   SWITCH_MEDIA_TYPE_TEXT, SWITCH_NO_CRYPTO_TAG, smh->crypto_suite_order[i], SWITCH_RTP_CRYPTO_SEND, 0);
 	}
 
 }
@@ -1544,7 +1620,7 @@ static void set_stats(switch_core_session_t *session, switch_media_type_t type,
 SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session)
 {
 	switch_media_handle_t *smh;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 
 	switch_assert(session);
 
@@ -1554,6 +1630,7 @@ SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];	
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];	
 
 	if (a_engine->rtp_session) {
 		switch_rtp_sync_stats(a_engine->rtp_session);
@@ -1563,6 +1640,10 @@ SWITCH_DECLARE(void) switch_core_media_sync_stats(switch_core_session_t *session
 		switch_rtp_sync_stats(v_engine->rtp_session);
 	}
 
+	if (t_engine->rtp_session) {
+		switch_rtp_sync_stats(t_engine->rtp_session);
+	}
+
 }
 
 SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session)
@@ -1576,6 +1657,7 @@ SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session)
 
 	set_stats(session, SWITCH_MEDIA_TYPE_AUDIO, "audio");
 	set_stats(session, SWITCH_MEDIA_TYPE_VIDEO, "video");
+	set_stats(session, SWITCH_MEDIA_TYPE_TEXT, "text");
 }
 
 
@@ -1583,7 +1665,7 @@ SWITCH_DECLARE(void) switch_core_media_set_stats(switch_core_session_t *session)
 SWITCH_DECLARE(void) switch_media_handle_destroy(switch_core_session_t *session)
 {
 	switch_media_handle_t *smh;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine;//, *t_engine;
 
 	switch_assert(session);
 
@@ -1593,6 +1675,7 @@ SWITCH_DECLARE(void) switch_media_handle_destroy(switch_core_session_t *session)
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];	
+	//t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];	
 
 	
 	if (smh->video_timer.timer_interface) {
@@ -1644,6 +1727,7 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t
 		*smhp = session->media_handle;
 		switch_set_flag(session->media_handle, SMF_INIT);
 		session->media_handle->media_flags[SCMF_RUNNING] = 1;
+
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].type = SWITCH_MEDIA_TYPE_AUDIO;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].crypto_type = CRYPTO_INVALID;
@@ -1652,16 +1736,30 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t
 			session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].ssec[i].crypto_type = i;
 		}
 
+
+
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN;
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].type = SWITCH_MEDIA_TYPE_AUDIO;
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].crypto_type = CRYPTO_INVALID;
+
+		for (i = 0; i < CRYPTO_INVALID; i++) {
+			session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].ssec[i].crypto_type = i;
+		}
+
+
+		
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].read_frame.buflen = SWITCH_RTP_MAX_BUF_LEN;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].type = SWITCH_MEDIA_TYPE_VIDEO;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].crypto_type = CRYPTO_INVALID;
-
+		
 
 		switch_channel_set_variable(session->channel, "video_media_flow", "sendrecv");
 		switch_channel_set_variable(session->channel, "audio_media_flow", "sendrecv");
+		switch_channel_set_variable(session->channel, "text_media_flow", "sendrecv");
 
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].smode = SWITCH_MEDIA_FLOW_SENDRECV;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].smode = SWITCH_MEDIA_FLOW_SENDRECV;
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].smode = SWITCH_MEDIA_FLOW_SENDRECV;
 
 		for (i = 0; i < CRYPTO_INVALID; i++) {
 			session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].ssec[i].crypto_type = i;
@@ -1692,14 +1790,25 @@ SWITCH_DECLARE(switch_status_t) switch_media_handle_create(switch_media_handle_t
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].ssrc = 
 			(uint32_t) ((intptr_t) &session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO] + (uint32_t) time(NULL) / 2);
 
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].ssrc = 
+			(uint32_t) ((intptr_t) &session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT] + (uint32_t) time(NULL) / 2);
+
+
+
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t));
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].cur_payload_map->current = 1;
+
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t));
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].payload_map;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].cur_payload_map->current = 1;
 		session->media_handle->engines[SWITCH_MEDIA_TYPE_VIDEO].codec_settings.video.try_hardware_encoder = 1;
 
+
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].payload_map = switch_core_alloc(session->pool, sizeof(payload_map_t));
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].cur_payload_map = session->media_handle->engines[SWITCH_MEDIA_TYPE_AUDIO].payload_map;
+		session->media_handle->engines[SWITCH_MEDIA_TYPE_TEXT].cur_payload_map->current = 1;
+
 		switch_channel_set_flag(session->channel, CF_DTLS_OK);
 
 		status = SWITCH_STATUS_SUCCESS;
@@ -1874,7 +1983,7 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t
 {
 	const char *val;
 	switch_media_handle_t *smh;
-	switch_rtp_engine_t *a_engine = NULL, *v_engine = NULL;
+	switch_rtp_engine_t *a_engine = NULL, *v_engine = NULL, *t_engine = NULL;
 
 	switch_assert(session);
 
@@ -1884,6 +1993,7 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 
 	if (!zstr(input)) {
@@ -1935,6 +2045,32 @@ static void check_jb(switch_core_session_t *session, const char *input, int32_t
 				return;
 			}
 		}
+
+		if (t_engine->rtp_session) {
+			if (!strncasecmp(input, "tbsize:", 7)) {
+				int frames = 0, max_frames = 0;
+				s = input + 7;
+				
+				frames = atoi(s);
+
+				if ((s = strchr(s, ':')) && *(s+1) != '\0') {
+					max_frames = atoi(s+1);
+				}
+				
+				if (frames > 0) {
+					switch_rtp_set_video_buffer_size(t_engine->rtp_session, frames, max_frames);
+				}
+				return;
+			} else if (!strncasecmp(input, "tdebug:", 7)) {
+				s = input + 7;
+				
+				if (s && !strcmp(s, "off")) {
+					s = NULL;
+				}
+				switch_rtp_debug_jitter_buffer(t_engine->rtp_session, s);
+				return;
+			}
+		}
 	}
 	
 
@@ -2134,6 +2270,133 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_lock_unlock(switch_core_s
 }
 
 
+
+
+//?
+SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_create(switch_rtp_text_factory_t **tfP, switch_memory_pool_t *pool)
+{
+	int x;
+
+	*tfP = switch_core_alloc(pool, sizeof(**tfP));
+	
+	switch_buffer_create_dynamic(&(*tfP)->write_buffer,  512, 1024, 0);
+	(*tfP)->pool = pool;
+	(*tfP)->text_write_frame_data = switch_core_alloc(pool, SWITCH_RTP_MAX_BUF_LEN);
+	(*tfP)->text_write_frame.packet = (*tfP)->text_write_frame_data;
+	(*tfP)->text_write_frame.data = (switch_byte_t *)(*tfP)->text_write_frame.packet + 12;
+	(*tfP)->text_write_frame.buflen = SWITCH_RTP_MAX_BUF_LEN - 12;
+
+	(*tfP)->red_max = 5;
+	(*tfP)->red_bufsize = SWITCH_RTP_MAX_BUF_LEN;
+
+	switch_core_timer_init(&(*tfP)->timer, "soft", TEXT_TIMER_MS, TEXT_TIMER_SAMPLES, pool);
+
+	for(x = 0; x < (*tfP)->red_max; x++) {
+		(*tfP)->red_buf[x] = switch_core_alloc(pool, SWITCH_RTP_MAX_BUF_LEN);
+	}
+
+	return SWITCH_STATUS_SUCCESS;
+}
+
+SWITCH_DECLARE(switch_status_t) switch_rtp_text_factory_destroy(switch_rtp_text_factory_t **tfP)
+{
+	switch_core_timer_destroy(&(*tfP)->timer);
+	switch_buffer_destroy(&(*tfP)->write_buffer);
+
+	return SWITCH_STATUS_SUCCESS;;
+}
+
+#include <wchar.h>
+
+static int get_rtt_red_seq(int want_seq, void *data, switch_size_t datalen, int seq, switch_payload_t *new_payload, void *new_data, uint32_t *new_datalen)
+{
+	unsigned char *buf = data;
+	int count = 0;
+	unsigned char *e = (buf + datalen);
+
+	int len[MAX_RED_FRAMES] = { 0 };
+	int pt[MAX_RED_FRAMES] = { 0 };
+	int idx = 0, x = 0;
+
+	*new_datalen = datalen;
+
+	*(buf + datalen) = '\0';
+	
+	while (*buf & 0x80) {
+		if (buf + 3 > e) {
+			*new_datalen = 0;
+			return 0;
+		}
+		
+		pt[count] = *buf & 0x7F;
+		len[count] = (ntohs(*(uint16_t *)(buf + 2)) & 0x03ff);
+		buf += 4;
+		count++;
+	}
+
+	buf++;
+
+	idx = count - (seq - want_seq);
+
+	if (idx < 0) {
+		*new_datalen = 0;
+		return 0;
+	}
+
+	if (!len[idx]) {
+		*new_datalen = len[idx];
+		return 0;
+	}
+
+	for(x = 0; x < idx; x++) {
+		buf += len[x];
+	}
+
+	*new_datalen = len[idx];
+	*new_payload = pt[idx];
+
+	memcpy(new_data, buf, len[idx]);
+	
+	*(((char *)new_data) + len[idx]) = '\0';
+
+	return 1;
+	
+}
+
+static void *get_rtt_payload(void *data, switch_size_t datalen, switch_payload_t *new_payload, uint32_t *new_datalen, int *red_level)
+{
+	unsigned char *buf = data;
+	int bytes = 0, count = 0, pt = 0, len = 0;//, ts = 0;
+	unsigned char *e = (buf + datalen);
+
+	*new_datalen = datalen;
+	*red_level = 1;
+
+	while (*buf & 0x80) {
+		if (buf + 3 > e) {
+			*new_datalen = 0;
+			return NULL;
+		}
+		count++;
+		pt = *buf & 0x7F;
+		//ts = ntohs(*(uint16_t *)(buf + 1)) >> 2;
+		len  = (ntohs(*(uint16_t *)(buf + 2)) & 0x03ff);
+		buf += 4;
+		bytes += len;
+	}
+
+	*new_datalen = datalen - bytes - 1 - (count *4);
+	*new_payload = pt;
+	buf += bytes + 1;
+	
+	if (buf > e) {
+		*new_datalen = 0;
+		return NULL;
+	}
+	
+	return buf;
+}
+
 //?
 SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session_t *session, switch_frame_t **frame,
 															 switch_io_flag_t flags, int stream_id, switch_media_type_t type)
@@ -2156,7 +2419,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 
 	engine = &smh->engines[type];
 
-	if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) {
+	if (type != SWITCH_MEDIA_TYPE_TEXT && (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec))) {
 		return SWITCH_STATUS_FALSE;
 	}
 
@@ -2176,10 +2439,12 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 	engine->read_frame.flags = SFF_NONE;
 	engine->read_frame.m = SWITCH_FALSE;
 	engine->read_frame.img = NULL;
-
+	engine->read_frame.payload = 0;
+	
 	while (smh->media_flags[SCMF_RUNNING] && engine->read_frame.datalen == 0) {
 		engine->read_frame.flags = SFF_NONE;
 		status = switch_rtp_zerocopy_read_frame(engine->rtp_session, &engine->read_frame, flags);
+
 		if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) {
 			if (status == SWITCH_STATUS_TIMEOUT) {
 
@@ -2225,7 +2490,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 
 
 		/* re-set codec if necessary */
-		if (engine->reset_codec > 0) {
+		if (type != SWITCH_MEDIA_TYPE_TEXT && engine->reset_codec > 0) {
 			const char *val;
 			int rtp_timeout_sec = 0;
 			int rtp_hold_timeout_sec = 0;
@@ -2283,6 +2548,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 			do_cng = 1;
 		}
 
+
 		if (do_cng) {
 			/* return CNG for now */
 			*frame = &engine->read_frame;
@@ -2379,7 +2645,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 			switch_channel_queue_dtmf(session->channel, &dtmf);
 		}
 		
-		if (engine->read_frame.datalen > 0) {
+		if (type != SWITCH_MEDIA_TYPE_TEXT && engine->read_frame.datalen > 0) {
 			uint32_t bytes = 0;
 			int frames = 1;
 
@@ -2466,8 +2732,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 						engine->last_seq = engine->read_frame.seq;
 
 					} else if (smh->media_flags[SCMF_AUTOFIX_TIMING] && is_vbr && switch_rtp_get_jitter_buffer(engine->rtp_session) 
-																			&& engine->read_frame.timestamp && engine->read_frame.seq) {
-
+							   && engine->read_frame.timestamp && engine->read_frame.seq && engine->read_impl.samples_per_second) {
 						uint32_t codec_ms = (int) (engine->read_frame.timestamp -
 								   engine->last_ts) / (engine->read_impl.samples_per_second / 1000);
 
@@ -2582,12 +2847,72 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_read_frame(switch_core_session
 			break;
 		}
 	}
-	
+
 	if (engine->read_frame.datalen == 0) {
 		*frame = NULL;
 	}
 
-	*frame = &engine->read_frame;
+
+	if (type == SWITCH_MEDIA_TYPE_TEXT && !switch_test_flag((&engine->read_frame), SFF_CNG)) {
+		if (engine->red_pt) {
+			unsigned char *p = engine->read_frame.data;
+			
+			*(p + engine->read_frame.datalen) = '\0';
+			engine->tf->text_frame = engine->read_frame;
+			
+			if (switch_test_flag((&engine->read_frame), SFF_PLC)) {
+				switch_jb_t *jb = switch_core_session_get_jb(session, SWITCH_MEDIA_TYPE_TEXT);
+				int i = 0;
+					
+				engine->tf->text_frame.datalen = 0;
+
+				for (i = 1; i < 3; i++) {
+					switch_frame_t frame = { 0 };
+					uint8_t buf[SWITCH_RTP_MAX_BUF_LEN];
+					frame.data = buf;
+					frame.buflen = sizeof(buf);
+
+					if (switch_jb_peek_frame(jb, 0, engine->read_frame.seq, i, &frame) == SWITCH_STATUS_SUCCESS) {
+						if (get_rtt_red_seq(engine->read_frame.seq,
+											frame.data, 
+											frame.datalen, 
+											frame.seq,
+											&engine->tf->text_frame.payload,
+											engine->tf->text_frame.data,
+											&engine->tf->text_frame.datalen)) {
+							break;
+							
+						}
+					}
+
+				}
+
+				if (engine->tf->text_frame.datalen == 0) {
+					engine->tf->text_frame.data = "� ";
+					engine->tf->text_frame.datalen = strlen(engine->tf->text_frame.data);
+				}
+
+			} else {
+				if (!(engine->tf->text_frame.data = get_rtt_payload(engine->read_frame.data, 
+																	engine->tf->text_frame.datalen, 
+																	&engine->tf->text_frame.payload,
+																	&engine->tf->text_frame.datalen,
+																	&engine->tf->red_level))) {
+					engine->tf->text_frame.datalen = 0;
+				}
+			}
+
+			*frame = &engine->tf->text_frame;
+
+			if ((*frame)->datalen == 0) {
+				(*frame)->flags |= SFF_CNG;
+				(*frame)->data = "";
+			}
+		}
+
+	} else {
+		*frame = &engine->read_frame;
+	}
 
 	status = SWITCH_STATUS_SUCCESS;
 
@@ -2626,26 +2951,29 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_write_frame(switch_core_sessio
 		return SWITCH_STATUS_SUCCESS;
 	}  
 
-	while (!(engine->read_codec.implementation && switch_rtp_ready(engine->rtp_session))) {
-		if (switch_channel_ready(session->channel)) {
-			switch_yield(10000);
-		} else {
+	if (type != SWITCH_MEDIA_TYPE_TEXT) {
+
+		while (!(engine->read_codec.implementation && switch_rtp_ready(engine->rtp_session))) {
+			if (switch_channel_ready(session->channel)) {
+				switch_yield(10000);
+			} else {
+				return SWITCH_STATUS_GENERR;
+			}
+		}
+
+		if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) {
 			return SWITCH_STATUS_GENERR;
 		}
-	}
 
-	if (!engine->read_codec.implementation || !switch_core_codec_ready(&engine->read_codec)) {
-		return SWITCH_STATUS_GENERR;
-	}
+		if (!switch_test_flag(frame, SFF_CNG) && !switch_test_flag(frame, SFF_PROXY_PACKET)) {
+			if (engine->read_impl.encoded_bytes_per_packet) {
+				bytes = engine->read_impl.encoded_bytes_per_packet;
+				frames = ((int) frame->datalen / bytes);
+			} else
+				frames = 1;
 
-	if (!switch_test_flag(frame, SFF_CNG) && !switch_test_flag(frame, SFF_PROXY_PACKET)) {
-		if (engine->read_impl.encoded_bytes_per_packet) {
-			bytes = engine->read_impl.encoded_bytes_per_packet;
-			frames = ((int) frame->datalen / bytes);
-		} else
-			frames = 1;
-
-		samples = frames * engine->read_impl.samples_per_packet;
+			samples = frames * engine->read_impl.samples_per_packet;
+		}
 	}
 
 	engine->timestamp_send += samples;
@@ -3083,7 +3411,7 @@ SWITCH_DECLARE(void) switch_core_media_clear_ice(switch_core_session_t *session)
 
 SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session)
 {
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 
 	switch_assert(session);
@@ -3094,6 +3422,7 @@ SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session)
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	if (a_engine->rtp_session) {
 		switch_rtp_set_flag(a_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
@@ -3102,11 +3431,15 @@ SWITCH_DECLARE(void) switch_core_media_pause(switch_core_session_t *session)
 	if (v_engine->rtp_session) {
 		switch_rtp_set_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
 	}
+
+	if (t_engine->rtp_session) {
+		switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
+	}
 }
 
 SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session)
 {
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 
 	switch_assert(session);
@@ -3117,6 +3450,7 @@ SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session)
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	if (a_engine->rtp_session) {
 		switch_rtp_clear_flag(a_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
@@ -3125,6 +3459,10 @@ SWITCH_DECLARE(void) switch_core_media_resume(switch_core_session_t *session)
 	if (v_engine->rtp_session) {
 		switch_rtp_clear_flag(v_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
 	}
+
+	if (t_engine->rtp_session) {
+		switch_rtp_clear_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_PAUSE);
+	}
 }
 
 
@@ -3713,13 +4051,13 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
 	switch_channel_t *channel = switch_core_session_get_channel(session);
 	const char *val;
 	const char *crypto = NULL;
-	int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0;
+	int got_crypto = 0, got_video_crypto = 0, got_audio = 0, saw_audio = 0, got_avp = 0, got_video_avp = 0, got_video_savp = 0, got_savp = 0, got_udptl = 0, got_webrtc = 0, got_text = 0, got_text_crypto = 0;
 	int scrooge = 0;
 	sdp_parser_t *parser = NULL;
 	sdp_session_t *sdp;
 	const switch_codec_implementation_t **codec_array;
 	int total_codecs;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 	uint32_t near_rate = 0;
 	const switch_codec_implementation_t *mimp = NULL, *near_match = NULL;
@@ -3749,6 +4087,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	codec_array = smh->codecs;
 	total_codecs = smh->mparams->num_codecs;
@@ -3843,6 +4182,7 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
 
 	check_ice(smh, SWITCH_MEDIA_TYPE_AUDIO, sdp, NULL);
 	check_ice(smh, SWITCH_MEDIA_TYPE_VIDEO, sdp, NULL);
+	check_ice(smh, SWITCH_MEDIA_TYPE_TEXT, sdp, NULL);
 
 	if ((sdp->sdp_connection && sdp->sdp_connection->c_address && !strcmp(sdp->sdp_connection->c_address, "0.0.0.0"))) {
 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_WARNING, "RFC2543 from March 1999 called; They want their 0.0.0.0 hold method back.....\n");
@@ -4629,6 +4969,102 @@ SWITCH_DECLARE(uint8_t) switch_core_media_negotiate_sdp(switch_core_session_t *s
 				}
 			}
 
+		} else if (switch_channel_var_true(session->channel, "rtp_enable_text") && !got_text && m->m_type == sdp_media_text && m->m_port) {
+			sdp_rtpmap_t *map;
+			payload_map_t *red_pmap = NULL;
+
+
+			connection = sdp->sdp_connection;
+			if (m->m_connections) {
+				connection = m->m_connections;
+			}
+			
+			if (!connection) {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot find a c= line in the sdp at media or session level!\n");
+				match = 0;
+				break;
+			}
+
+			switch_channel_set_variable(session->channel, "text_possible", "true");
+			switch_channel_set_flag(session->channel, CF_TEXT_SDP_RECVD);
+			switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE);
+
+			got_text++;
+			
+			for (map = m->m_rtpmaps; map; map = map->rm_next) {
+				payload_map_t *pmap;
+
+				pmap = switch_core_media_add_payload_map(session,
+														 SWITCH_MEDIA_TYPE_TEXT,
+														 map->rm_encoding,
+														 NULL,
+														 NULL,
+														 SDP_TYPE_REQUEST,
+														 map->rm_pt,
+														 1000,
+														 0,
+														 1,
+														 SWITCH_TRUE);
+
+
+				pmap->remote_sdp_ip = switch_core_session_strdup(session, (char *) connection->c_address);
+				pmap->remote_sdp_port = (switch_port_t) m->m_port;
+				pmap->rm_fmtp = switch_core_session_strdup(session, (char *) mmap->rm_fmtp);
+				
+				pmap->agreed_pt = (switch_payload_t) map->rm_pt;
+				pmap->recv_pt = (switch_payload_t) map->rm_pt;
+
+
+				t_engine->cur_payload_map = pmap;
+				
+				if (!strcasecmp(map->rm_encoding, "red")) {
+					red_pmap = pmap;
+				}
+			}
+
+			t_engine->cur_payload_map = red_pmap;
+
+			for (attr = m->m_attributes; attr; attr = attr->a_next) {
+				if (!strcasecmp(attr->a_name, "rtcp") && attr->a_value) {
+					switch_channel_set_variable(session->channel, "sip_remote_text_rtcp_port", attr->a_value);
+					
+				} else if (!got_text_crypto && !strcasecmp(attr->a_name, "crypto") && !zstr(attr->a_value)) {
+					int crypto_tag;
+						
+					if (!(smh->mparams->ndlb & SM_NDLB_ALLOW_CRYPTO_IN_AVP) && 
+						!switch_true(switch_channel_get_variable(session->channel, "rtp_allow_crypto_in_avp"))) {
+						if (m->m_proto != sdp_proto_srtp && !got_webrtc) {
+							switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "a=crypto in RTP/AVP, refer to rfc3711\n");
+							match = 0;
+							goto done;
+						}
+					}
+						
+					crypto = attr->a_value;
+					crypto_tag = atoi(crypto);
+						
+					got_text_crypto = switch_core_session_check_incoming_crypto(session, 
+																				"rtp_has_text_crypto", 
+																				SWITCH_MEDIA_TYPE_TEXT, crypto, crypto_tag, sdp_type);
+					
+				}
+			}
+
+
+			//map->rm_encoding
+			//map->rm_fmtp
+			//map->rm_pt
+			//t_engine->cur_payload_map = pmap;
+
+			t_engine->codec_negotiated = 1;
+
+			if (!t_engine->local_sdp_port) {
+				switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1);
+			}
+
+			check_ice(smh, SWITCH_MEDIA_TYPE_TEXT, sdp, m);
+			//parse rtt
+
 		} else if (m->m_type == sdp_media_video && m->m_port) {
 			sdp_rtpmap_t *map;
 			const char *rm_encoding;
@@ -5433,6 +5869,138 @@ SWITCH_DECLARE(void) switch_core_autobind_cpu(void)
 }
 
 
+
+
+static void *SWITCH_THREAD_FUNC text_helper_thread(switch_thread_t *thread, void *obj)
+{
+	struct media_helper *mh = obj;
+	switch_core_session_t *session = mh->session;
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+	switch_status_t status;
+	switch_frame_t *read_frame = NULL;
+	switch_media_handle_t *smh;
+	switch_rtp_engine_t *t_engine = NULL;
+	unsigned char CR[] = TEXT_UNICODE_LINEFEED;
+	switch_frame_t cr_frame = { 0 };
+
+	if (!(smh = session->media_handle)) {
+		return NULL;
+	}
+
+	cr_frame.data = CR;
+	cr_frame.datalen = 3;
+	
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+	t_engine->thread_id = switch_thread_self();
+
+	switch_core_session_read_lock(session);
+
+	mh->up = 1;
+
+	switch_core_media_check_dtls(session, SWITCH_MEDIA_TYPE_TEXT);
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s Text thread started.\n", switch_channel_get_name(session->channel));
+
+	switch_core_session_write_text_frame(session, &cr_frame, 0, 0);
+
+	while (switch_channel_up_nosig(channel)) {
+
+		if (t_engine->engine_function) {
+			int run = 0;
+
+			switch_mutex_lock(smh->control_mutex);
+			if (t_engine->engine_function_running == 0) {
+				t_engine->engine_function_running = 1;
+				run = 1;
+			}
+			switch_mutex_unlock(smh->control_mutex);
+			
+			if (run) {
+				t_engine->engine_function(session, t_engine->engine_user_data);
+				switch_mutex_lock(smh->control_mutex);
+				t_engine->engine_function = NULL;
+				t_engine->engine_user_data = NULL;
+				t_engine->engine_function_running = 0;
+				switch_mutex_unlock(smh->control_mutex);
+			}
+		}
+
+		if (!switch_channel_test_flag(session->channel, CF_TEXT_PASSIVE)) {
+
+			status = switch_core_session_read_text_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
+			
+			if (!SWITCH_READ_ACCEPTABLE(status)) {
+				switch_cond_next();
+				continue;
+			}
+
+			if (!switch_test_flag(read_frame, SFF_CNG)) {
+				if (switch_channel_test_flag(session->channel, CF_TEXT_ECHO)) {
+					switch_core_session_write_text_frame(session, read_frame, 0, 0);
+				}
+			}
+		}
+
+		switch_core_session_write_text_frame(session, NULL, 0, 0);
+
+
+	}
+
+	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "%s Text thread ended\n", switch_channel_get_name(session->channel));
+
+	switch_core_session_rwunlock(session);
+
+	mh->up = 0;
+	return NULL;
+}
+
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_start_text_thread(switch_core_session_t *session)
+{
+	switch_threadattr_t *thd_attr = NULL;
+	switch_memory_pool_t *pool = switch_core_session_get_pool(session);
+	switch_rtp_engine_t *t_engine = NULL;
+	switch_media_handle_t *smh;
+
+	if (!switch_channel_test_flag(session->channel, CF_TEXT)) {
+		return SWITCH_STATUS_NOTIMPL;
+	}
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+
+	switch_mutex_lock(smh->control_mutex);
+
+	if (t_engine->media_thread) {
+		switch_mutex_unlock(smh->control_mutex);
+		return SWITCH_STATUS_FALSE;
+	}
+
+	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s Starting Text thread\n", switch_core_session_get_name(session));
+
+	if (t_engine->rtp_session) {
+		switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt);
+	}
+
+	t_engine->mh.session = session;
+	switch_threadattr_create(&thd_attr, pool);
+	switch_threadattr_stacksize_set(thd_attr, SWITCH_THREAD_STACKSIZE);
+	
+	switch_thread_cond_create(&t_engine->mh.cond, pool);
+	switch_mutex_init(&t_engine->mh.cond_mutex, SWITCH_MUTEX_NESTED, pool);
+	//switch_mutex_init(&t_engine->mh.file_read_mutex, SWITCH_MUTEX_NESTED, pool);
+	//switch_mutex_init(&t_engine->mh.file_write_mutex, SWITCH_MUTEX_NESTED, pool);
+	//switch_mutex_init(&smh->read_mutex[SWITCH_MEDIA_TYPE_TEXT], SWITCH_MUTEX_NESTED, pool);
+	//switch_mutex_init(&smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT], SWITCH_MUTEX_NESTED, pool);
+	switch_thread_create(&t_engine->media_thread, thd_attr, text_helper_thread, &t_engine->mh, switch_core_session_get_pool(session));
+
+	switch_mutex_unlock(smh->control_mutex);
+	return SWITCH_STATUS_SUCCESS;
+}
+
 static void *SWITCH_THREAD_FUNC video_helper_thread(switch_thread_t *thread, void *obj)
 {
 	struct media_helper *mh = obj;
@@ -5522,23 +6090,23 @@ static void *SWITCH_THREAD_FUNC video_helper_thread(switch_thread_t *thread, voi
 			switch_yield(10000);
 			continue;
 		}
-
-		if (smh->video_function) {
+		
+		if (v_engine->engine_function) {
 			int run = 0;
 
 			switch_mutex_lock(smh->control_mutex);
-			if (smh->video_function_running == 0) {
-				smh->video_function_running = 1;
+			if (v_engine->engine_function_running == 0) {
+				v_engine->engine_function_running = 1;
 				run = 1;
 			}
 			switch_mutex_unlock(smh->control_mutex);
 
 			if (run) {
-				smh->video_function(session, smh->video_user_data);
+				v_engine->engine_function(session, v_engine->engine_user_data);
 				switch_mutex_lock(smh->control_mutex);
-				smh->video_function = NULL;
-				smh->video_user_data = NULL;
-				smh->video_function_running = 0;
+				v_engine->engine_function = NULL;
+				v_engine->engine_user_data = NULL;
+				v_engine->engine_function_running = 0;
 				switch_mutex_unlock(smh->control_mutex);
 			}
 		}
@@ -5635,56 +6203,71 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_start_video_thread(switch_co
 	return SWITCH_STATUS_SUCCESS;
 }
 
-SWITCH_DECLARE(void) switch_core_media_start_video_function(switch_core_session_t *session, switch_video_function_t video_function, void *user_data)
+SWITCH_DECLARE(void) switch_core_media_start_engine_function(switch_core_session_t *session, switch_media_type_t type, switch_engine_function_t engine_function, void *user_data)
 {
 	switch_media_handle_t *smh;
+	switch_rtp_engine_t *engine;
 
 	if (!(smh = session->media_handle)) {
 		return;
 	}
 
-	switch_core_session_start_video_thread(session);
+	engine = &smh->engines[type];
+
+	if (type == SWITCH_MEDIA_TYPE_VIDEO) {
+		switch_core_session_start_video_thread(session);
+	}
+
+	if (type == SWITCH_MEDIA_TYPE_TEXT) {
+		switch_core_session_start_text_thread(session);
+	}
 
 	switch_mutex_lock(smh->control_mutex);
-	if (!smh->video_function_running) {
-		smh->video_function = video_function;
-		smh->video_user_data = user_data;
+	if (!engine->engine_function_running) {
+		engine->engine_function = engine_function;
+		engine->engine_user_data = user_data;
 		switch_core_session_video_reset(session);
 	}
 	switch_mutex_unlock(smh->control_mutex);
 }
 
-SWITCH_DECLARE(int) switch_core_media_check_video_function(switch_core_session_t *session)
+SWITCH_DECLARE(int) switch_core_media_check_engine_function(switch_core_session_t *session, switch_media_type_t type)
 {
 	switch_media_handle_t *smh;
 	int r;
+	switch_rtp_engine_t *engine;
 
 	if (!(smh = session->media_handle)) {
 		return 0;
 	}
+
+	engine = &smh->engines[type];
 	
 	switch_mutex_lock(smh->control_mutex);
-	r = (smh->video_function_running > 0);
+	r = (engine->engine_function_running > 0);
 	switch_mutex_unlock(smh->control_mutex);
 
 	return r;
 }
 
-SWITCH_DECLARE(void) switch_core_media_end_video_function(switch_core_session_t *session)
+SWITCH_DECLARE(void) switch_core_media_end_engine_function(switch_core_session_t *session, switch_media_type_t type)
 {
 	switch_media_handle_t *smh;
+	switch_rtp_engine_t *engine;
 
 	if (!(smh = session->media_handle)) {
 		return;
 	}
+
+	engine = &smh->engines[type];
 	
 	switch_mutex_lock(smh->control_mutex);
-	if (smh->video_function_running > 0) {
-		smh->video_function_running = -1;
+	if (engine->engine_function_running > 0) {
+		engine->engine_function_running = -1;
 	}
 	switch_mutex_unlock(smh->control_mutex);
 
-	while(smh->video_function_running != 0) {
+	while(engine->engine_function_running != 0) {
 		switch_yield(10000);
 	}
 }
@@ -5732,11 +6315,12 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 	char rip[RA_PTR_LEN] = "";
 	char rp[RA_PTR_LEN] = "";
 	char rvp[RA_PTR_LEN] = "";
-	char *p, *ip_ptr = NULL, *port_ptr = NULL, *vid_port_ptr = NULL, *pe;
+	char rtp[RA_PTR_LEN] = "";
+	char *p, *ip_ptr = NULL, *port_ptr = NULL, *vid_port_ptr = NULL, *text_port_ptr = NULL, *pe;
 	int x;
 	const char *val;
 	switch_status_t status = SWITCH_STATUS_FALSE;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 
 	switch_assert(session);
@@ -5747,6 +6331,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 	
 	if (zstr(sdp_str)) {
 		sdp_str = smh->mparams->remote_sdp_str;
@@ -5776,6 +6361,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 		vid_port_ptr = p + 8;
 	}
 
+	if ((p = (char *) switch_stristr("m=text ", sdp_str))) {
+		text_port_ptr = p + 7;
+	}
+
 	if (!(ip_ptr && port_ptr)) {
 		goto end;
 	}
@@ -5811,6 +6400,16 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 		}
 	}
 
+	p = text_port_ptr;
+	x = 0;
+	while (x < sizeof(rtp) - 1 && p && *p && (*p >= '0' && *p <= '9')) {
+		rtp[x++] = *p;
+		p++;
+		if (p >= pe) {
+			goto end;
+		}
+	}
+
 	if (!(*rip && *rp)) {
 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "invalid SDP\n");
 		goto end;
@@ -5826,6 +6425,13 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 		switch_channel_set_flag(session->channel, CF_VIDEO);
 	}
 
+	if (*rtp) {
+		t_engine->cur_payload_map->remote_sdp_ip = switch_core_session_strdup(session, rip);
+		t_engine->cur_payload_map->remote_sdp_port = (switch_port_t) atoi(rtp);
+		switch_channel_set_flag(session->channel, CF_TEXT);
+		switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE);
+	}
+
 	if (v_engine->cur_payload_map->remote_sdp_ip && v_engine->cur_payload_map->remote_sdp_port) {
 		if (!strcmp(v_engine->cur_payload_map->remote_sdp_ip, rip) && atoi(rvp) == v_engine->cur_payload_map->remote_sdp_port) {
 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote video address:port [%s:%d] has not changed.\n",
@@ -5864,6 +6470,44 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_proxy_remote_addr(switch_core_
 		}
 	}
 
+	if (t_engine->cur_payload_map->remote_sdp_ip && t_engine->cur_payload_map->remote_sdp_port) {
+		if (!strcmp(t_engine->cur_payload_map->remote_sdp_ip, rip) && atoi(rvp) == t_engine->cur_payload_map->remote_sdp_port) {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Remote text address:port [%s:%d] has not changed.\n",
+							  t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port);
+		} else {
+			switch_channel_set_flag(session->channel, CF_TEXT);
+			switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE);
+			if (switch_rtp_ready(t_engine->rtp_session)) {
+				const char *rport = NULL;
+				switch_port_t remote_rtcp_port = t_engine->remote_rtcp_port;
+
+				if (!remote_rtcp_port) {
+					if ((rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port"))) {
+						remote_rtcp_port = (switch_port_t)atoi(rport);
+					}
+				}
+
+				
+				if (switch_rtp_set_remote_address(t_engine->rtp_session, t_engine->cur_payload_map->remote_sdp_ip,
+												  t_engine->cur_payload_map->remote_sdp_port, remote_rtcp_port, SWITCH_TRUE, &err) != SWITCH_STATUS_SUCCESS) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", err);
+				} else {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "TEXT RTP CHANGING DEST TO: [%s:%d]\n",
+									  t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port);
+					if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_PROXY_MODE) &&
+						!((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && 
+						!switch_channel_test_flag(session->channel, CF_AVPF)) {
+						/* Reactivate the NAT buster flag. */
+						switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
+					}
+					if (switch_media_handle_test_media_flag(smh, SCMF_AUTOFIX_TIMING)) {
+						t_engine->check_frames = 0;
+					}
+				}
+			}
+		}
+	}
+
 	if (switch_rtp_ready(a_engine->rtp_session)) {
 		char *remote_host = switch_rtp_get_remote_host(a_engine->rtp_session);
 		switch_port_t remote_port = switch_rtp_get_remote_port(a_engine->rtp_session);
@@ -6113,16 +6757,19 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_choose_port(switch_core_sessio
 	engine->adv_sdp_port = sdp_port;
 	engine->adv_sdp_ip = smh->mparams->adv_sdp_audio_ip = smh->mparams->extrtpip = switch_core_session_strdup(session, use_ip);
 
-
 	if (type == SWITCH_MEDIA_TYPE_AUDIO) {
 		switch_channel_set_variable(session->channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE, engine->local_sdp_ip);
 		switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE, "%d", sdp_port);
 		switch_channel_set_variable(session->channel, SWITCH_ADVERTISED_MEDIA_IP_VARIABLE, engine->adv_sdp_ip);
-	} else {
+	} else if (type == SWITCH_MEDIA_TYPE_VIDEO) {
 		switch_channel_set_variable(session->channel, SWITCH_LOCAL_VIDEO_IP_VARIABLE, engine->adv_sdp_ip);
 		switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_VIDEO_PORT_VARIABLE, "%d", sdp_port);
+	} else if (type == SWITCH_MEDIA_TYPE_TEXT) {
+		switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_IP_VARIABLE, engine->adv_sdp_ip);
+		switch_channel_set_variable_printf(session->channel, SWITCH_LOCAL_TEXT_PORT_VARIABLE, "%d", sdp_port);
 	}
 
+
 	return SWITCH_STATUS_SUCCESS;
 }
 
@@ -6161,7 +6808,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_choose_ports(switch_core_sessi
 //?
 SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *session)
 {
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 
 	switch_assert(session);
@@ -6172,6 +6819,11 @@ SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *ses
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+
+	if (t_engine->tf) {
+		switch_rtp_text_factory_destroy(&t_engine->tf);
+	}
 
 	if (v_engine->media_thread) {
 		switch_status_t st;
@@ -6196,6 +6848,29 @@ SWITCH_DECLARE(void) switch_core_media_deactivate_rtp(switch_core_session_t *ses
 	}
 
 
+	if (t_engine->media_thread) {
+		switch_status_t st;
+
+		t_engine->mh.up = 0;
+		switch_thread_join(&st, t_engine->media_thread);
+		t_engine->media_thread = NULL;
+	}
+
+
+	if (t_engine->rtp_session) {
+		switch_rtp_destroy(&t_engine->rtp_session);
+	} else if (t_engine->local_sdp_port) {
+		switch_rtp_release_port(smh->mparams->rtpip, t_engine->local_sdp_port);
+	}
+
+
+	if (t_engine->local_sdp_port > 0 && !zstr(smh->mparams->remote_ip) && 
+		switch_core_media_check_nat(smh, smh->mparams->remote_ip)) {
+		switch_nat_del_mapping((switch_port_t) t_engine->local_sdp_port, SWITCH_NAT_UDP);
+		switch_nat_del_mapping((switch_port_t) t_engine->local_sdp_port + 1, SWITCH_NAT_UDP);
+	}
+
+
 	if (a_engine->rtp_session) {
 		switch_rtp_destroy(&a_engine->rtp_session);
 	} else if (a_engine->local_sdp_port) {
@@ -6351,7 +7026,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 	char tmp[50];
 	char *timer_name = NULL;
 	const char *var;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 
 	switch_assert(session);
@@ -6362,6 +7037,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 
 	if (switch_channel_down(session->channel)) {
@@ -6389,6 +7065,10 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 				goto video;
 			}
 
+			if (switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) && !switch_rtp_ready(t_engine->rtp_session)) {
+				goto text;
+			}
+
 			status = SWITCH_STATUS_SUCCESS;
 			goto end;
 		} 
@@ -6826,9 +7506,313 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 
 
 		
+	text:
+
+
+
+		if (switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) && t_engine->cur_payload_map->rm_encoding && t_engine->cur_payload_map->remote_sdp_port) {
+			/******************************************************************************************/
+			if (t_engine->rtp_session && switch_channel_test_flag(session->channel, CF_REINVITE)) {
+				//const char *ip = switch_channel_get_variable(session->channel, SWITCH_LOCAL_MEDIA_IP_VARIABLE);
+				//const char *port = switch_channel_get_variable(session->channel, SWITCH_LOCAL_MEDIA_PORT_VARIABLE);
+				char *remote_host = switch_rtp_get_remote_host(t_engine->rtp_session);
+				switch_port_t remote_port = switch_rtp_get_remote_port(t_engine->rtp_session);
+				
+
+
+				if (remote_host && remote_port && !strcmp(remote_host, t_engine->cur_payload_map->remote_sdp_ip) && remote_port == t_engine->cur_payload_map->remote_sdp_port) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Text params are unchanged for %s.\n",
+									  switch_channel_get_name(session->channel));
+					t_engine->cur_payload_map->negotiated = 1;
+					goto text_up;
+				} else {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Text params changed for %s from %s:%d to %s:%d\n",
+									  switch_channel_get_name(session->channel),
+									  remote_host, remote_port, t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port);
+				}
+			}
+
+			if (!switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) {
+				if (switch_rtp_ready(t_engine->rtp_session)) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG,
+									  "TEXT RTP [%s] %s port %d -> %s port %d codec: %u\n", switch_channel_get_name(session->channel),
+									  t_engine->local_sdp_ip, t_engine->local_sdp_port, t_engine->cur_payload_map->remote_sdp_ip,
+									  t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt);
+
+					switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt);
+				}
+			}
+			
+			switch_snprintf(tmp, sizeof(tmp), "%d", t_engine->local_sdp_port);
+			switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_IP_VARIABLE, a_engine->adv_sdp_ip);
+			switch_channel_set_variable(session->channel, SWITCH_LOCAL_TEXT_PORT_VARIABLE, tmp);
+
+
+			if (t_engine->rtp_session && switch_channel_test_flag(session->channel, CF_REINVITE)) {
+				const char *rport = NULL;
+				switch_port_t remote_rtcp_port = t_engine->remote_rtcp_port;
+
+				//switch_channel_clear_flag(session->channel, CF_REINVITE);
+
+				if (!remote_rtcp_port) {
+					if ((rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port"))) {
+						remote_rtcp_port = (switch_port_t)atoi(rport);
+					}
+				}
+				
+				if (switch_rtp_set_remote_address
+					(t_engine->rtp_session, t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port, remote_rtcp_port, SWITCH_TRUE,
+					 &err) != SWITCH_STATUS_SUCCESS) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", err);
+				} else {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "TEXT RTP CHANGING DEST TO: [%s:%d]\n",
+									  t_engine->cur_payload_map->remote_sdp_ip, t_engine->cur_payload_map->remote_sdp_port);
+					if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_AVPF) &&
+						!((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val))) {
+						/* Reactivate the NAT buster flag. */
+						switch_rtp_set_flag(t_engine->rtp_session, SWITCH_RTP_FLAG_AUTOADJ);
+					}
+
+				}
+				goto text_up;
+			}
+
+			if (switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) {
+				switch_core_media_proxy_remote_addr(session, NULL);
+
+				memset(flags, 0, sizeof(flags));
+				flags[SWITCH_RTP_FLAG_PROXY_MEDIA]++;
+				flags[SWITCH_RTP_FLAG_DATAWAIT]++;
+
+				if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_AVPF) &&
+					!((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val))) {
+					flags[SWITCH_RTP_FLAG_AUTOADJ]++;
+				}
+				timer_name = NULL;
+
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG,
+								  "PROXY TEXT RTP [%s] %s:%d->%s:%d codec: %u ms: %d\n",
+								  switch_channel_get_name(session->channel),
+								  a_engine->cur_payload_map->remote_sdp_ip,
+								  t_engine->local_sdp_port,
+								  t_engine->cur_payload_map->remote_sdp_ip,
+								  t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt, t_engine->read_impl.microseconds_per_packet / 1000);
+
+				if (switch_rtp_ready(t_engine->rtp_session)) {
+					switch_rtp_set_default_payload(t_engine->rtp_session, t_engine->cur_payload_map->agreed_pt);
+				}
+			} else {
+				timer_name = smh->mparams->timer_name;
+
+				if ((var = switch_channel_get_variable(session->channel, "rtp_timer_name"))) {
+					timer_name = (char *) var;
+				}
+			}
+
+			/******************************************************************************************/
+
+			if (t_engine->rtp_session) {
+				goto text_up;
+			}
+
+
+			if (!t_engine->local_sdp_port) {
+				switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1);
+			}
+
+			memset(flags, 0, sizeof(flags));
+			flags[SWITCH_RTP_FLAG_DATAWAIT]++;
+			flags[SWITCH_RTP_FLAG_RAW_WRITE]++;
+
+			if (!switch_media_handle_test_media_flag(smh, SCMF_DISABLE_RTP_AUTOADJ) && !switch_channel_test_flag(session->channel, CF_PROXY_MODE) &&
+				!((val = switch_channel_get_variable(session->channel, "disable_rtp_auto_adjust")) && switch_true(val)) && 
+				!switch_channel_test_flag(session->channel, CF_AVPF)) {
+				flags[SWITCH_RTP_FLAG_AUTOADJ]++;				
+			}
+
+			if (switch_channel_test_flag(session->channel, CF_PROXY_MEDIA)) {
+				flags[SWITCH_RTP_FLAG_PROXY_MEDIA]++;
+			}
+			//TEXT switch_core_media_set_text_codec(session, 0);
+
+			flags[SWITCH_RTP_FLAG_USE_TIMER] = 1;
+			flags[SWITCH_RTP_FLAG_NOBLOCK] = 0;
+			flags[SWITCH_RTP_FLAG_TEXT]++;
+			//flags[SWITCH_RTP_FLAG_VIDEO]++;
+			
+			t_engine->rtp_session = switch_rtp_new(a_engine->local_sdp_ip,
+												   t_engine->local_sdp_port,
+												   t_engine->cur_payload_map->remote_sdp_ip,
+												   t_engine->cur_payload_map->remote_sdp_port,
+												   t_engine->cur_payload_map->agreed_pt,
+												   TEXT_TIMER_SAMPLES, TEXT_TIMER_MS * 1000, flags, NULL, &err, switch_core_session_get_pool(session));
+
+
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%sTEXT RTP [%s] %s:%d->%s:%d codec: %u ms: %d [%s]\n",
+							  switch_channel_test_flag(session->channel, CF_PROXY_MEDIA) ? "PROXY " : "",
+							  switch_channel_get_name(session->channel),
+							  a_engine->local_sdp_ip,
+							  t_engine->local_sdp_port,
+							  t_engine->cur_payload_map->remote_sdp_ip,
+							  t_engine->cur_payload_map->remote_sdp_port, t_engine->cur_payload_map->agreed_pt,
+							  0, switch_rtp_ready(t_engine->rtp_session) ? "SUCCESS" : err);
+
+
+			if (switch_rtp_ready(t_engine->rtp_session)) {
+				const char *ssrc;
+				
+
+				if (!t_engine->tf) {
+					switch_rtp_text_factory_create(&t_engine->tf, switch_core_session_get_pool(session));
+				}
+
+				switch_rtp_set_video_buffer_size(t_engine->rtp_session, 2, 2048);
+
+				switch_rtp_set_payload_map(t_engine->rtp_session, &t_engine->payload_map);
+				switch_channel_set_flag(session->channel, CF_TEXT);
+				switch_core_session_start_text_thread(session);
+
+				if ((ssrc = switch_channel_get_variable(session->channel, "rtp_use_text_ssrc"))) {
+					uint32_t ssrc_ul = (uint32_t) strtoul(ssrc, NULL, 10);
+					switch_rtp_set_ssrc(t_engine->rtp_session, ssrc_ul);
+					t_engine->ssrc = ssrc_ul;
+				} else {
+					switch_rtp_set_ssrc(t_engine->rtp_session, t_engine->ssrc);
+				}
+				
+				if (t_engine->remote_ssrc) {
+					switch_rtp_set_remote_ssrc(t_engine->rtp_session, t_engine->remote_ssrc);
+				}
+
+				if (t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].ready) {
+					
+					gen_ice(session, SWITCH_MEDIA_TYPE_TEXT, NULL, 0);
+
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating Text ICE\n");
+						
+					switch_rtp_activate_ice(t_engine->rtp_session, 
+											t_engine->ice_in.ufrag,
+											t_engine->ice_out.ufrag,
+											t_engine->ice_out.pwd,
+											t_engine->ice_in.pwd,
+											IPR_RTP,
+#ifdef GOOGLE_ICE
+											ICE_GOOGLE_JINGLE,
+											NULL
+#else
+											switch_ice_direction(session) == 
+											SWITCH_CALL_DIRECTION_OUTBOUND ? ICE_VANILLA : (ICE_VANILLA | ICE_CONTROLLED),
+											&t_engine->ice_in
+#endif
+											);
+						
+					
+				}
+
+				if ((val = switch_channel_get_variable(session->channel, "rtcp_text_interval_msec")) || (val = smh->mparams->rtcp_text_interval_msec)) {
+					const char *rport = switch_channel_get_variable(session->channel, "rtp_remote_text_rtcp_port");
+					switch_port_t remote_port = t_engine->remote_rtcp_port;
+
+					if (rport) {
+						remote_port = (switch_port_t)atoi(rport);
+					}
+					if (!strcasecmp(val, "passthru")) {
+						switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating TEXT RTCP PASSTHRU PORT %d\n", remote_port);
+						switch_rtp_activate_rtcp(t_engine->rtp_session, -1, remote_port, t_engine->rtcp_mux > 0);
+					} else {
+						int interval = atoi(val);
+						if (interval < 100 || interval > 500000) {
+							switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR,
+											  "Invalid rtcp interval spec [%d] must be between 100 and 500000\n", interval);
+							interval = 5000;
+						}
+						switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO,
+										  "Activating TEXT RTCP PORT %d interval %d mux %d\n", remote_port, interval, t_engine->rtcp_mux);
+						switch_rtp_activate_rtcp(t_engine->rtp_session, interval, remote_port, t_engine->rtcp_mux > 0);
+							
+					}
+					
+
+					if (t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].ready) {
+						if (t_engine->rtcp_mux > 0 && !strcmp(t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].con_addr, 
+															  t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].con_addr) &&
+							t_engine->ice_in.cands[t_engine->ice_in.chosen[1]][1].con_port == t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].con_port) {
+							switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Skipping TEXT RTCP ICE (Same as TEXT RTP)\n");
+						} else {
+							switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Activating TEXT RTCP ICE\n");
+							switch_rtp_activate_ice(t_engine->rtp_session, 
+													t_engine->ice_in.ufrag,
+													t_engine->ice_out.ufrag,
+													t_engine->ice_out.pwd,
+													t_engine->ice_in.pwd,
+													IPR_RTCP,
+#ifdef GOOGLE_ICE
+													ICE_GOOGLE_JINGLE,
+													NULL
+#else
+													switch_ice_direction(session) == 
+													SWITCH_CALL_DIRECTION_OUTBOUND ? ICE_VANILLA : (ICE_VANILLA | ICE_CONTROLLED),
+													&t_engine->ice_in
+#endif
+													);
+						
+						
+						
+						}
+				
+					}
+				}
+				
+				if (!zstr(t_engine->local_dtls_fingerprint.str) && switch_rtp_has_dtls() && dtls_ok(smh->session)) {
+					dtls_type_t xtype, 
+						dtype = t_engine->dtls_controller ? DTLS_TYPE_CLIENT : DTLS_TYPE_SERVER;
+					xtype = DTLS_TYPE_RTP;
+					if (t_engine->rtcp_mux > 0 && smh->mparams->rtcp_text_interval_msec) xtype |= DTLS_TYPE_RTCP;
+					
+					switch_rtp_add_dtls(t_engine->rtp_session, &t_engine->local_dtls_fingerprint, &t_engine->remote_dtls_fingerprint, dtype | xtype);
+					
+					if (t_engine->rtcp_mux < 1 && smh->mparams->rtcp_text_interval_msec) {
+						xtype = DTLS_TYPE_RTCP;
+						switch_rtp_add_dtls(t_engine->rtp_session, &t_engine->local_dtls_fingerprint, &t_engine->remote_dtls_fingerprint, dtype | xtype);
+					}
+				}
+					
+					
+				if ((val = switch_channel_get_variable(session->channel, "rtp_manual_text_rtp_bugs"))) {
+					switch_core_media_parse_rtp_bugs(&t_engine->rtp_bugs, val);
+				}
+				
+
+				//if (switch_channel_test_flag(session->channel, CF_AVPF)) {
+					//smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS;
+				//}
+				
+				switch_rtp_intentional_bugs(t_engine->rtp_session, t_engine->rtp_bugs | smh->mparams->manual_text_rtp_bugs);
+
+				//XX
+
+
+				switch_channel_set_variable_printf(session->channel, "rtp_use_text_pt", "%d", t_engine->cur_payload_map->agreed_pt);
+				t_engine->ssrc = switch_rtp_get_ssrc(t_engine->rtp_session);
+				switch_channel_set_variable_printf(session->channel, "rtp_use_text_ssrc", "%u", t_engine->ssrc);
+
+				switch_core_session_apply_crypto(session, SWITCH_MEDIA_TYPE_TEXT);
+
+				
+				if (switch_channel_test_flag(session->channel, CF_ZRTP_PASSTHRU)) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Activating text UDPTL mode\n");
+					switch_rtp_udptl_mode(t_engine->rtp_session);
+				}
+
+			} else {
+				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "TEXT RTP REPORTS ERROR: [%s]\n", switch_str_nil(err));
+				switch_channel_hangup(session->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+				goto end;
+			}
+		}
 	
 
-	
+	text_up:
 	video:
 	
 		if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) {
@@ -7076,10 +8060,8 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 
 					if (v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].ready) {
 
-						if (v_engine->rtcp_mux > 0 && v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].ready &&
-							!strcmp(v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_addr, 
-									v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_addr) && 
-							v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_port == v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_port) {
+						if (v_engine->rtcp_mux > 0 && !strcmp(v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_addr, v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_addr)
+							&& v_engine->ice_in.cands[v_engine->ice_in.chosen[1]][1].con_port == v_engine->ice_in.cands[v_engine->ice_in.chosen[0]][0].con_port) {
 							switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Skipping VIDEO RTCP ICE (Same as VIDEO RTP)\n");
 						} else {
 
@@ -7126,9 +8108,9 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_activate_rtp(switch_core_sessi
 					switch_core_media_parse_rtp_bugs(&v_engine->rtp_bugs, val);
 				}
 				
-				//if (switch_channel_test_flag(session->channel, CF_AVPF)) {
-					//smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS;
-				//}
+				if (switch_channel_test_flag(session->channel, CF_AVPF)) {
+					smh->mparams->manual_video_rtp_bugs = RTP_BUG_SEND_LINEAR_TIMESTAMPS;
+				}
 				
 				switch_rtp_intentional_bugs(v_engine->rtp_session, v_engine->rtp_bugs | smh->mparams->manual_video_rtp_bugs);
 
@@ -7655,7 +8637,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess
 	char *buf;
 	int ptime = 0;
 	uint32_t rate = 0;
-	uint32_t v_port;
+	uint32_t v_port, t_port;
 	int use_cng = 1;
 	const char *val;
 	const char *family;
@@ -7673,8 +8655,9 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess
 	//const char *local_audio_crypto_key = switch_core_session_local_crypto_key(session, SWITCH_MEDIA_TYPE_AUDIO);
 	const char *local_sdp_audio_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_AUDIO, SWITCH_TRUE);
 	const char *local_sdp_video_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_VIDEO, SWITCH_TRUE);
+	const char *local_sdp_text_zrtp_hash = switch_core_media_get_zrtp_hash(session, SWITCH_MEDIA_TYPE_TEXT, SWITCH_TRUE);
 	const char *tmp;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 	ice_t *ice_out;
 	//int vp8 = 0;
@@ -7693,6 +8676,7 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	if ((!a_engine->rtcp_mux && !v_engine->rtcp_mux) && 
 		(sdp_type == SDP_TYPE_REQUEST || switch_true(switch_channel_get_variable(session->channel, "rtcp_mux")))) {
@@ -8745,6 +9729,273 @@ SWITCH_DECLARE(void) switch_core_media_gen_local_sdp(switch_core_session_t *sess
 
 	}
 
+	///TEXT 
+
+	if (sdp_type == SDP_TYPE_RESPONSE && !switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE)) {
+		if (switch_channel_test_flag(session->channel, CF_TEXT_SDP_RECVD)) {
+			switch_channel_clear_flag(session->channel, CF_TEXT_SDP_RECVD);
+			switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "m=text 0 %s 19\r\n", 
+							get_media_profile_name(session, 
+												   (switch_channel_test_flag(session->channel, CF_SECURE) 
+													&& switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) || 
+												   a_engine->crypto_type != CRYPTO_INVALID || switch_channel_test_flag(session->channel, CF_DTLS)));
+		}
+	} else if ((switch_channel_test_flag(session->channel, CF_TEXT_POSSIBLE) || switch_channel_var_true(session->channel, "rtp_enable_text")) &&
+			   switch_channel_test_cap(session->channel, CC_RTP_RTT)) {
+		t_engine->t140_pt = 0;
+		t_engine->red_pt = 0;
+		
+
+		if (sdp_type == SDP_TYPE_REQUEST) {
+			t_engine->t140_pt = 96;
+			t_engine->red_pt = 97;
+
+			switch_core_media_add_payload_map(session,
+											  SWITCH_MEDIA_TYPE_TEXT,
+											  "red",
+											  NULL,
+											  NULL,
+											  SDP_TYPE_REQUEST,
+											  t_engine->red_pt,
+											  1000,
+											  0,
+											  1,
+											  SWITCH_TRUE);
+
+			switch_core_media_add_payload_map(session,
+											  SWITCH_MEDIA_TYPE_TEXT,
+											  "t140",
+											  NULL,
+											  NULL,
+											  SDP_TYPE_REQUEST,
+											  t_engine->t140_pt,
+											  1000,
+											  0,
+											  1,
+											  SWITCH_TRUE);
+
+			t_engine->codec_negotiated = 1;
+		}
+
+		if (switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_INBOUND) {
+			if (switch_channel_test_flag(smh->session->channel, CF_DTLS)) {
+				t_engine->no_crypto = 1;
+			}
+		}
+
+		
+		if (!t_engine->local_sdp_port) {
+			switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 0);
+		}
+
+		if ((t_port = t_engine->adv_sdp_port)) {
+			int loops;
+
+			for (loops = 0; loops < 2; loops++) {
+
+				if (switch_channel_test_flag(smh->session->channel, CF_ICE)) {
+					gen_ice(session, SWITCH_MEDIA_TYPE_TEXT, ip, (switch_port_t)t_port);
+				}
+
+
+				switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "m=text %d %s", 
+								t_port, 
+								get_media_profile_name(session, 
+													   (loops == 0 && switch_channel_test_flag(session->channel, CF_SECURE) 
+														&& switch_channel_direction(session->channel) == SWITCH_CALL_DIRECTION_OUTBOUND) || 
+													   a_engine->crypto_type != CRYPTO_INVALID || switch_channel_test_flag(session->channel, CF_DTLS)));
+			
+							
+				/*****************************/
+				if (t_engine->codec_negotiated) {
+					
+					switch_mutex_lock(smh->sdp_mutex);
+					for (pmap = t_engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) {
+
+						if (pmap->type != SWITCH_MEDIA_TYPE_TEXT || !pmap->negotiated) {
+							continue;
+						}
+
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), " %d", pmap->pt);
+						
+					}
+					switch_mutex_unlock(smh->sdp_mutex);
+					
+
+					//TEXT switch_core_media_set_text_codec(session, 0);
+					//switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), " %d", t_engine->cur_payload_map->agreed_pt);
+				}
+
+				switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "\r\n");
+
+				if (t_engine->codec_negotiated) {
+					switch_mutex_lock(smh->sdp_mutex);
+					for (pmap = t_engine->payload_map; pmap && pmap->allocated; pmap = pmap->next) {
+
+						if (pmap->type != SWITCH_MEDIA_TYPE_TEXT || !pmap->negotiated) {
+							continue;
+						}
+					
+						if (!strcasecmp(pmap->iananame, "t140")) {
+							t_engine->t140_pt = pmap->pt;
+						}
+
+						if (!strcasecmp(pmap->iananame, "red")) {
+							t_engine->red_pt = pmap->pt;
+						}
+						
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtpmap:%d %s/%ld\r\n",
+										pmap->pt, pmap->iananame, pmap->rate);
+						
+					}
+					switch_mutex_unlock(smh->sdp_mutex);
+
+
+					if (t_engine->t140_pt && t_engine->red_pt) {
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=fmtp:%d %d/%d/%d\r\n", t_engine->red_pt, t_engine->t140_pt, t_engine->t140_pt, t_engine->t140_pt);
+					}
+
+					
+					if (t_engine->smode == SWITCH_MEDIA_FLOW_SENDONLY) {
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "%s", "a=sendonly\r\n");
+					} else if (t_engine->smode == SWITCH_MEDIA_FLOW_RECVONLY) {
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "%s", "a=recvonly\r\n");
+					}
+
+				}
+
+				if ((is_outbound || switch_channel_test_flag(session->channel, CF_RECOVERING))
+					&& switch_channel_test_flag(smh->session->channel, CF_DTLS)) {
+					generate_local_fingerprint(smh, SWITCH_MEDIA_TYPE_TEXT);
+				}
+
+
+				if (!zstr(t_engine->local_dtls_fingerprint.type)) {
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=fingerprint:%s %s\na=setup:%s\r\n", t_engine->local_dtls_fingerprint.type, 
+									t_engine->local_dtls_fingerprint.str, get_setup(t_engine, session, sdp_type));
+				}
+
+
+				if (smh->mparams->rtcp_text_interval_msec) {
+					if (t_engine->rtcp_mux > 0) {
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp-mux\r\n");
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp:%d IN %s %s\r\n", t_port, family, ip);
+					} else {
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=rtcp:%d IN %s %s\r\n", t_port + 1, family, ip);
+					}
+				}
+
+				//switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u\r\n", t_engine->ssrc);
+				
+				if (t_engine->ice_out.cands[0][0].ready) {
+					char tmp1[11] = "";
+					char tmp2[11] = "";
+					uint32_t c1 = (2^24)*126 + (2^8)*65535 + (2^0)*(256 - 1);
+					//uint32_t c2 = (2^24)*126 + (2^8)*65535 + (2^0)*(256 - 2);
+					//uint32_t c3 = (2^24)*126 + (2^8)*65534 + (2^0)*(256 - 1);
+					//uint32_t c4 = (2^24)*126 + (2^8)*65534 + (2^0)*(256 - 2);
+
+					uint32_t c2 = c1 - 1;
+					uint32_t c3 = c1 - 2;
+					uint32_t c4 = c1 - 3;
+				
+					tmp1[10] = '\0';
+					tmp2[10] = '\0';
+					switch_stun_random_string(tmp1, 10, "0123456789");
+					switch_stun_random_string(tmp2, 10, "0123456789");
+
+					ice_out = &t_engine->ice_out;
+					
+					
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u cname:%s\r\n", t_engine->ssrc, smh->cname);
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u msid:%s v0\r\n", t_engine->ssrc, smh->msid);
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u mslabel:%s\r\n", t_engine->ssrc, smh->msid);
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ssrc:%u label:%sv0\r\n", t_engine->ssrc, smh->msid);
+				
+
+				
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-ufrag:%s\r\n", ice_out->ufrag);
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-pwd:%s\r\n", ice_out->pwd);
+
+
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 1 %s %u %s %d typ host generation 0\r\n", 
+									tmp1, ice_out->cands[0][0].transport, c1,
+									ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port
+									);
+
+					if (!zstr(t_engine->local_sdp_ip) && !zstr(ice_out->cands[0][0].con_addr) && 
+						strcmp(t_engine->local_sdp_ip, ice_out->cands[0][0].con_addr)
+						&& t_engine->local_sdp_port != ice_out->cands[0][0].con_port) {
+
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 1 %s %u %s %d typ srflx raddr %s rport %d generation 0\r\n", 
+										tmp2, ice_out->cands[0][0].transport, c3,
+										ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port,
+										t_engine->local_sdp_ip, t_engine->local_sdp_port
+										);
+					}
+
+
+					if (t_engine->rtcp_mux < 1 || is_outbound || switch_channel_test_flag(session->channel, CF_RECOVERING)) {
+
+						switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 2 %s %u %s %d typ host generation 0\r\n", 
+										tmp1, ice_out->cands[0][0].transport, c2,
+										ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port + (t_engine->rtcp_mux > 0 ? 0 : 1)
+										);
+					
+					
+						if (!zstr(t_engine->local_sdp_ip) && !zstr(ice_out->cands[0][1].con_addr) && 
+							strcmp(t_engine->local_sdp_ip, ice_out->cands[0][1].con_addr)
+							&& t_engine->local_sdp_port != ice_out->cands[0][1].con_port) {
+						
+							switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=candidate:%s 2 %s %u %s %d typ srflx generation 0\r\n", 
+											tmp2, ice_out->cands[0][0].transport, c4,
+											ice_out->cands[0][0].con_addr, ice_out->cands[0][0].con_port + (t_engine->rtcp_mux > 0 ? 0 : 1),
+											t_engine->local_sdp_ip, t_engine->local_sdp_port + (t_engine->rtcp_mux > 0 ? 0 : 1)
+											);
+						}
+					}
+
+			
+				
+#ifdef GOOGLE_ICE
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=ice-options:google-ice\r\n");
+#endif
+				}
+
+				
+
+				if (loops == 0 && switch_channel_test_flag(session->channel, CF_SECURE) && !switch_channel_test_flag(session->channel, CF_DTLS)) {
+					int i;
+				
+					for (i = 0; smh->crypto_suite_order[i] != CRYPTO_INVALID; i++) {
+						switch_rtp_crypto_key_type_t j = SUITES[smh->crypto_suite_order[i]].type;
+					
+						if ((t_engine->crypto_type == j || t_engine->crypto_type == CRYPTO_INVALID) && !zstr(t_engine->ssec[j].local_crypto_key)) {
+							switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=crypto:%s\r\n", t_engine->ssec[j].local_crypto_key);
+						}
+					}
+					//switch_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "a=encryption:optional\r\n");
+				}
+
+
+				if (local_sdp_text_zrtp_hash) {
+					switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "Adding text a=zrtp-hash:%s\n", local_sdp_text_zrtp_hash);
+					switch_snprintf(buf + strlen(buf), SDPBUFLEN - strlen(buf), "a=zrtp-hash:%s\r\n", local_sdp_text_zrtp_hash);
+				}
+
+
+				if (switch_channel_test_flag(session->channel, CF_DTLS) || 
+					!switch_channel_test_flag(session->channel, CF_SECURE) || 
+					smh->crypto_mode == CRYPTO_MODE_MANDATORY || smh->crypto_mode == CRYPTO_MODE_FORBIDDEN) {
+					break;
+				}
+			}
+		}
+
+	}
+	
+
+
 
 	if (map) {
 		switch_event_destroy(&map);
@@ -8965,13 +10216,14 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session)
 {
 	switch_size_t len;
 	char *p, *q, *pe, *qe;
-	int has_video = 0, has_audio = 0, has_ip = 0;
+	int has_video = 0, has_audio = 0, has_text = 0, has_ip = 0;
 	char port_buf[25] = "";
 	char vport_buf[25] = "";
+	char tport_buf[25] = "";
 	char *new_sdp;
 	int bad = 0;
 	switch_media_handle_t *smh;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	payload_map_t *pmap;
 
 	switch_assert(session);
@@ -8982,6 +10234,7 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session)
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	if (zstr(smh->mparams->local_sdp_str)) {
 		return;
@@ -9222,8 +10475,72 @@ SWITCH_DECLARE(void) switch_core_media_patch_sdp(switch_core_session_t *session)
 			}
 
 			has_video++;
+				} else if (!strncmp("m=text ", p, 8) && *(p + 8) != '0') {
+			if (!has_text) {
+				switch_core_media_choose_port(session, SWITCH_MEDIA_TYPE_TEXT, 1);
+				clear_pmaps(t_engine);
+				pmap = switch_core_media_add_payload_map(session,
+														 SWITCH_MEDIA_TYPE_TEXT,
+														 "PROXY-VID",
+														 NULL,
+														 NULL,
+														 SDP_TYPE_RESPONSE,
+														 0,
+														 90000,
+														 90000,
+														 1,
+														 SWITCH_TRUE);
+				t_engine->cur_payload_map = pmap;
+
+				switch_snprintf(tport_buf, sizeof(tport_buf), "%u", t_engine->adv_sdp_port);
+				
+				if (switch_channel_media_ready(session->channel) && !switch_rtp_ready(t_engine->rtp_session)) {
+					switch_channel_set_flag(session->channel, CF_TEXT_POSSIBLE);
+					switch_channel_set_flag(session->channel, CF_REINVITE);
+					switch_core_media_activate_rtp(session);
+				}
+
+				t_engine->codec_negotiated = 1;
+				//TEXT switch_core_media_set_text_codec(session, SWITCH_FALSE);
+			}
+
+			strncpy(q, p, 8);
+			p += 8;
+
+			if (p >= pe) {
+				bad = 8;
+				goto end;
+			}
+
+			q += 8;
+
+			if (q >= qe) {
+				bad = 9;
+				goto end;
+			}
+
+			strncpy(q, tport_buf, strlen(tport_buf));
+			q += strlen(tport_buf);
+
+			if (q >= qe) {
+				bad = 10;
+				goto end;
+			}
+
+			while (p && *p && (*p >= '0' && *p <= '9')) {
+
+				if (p >= pe) {
+					bad = 11;
+					goto end;
+				}
+
+				p++;
+			}
+
+			has_text++;
 		}
 
+
 		while (p && *p && *p != '\n') {
 
 			if (p >= pe) {
@@ -9450,7 +10767,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_reset_jb(switch_core_session_t
 SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_session_t *session, switch_core_session_message_t *msg)
 {
 	switch_media_handle_t *smh;
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine;//, *t_engine;
 	switch_status_t status = SWITCH_STATUS_SUCCESS;
 
 	switch_assert(session);
@@ -9465,6 +10782,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_receive_message(switch_core_se
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	//t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	switch (msg->message_id) {
 
@@ -10515,7 +11833,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_codec_chosen(switch_core_sessi
 
 SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *session)
 {
-	switch_rtp_engine_t *a_engine, *v_engine;
+	switch_rtp_engine_t *a_engine, *v_engine, *t_engine;
 	switch_media_handle_t *smh;
 	int type;
 
@@ -10527,6 +11845,7 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi
 
 	a_engine = &smh->engines[SWITCH_MEDIA_TYPE_AUDIO];
 	v_engine = &smh->engines[SWITCH_MEDIA_TYPE_VIDEO];
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
 
 	if (switch_core_codec_ready(&v_engine->read_codec)) {
 		type = 1;
@@ -10546,6 +11865,10 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi
 		switch_rtp_reset(v_engine->rtp_session);
 	}
 
+	if (t_engine->rtp_session) {
+		switch_rtp_reset(t_engine->rtp_session);
+	}
+
 
 	smh->msid = NULL;
 	smh->cname = NULL;
@@ -10554,6 +11877,11 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi
 	v_engine->ice_out.cands[0][0].foundation = NULL;
 	v_engine->ice_out.cands[0][0].component_id = 0;
 
+	t_engine->ice_out.ufrag = NULL;
+	t_engine->ice_out.pwd = NULL;
+	t_engine->ice_out.cands[0][0].foundation = NULL;
+	t_engine->ice_out.cands[0][0].component_id = 0;
+
 
 	a_engine->ice_out.ufrag = NULL;
 	a_engine->ice_out.pwd = NULL;
@@ -10564,6 +11892,10 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi
 		gen_ice(smh->session, SWITCH_MEDIA_TYPE_VIDEO, NULL, 0);
 	}
 
+	if (t_engine->ice_in.cands[t_engine->ice_in.chosen[0]][0].ready) {
+		gen_ice(smh->session, SWITCH_MEDIA_TYPE_TEXT, NULL, 0);
+	}
+
 	if (a_engine->ice_in.cands[a_engine->ice_in.chosen[0]][0].ready) {
 		gen_ice(smh->session, SWITCH_MEDIA_TYPE_AUDIO, NULL, 0);
 	}
@@ -10573,9 +11905,11 @@ SWITCH_DECLARE(void) switch_core_session_stop_media(switch_core_session_t *sessi
 	
 	a_engine->local_dtls_fingerprint.len = 0;
 	v_engine->local_dtls_fingerprint.len = 0;
+	t_engine->local_dtls_fingerprint.len = 0;
 
 	a_engine->remote_ssrc = 0;
 	v_engine->remote_ssrc = 0;
+	t_engine->remote_ssrc = 0;
 
 	switch_channel_clear_flag(smh->session->channel, CF_VIDEO_READY);
 	switch_core_session_wake_video_thread(smh->session);
@@ -11725,7 +13059,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_read_video_frame(switch_core
 		}
 	}
 
-	if ((*frame) && (*frame)->codec) {
+	if ((*frame)->codec) {
 		if (patchers) {
 			switch_set_flag((*frame)->codec, SWITCH_CODEC_FLAG_VIDEO_PATCHING);
 		} else {
@@ -11740,6 +13074,7 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_read_video_frame(switch_core
 	return status;
 }
 
+
 SWITCH_DECLARE(switch_status_t) switch_core_session_set_video_read_callback(switch_core_session_t *session, 
 																			switch_core_video_thread_callback_func_t func, void *user_data)
 {
@@ -11788,6 +13123,500 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_video_read_callback(switch_c
 }
 
 
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_set_text_read_callback(switch_core_session_t *session, 
+																			switch_core_text_thread_callback_func_t func, void *user_data)
+{
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+	switch_media_handle_t *smh;
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	switch_mutex_lock(smh->control_mutex);
+	if (!func) {
+		session->text_read_callback = NULL;
+		session->text_read_user_data = NULL;
+	} else if (session->text_read_callback) {
+		status = SWITCH_STATUS_FALSE;
+	} else {
+		session->text_read_callback = func;
+		session->text_read_user_data = user_data;
+	}
+
+	switch_core_session_start_text_thread(session);
+	switch_mutex_unlock(smh->control_mutex);
+
+	return status;
+}
+
+
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_text_read_callback(switch_core_session_t *session, switch_frame_t *frame)
+{
+	switch_media_handle_t *smh;
+	switch_status_t status = SWITCH_STATUS_CONTINUE;
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	switch_mutex_lock(smh->control_mutex);
+
+	if (session->text_read_callback) {
+		status = session->text_read_callback(session, frame, session->text_read_user_data);
+	}
+
+	switch_mutex_unlock(smh->control_mutex);
+
+	return status;
+}
+
+
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_read_text_frame(switch_core_session_t *session, switch_frame_t **frame, switch_io_flag_t flags,
+																	 int stream_id)
+{
+	switch_status_t status = SWITCH_STATUS_FALSE;
+	switch_io_event_hook_text_read_frame_t *ptr;
+	switch_media_handle_t *smh;
+	switch_io_read_text_frame_t read_text_frame = NULL;
+	switch_time_t now;
+
+	switch_assert(session != NULL);
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	if (switch_channel_down_nosig(session->channel)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	if (!(read_text_frame = session->endpoint_interface->io_routines->read_text_frame)) {
+		if (session->io_override) {
+			read_text_frame = session->io_override->read_text_frame;
+		}
+	}
+
+	if (read_text_frame) {
+		if ((status = read_text_frame(session, frame, flags, stream_id)) == SWITCH_STATUS_SUCCESS) {
+			for (ptr = session->event_hooks.text_read_frame; ptr; ptr = ptr->next) {
+				if ((status = ptr->text_read_frame(session, frame, flags, stream_id)) != SWITCH_STATUS_SUCCESS) {
+					break;
+				}
+			}
+		}
+	}
+
+	if (status == SWITCH_STATUS_INUSE) {
+		*frame = &runtime.dummy_cng_frame;
+		switch_cond_next();
+		return SWITCH_STATUS_SUCCESS;
+	}
+
+	if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) {
+		goto done;
+	}
+
+	if (!(*frame)) {
+		goto done;
+	}
+	
+	now = switch_micro_time_now();
+
+	if (switch_test_flag((*frame), SFF_CNG)) {
+		if (smh->last_text_frame && now - smh->last_text_frame > TEXT_PERIOD_TIMEOUT * 1000) {
+			switch_channel_set_flag(session->channel, CF_TEXT_IDLE);
+			switch_channel_clear_flag(session->channel, CF_TEXT_ACTIVE);
+			smh->last_text_frame = 0;
+		}
+	} else {
+		unsigned char *p = (*frame)->data;
+
+		smh->last_text_frame = now;
+		switch_channel_set_flag(session->channel, CF_TEXT_ACTIVE);
+		switch_channel_clear_flag(session->channel, CF_TEXT_IDLE);
+
+		while(p && *p) {
+			if (*p == '\r' || *p == '\n') {
+				switch_set_flag((*frame), SFF_TEXT_LINE_BREAK);
+				break;
+			}
+			
+			if (*p == 0xE2 && *(p+1) == 0x80 && *(p+2) == 0xA8) {
+				switch_set_flag((*frame), SFF_TEXT_LINE_BREAK);
+				break;
+			}
+			
+			p++;
+		}
+	}
+	
+	if ((*frame)->data && (*frame)->datalen && !((*frame)->flags & SFF_CNG)) {
+		if (!session->text_buffer) {
+			switch_mutex_init(&session->text_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
+			switch_buffer_create_dynamic(&session->text_buffer, 512, 1024, 0);
+		}
+		switch_buffer_write(session->text_buffer, (*frame)->data, (*frame)->datalen);
+	}
+
+	if (session->bugs) {
+		switch_media_bug_t *bp;
+		int prune = 0;
+
+		switch_thread_rwlock_rdlock(session->bug_rwlock);
+		for (bp = session->bugs; bp; bp = bp->next) {
+			switch_bool_t ok = SWITCH_TRUE;
+
+			if (switch_channel_test_flag(session->channel, CF_PAUSE_BUGS) && !switch_core_media_bug_test_flag(bp, SMBF_NO_PAUSE)) {
+				continue;
+			}
+
+			if (!switch_channel_test_flag(session->channel, CF_ANSWERED) && switch_core_media_bug_test_flag(bp, SMBF_ANSWER_REQ)) {
+				continue;
+			}
+
+			if (switch_test_flag(bp, SMBF_PRUNE)) {
+				prune++;
+				continue;
+			}
+
+			if (bp->ready && switch_test_flag(bp, SMBF_READ_TEXT_STREAM)) {
+				int bytes = 0;
+
+				if ((*frame)) {
+					switch_size_t inuse = 0;				
+
+					if ((*frame)->data && (*frame)->datalen && !((*frame)->flags & SFF_CNG)) {
+						switch_mutex_lock(session->text_mutex);
+						switch_buffer_write(bp->text_buffer, (char *)(*frame)->data, (*frame)->datalen);
+						switch_mutex_unlock(session->text_mutex);
+					}
+
+					inuse = switch_buffer_inuse(bp->text_buffer);
+
+					if (zstr(bp->text_framedata) && inuse && 
+						(switch_channel_test_flag(session->channel, CF_TEXT_IDLE) || switch_test_flag((*frame), SFF_TEXT_LINE_BREAK))) {
+
+						if (inuse + 1 > bp->text_framesize) {
+							void *tmp = malloc(inuse + 1024);
+							memcpy(tmp, bp->text_framedata, bp->text_framesize);
+							
+							switch_assert(tmp);
+							
+							bp->text_framesize = inuse + 1024;
+							
+							free(bp->text_framedata);
+							bp->text_framedata = tmp;
+							
+						}
+					
+
+						bytes = switch_buffer_read(bp->text_buffer, bp->text_framedata, inuse);
+						*(bp->text_framedata + bytes) = '\0'; 
+
+						ok = bp->callback(bp, bp->user_data, SWITCH_ABC_TYPE_READ_TEXT);
+						bp->text_framedata[0] = '\0';
+					} else ok = SWITCH_TRUE;
+				}
+			}
+			
+			if (ok == SWITCH_FALSE) {
+				switch_set_flag(bp, SMBF_PRUNE);
+				prune++;
+			}
+		}
+		
+		switch_thread_rwlock_unlock(session->bug_rwlock);
+
+		if (prune) {
+			switch_core_media_bug_prune(session);
+		}
+	}
+
+	if (status == SWITCH_STATUS_SUCCESS || status == SWITCH_STATUS_BREAK) {
+		switch_core_session_text_read_callback(session, *frame);
+	}
+
+ done:
+
+	return status;
+}
+
+
+static void build_red_packet(switch_rtp_engine_t *t_engine)
+{
+	int pos;
+	switch_frame_t *frame = &t_engine->tf->text_write_frame;
+	switch_byte_t *buf = (switch_byte_t *) frame->data;
+	uint32_t plen = 0, loops = 0;
+	uint16_t *u16;
+
+	pos = t_engine->tf->red_pos + 1;
+	
+	if (pos == t_engine->tf->red_max) pos = 0;
+
+	for (;;) {
+		uint16_t ts = frame->timestamp - t_engine->tf->red_ts[pos];
+		uint16_t len = t_engine->tf->red_buflen[pos];
+
+		loops++;
+
+		//1
+		*buf = t_engine->t140_pt & 0x7f;
+
+		if (pos != t_engine->tf->red_pos) {
+			*buf |= 0x80;
+
+			buf++; //2
+			u16 = (uint16_t *) buf;
+			*u16 = htons(ts << 2);
+			buf++;//3
+			*buf += (len & 0x300) >> 8;
+			buf++;//4
+			*buf = len & 0xff;			
+		}
+
+		buf++;
+
+		if (pos == t_engine->tf->red_pos) break;
+		
+
+		pos++;
+
+		if (pos == t_engine->tf->red_max) pos = 0;
+	}
+
+
+	plen = ((loops - 1) * 4) + 1;
+
+
+	pos = t_engine->tf->red_pos + 1;
+	
+	if (pos == t_engine->tf->red_max) pos = 0;
+
+	for (;;) {
+		if (t_engine->tf->red_buflen[pos]) {
+			memcpy(buf, t_engine->tf->red_buf[pos], t_engine->tf->red_buflen[pos]);
+			plen += t_engine->tf->red_buflen[pos];
+			buf += t_engine->tf->red_buflen[pos];
+		}
+
+		if (pos == t_engine->tf->red_pos) break;
+
+		pos++;
+
+		if (pos == t_engine->tf->red_max) pos = 0;
+	}
+
+
+	buf = frame->data;
+	*(buf+plen) = '\0';
+
+	frame->datalen = plen;
+	frame->payload = t_engine->red_pt;
+}
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_write_text_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags,
+																	  int stream_id)
+{
+	switch_status_t status = SWITCH_STATUS_FALSE;
+	switch_media_handle_t *smh;
+	switch_io_event_hook_text_write_frame_t *ptr;
+	switch_rtp_engine_t *t_engine;
+	switch_io_write_text_frame_t write_text_frame = NULL;
+
+	switch_assert(session);
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}
+
+	if (switch_channel_down(session->channel)) {
+		return SWITCH_STATUS_FALSE;
+	}
+	
+	if (switch_core_session_media_flow(session, SWITCH_MEDIA_TYPE_TEXT) == SWITCH_MEDIA_FLOW_RECVONLY || switch_core_session_media_flow(session, SWITCH_MEDIA_TYPE_TEXT) == SWITCH_MEDIA_FLOW_INACTIVE) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG3, "Writing text to RECVONLY/INACTIVE session\n");
+		return SWITCH_STATUS_SUCCESS;
+	}
+
+	//if (switch_channel_test_flag(session->channel, CF_TEXT_PAUSE_WRITE)) {
+	//	return SWITCH_STATUS_SUCCESS;
+	//}
+
+	if (smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT] && switch_mutex_trylock(smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]) != SWITCH_STATUS_SUCCESS) {
+		/* return CNG, another thread is already writing  */
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG1, "%s is already being written to for %s\n", 
+						  switch_channel_get_name(session->channel), type2str(SWITCH_MEDIA_TYPE_TEXT));
+		goto done;
+	}
+
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+
+	if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) {
+
+		if (frame) {
+			char *str = (char *) frame->data;
+			switch_buffer_write(t_engine->tf->write_buffer, str, frame->datalen);
+		}
+
+		if (!switch_buffer_inuse(t_engine->tf->write_buffer)) {
+			t_engine->tf->write_empty++;
+			return SWITCH_STATUS_BREAK;
+		}
+
+		frame = &t_engine->tf->text_write_frame;
+		switch_core_timer_sync(&t_engine->tf->timer);
+		frame->timestamp = t_engine->tf->timer.samplecount;
+
+		if (t_engine->red_pt) {
+			t_engine->tf->red_ts[t_engine->tf->red_pos] = frame->timestamp;
+
+			if (t_engine->tf->write_empty > TEXT_PERIOD_TIMEOUT / TEXT_TIMER_MS) {
+				int pos;
+			
+				for(pos = 0; pos < t_engine->tf->red_max; pos++) {
+					t_engine->tf->red_ts[pos] = 0;
+					t_engine->tf->red_buf[pos][0] = '\0';
+					t_engine->tf->red_buflen[pos] = 0;
+				}
+
+				frame->m = 1;
+				t_engine->tf->write_empty = 0;
+			
+			} else {
+				frame->m = 0;
+			}
+
+			t_engine->tf->red_buflen[t_engine->tf->red_pos] = 
+				switch_buffer_read(t_engine->tf->write_buffer, t_engine->tf->red_buf[t_engine->tf->red_pos], RED_PACKET_SIZE);
+
+			*(t_engine->tf->red_buf[t_engine->tf->red_pos] + t_engine->tf->red_buflen[t_engine->tf->red_pos]) = '\0';
+
+			build_red_packet(t_engine);
+		
+		
+		} else {
+			frame->datalen = switch_buffer_read(t_engine->tf->write_buffer, t_engine->tf->text_write_frame.data, RED_PACKET_SIZE);
+			frame->payload = t_engine->t140_pt;
+		}
+	}
+
+	if (!(write_text_frame = session->endpoint_interface->io_routines->write_text_frame)) {
+		if (session->io_override) {
+			write_text_frame = session->io_override->write_text_frame;
+		}
+	}
+
+	if (write_text_frame) {
+		if ((status = write_text_frame(session, frame, flags, stream_id)) == SWITCH_STATUS_SUCCESS) {
+			for (ptr = session->event_hooks.text_write_frame; ptr; ptr = ptr->next) {
+				if ((status = ptr->text_write_frame(session, frame, flags, stream_id)) != SWITCH_STATUS_SUCCESS) {
+					break;
+				}
+			}
+		}
+	}
+
+
+	if (switch_channel_test_cap(session->channel, CC_RTP_RTT)) {
+		if (t_engine->red_pt) {
+			t_engine->tf->red_pos++;
+			if (t_engine->tf->red_pos == t_engine->tf->red_max) {
+				t_engine->tf->red_pos = 0;
+			}
+		}
+	}
+
+ done:
+
+	if (smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]) {
+		switch_mutex_unlock(smh->write_mutex[SWITCH_MEDIA_TYPE_TEXT]);
+	}
+
+	return status;
+		}
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_printf(switch_core_session_t *session, const char *fmt, ...)
+{
+	char *data = NULL;
+	int ret = 0;
+	va_list ap;
+	switch_frame_t frame = { 0 };
+	switch_rtp_engine_t *t_engine;
+	switch_media_handle_t *smh;
+	unsigned char CR[] = TEXT_UNICODE_LINEFEED;
+
+	if (!(smh = session->media_handle)) {
+		return SWITCH_STATUS_FALSE;
+	}               
+
+	t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+	
+	if (!switch_rtp_ready(t_engine->rtp_session)) {
+		return SWITCH_STATUS_NOTIMPL;
+	}
+
+
+	va_start(ap, fmt);
+	ret = switch_vasprintf(&data, fmt, ap);
+	va_end(ap);
+
+	if (ret == -1) {
+		abort();
+	}
+
+
+	frame.data = data;
+	frame.datalen = strlen(data);
+	
+	switch_core_session_write_text_frame(session, &frame, 0, 0);
+
+	frame.data = CR;
+	frame.datalen = 3;
+	
+	switch_core_session_write_text_frame(session, &frame, 0, 0);
+
+	switch_safe_free(data);
+
+	return SWITCH_STATUS_SUCCESS;
+}
+
+
+SWITCH_DECLARE(switch_status_t) switch_core_session_print(switch_core_session_t *session, const char *data)
+{
+	switch_frame_t frame = { 0 };
+	//switch_rtp_engine_t *t_engine;
+	//switch_media_handle_t *smh;
+
+	//if (!(smh = session->media_handle)) {
+	//	return SWITCH_STATUS_FALSE;
+	//}               
+
+	//t_engine = &smh->engines[SWITCH_MEDIA_TYPE_TEXT];
+	
+	//if (!switch_rtp_ready(t_engine->rtp_session)) {
+	//	return SWITCH_STATUS_NOTIMPL;
+	//}
+
+
+	if (!switch_channel_test_flag(session->channel, CF_TEXT)) {
+		return SWITCH_STATUS_NOTIMPL;
+	}
+
+	frame.data = (char *) data;
+	frame.datalen = strlen(data);
+	
+	switch_core_session_write_text_frame(session, &frame, 0, 0);
+
+	return SWITCH_STATUS_SUCCESS;
+}
+
+
+
 /* For Emacs:
  * Local Variables:
  * mode:c
diff --git a/src/switch_core_media_bug.c b/src/switch_core_media_bug.c
index e50eb3df57..b5f6a86549 100644
--- a/src/switch_core_media_bug.c
+++ b/src/switch_core_media_bug.c
@@ -88,6 +88,11 @@ SWITCH_DECLARE(switch_core_session_t *) switch_core_media_bug_get_session(switch
 	return bug->session;
 }
 
+SWITCH_DECLARE(const char *) switch_core_media_bug_get_text(switch_media_bug_t *bug)
+{
+	return bug->text_framedata;
+}
+
 SWITCH_DECLARE(switch_frame_t *) switch_core_media_bug_get_video_ping_frame(switch_media_bug_t *bug)
 {
 	return bug->video_ping_frame;
@@ -791,6 +796,14 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_add(switch_core_session_t
 		switch_queue_create(&bug->spy_video_queue[1], SWITCH_CORE_QUEUE_LEN, switch_core_session_get_pool(session));
 	}
 
+	if ((switch_test_flag(bug, SMBF_READ_TEXT_STREAM))) {
+
+		switch_buffer_create_dynamic(&bug->text_buffer, 512, 1024, 0);
+		switch_zmalloc(bug->text_framedata, 1024);
+		bug->text_framesize = 1024;
+		
+	}
+
 	if ((switch_test_flag(bug, SMBF_READ_VIDEO_STREAM) || switch_test_flag(bug, SMBF_WRITE_VIDEO_STREAM))) {
 		switch_memory_pool_t *pool = switch_core_session_get_pool(session);
 
@@ -1150,6 +1163,11 @@ SWITCH_DECLARE(switch_status_t) switch_core_media_bug_close(switch_media_bug_t *
 			bp->callback(bp, bp->user_data, SWITCH_ABC_TYPE_CLOSE);
 		}
 
+		if (bp->text_buffer) {
+			switch_buffer_destroy(&bp->text_buffer);
+			switch_safe_free(bp->text_framedata);
+		}
+
 		if (switch_test_flag(bp, SMBF_READ_VIDEO_STREAM) || switch_test_flag(bp, SMBF_WRITE_VIDEO_STREAM) || switch_test_flag(bp, SMBF_READ_VIDEO_PING) || switch_test_flag(bp, SMBF_WRITE_VIDEO_PING)) {
 			switch_channel_clear_flag_recursive(bp->session->channel, CF_VIDEO_DECODED_READ);
 		}
diff --git a/src/switch_core_session.c b/src/switch_core_session.c
index aa0f92f222..8433308609 100644
--- a/src/switch_core_session.c
+++ b/src/switch_core_session.c
@@ -1469,6 +1469,10 @@ SWITCH_DECLARE(void) switch_core_session_perform_destroy(switch_core_session_t *
 					  switch_channel_get_name((*session)->channel), switch_channel_state_name(switch_channel_get_state((*session)->channel)));
 
 
+	if ((*session)->text_buffer) {
+		switch_buffer_destroy(&(*session)->text_buffer);
+	}
+
 	switch_core_session_reset(*session, SWITCH_TRUE, SWITCH_TRUE);
 
 	switch_core_media_bug_remove_all(*session);
@@ -1880,6 +1884,19 @@ SWITCH_DECLARE(switch_status_t) switch_core_session_thread_launch(switch_core_se
 	return status;
 }
 
+SWITCH_DECLARE(const char *) switch_core_session_get_text_buffer(switch_core_session_t *session)
+{
+	const char *buf = NULL;
+	
+	if (session->text_buffer) {
+		switch_mutex_lock(session->text_mutex);
+		buf = (const char *)switch_core_session_strdup(session, (const char *) switch_buffer_get_head_pointer(session->text_buffer));
+		switch_mutex_unlock(session->text_mutex);
+	}
+
+	return buf;
+}
+
 SWITCH_DECLARE(void) switch_core_session_launch_thread(switch_core_session_t *session, switch_thread_start_t func, void *obj)
 {
 	switch_thread_t *thread;
@@ -2945,6 +2962,17 @@ SWITCH_DECLARE(void) switch_core_session_raw_read(switch_core_session_t *session
 	switch_core_session_set_codec_slin(session, session->sdata);
 }
 
+SWITCH_DECLARE(switch_status_t) switch_core_session_override_io_routines(switch_core_session_t *session, switch_io_routines_t *ior)
+{
+	if (session->endpoint_interface && switch_channel_test_cap(session->channel, CC_IO_OVERRIDE)) {
+		session->io_override = ior;
+		return SWITCH_STATUS_SUCCESS;
+	}
+
+	return SWITCH_STATUS_FALSE;
+}
+
+
 /* For Emacs:
  * Local Variables:
  * mode:c
diff --git a/src/switch_event.c b/src/switch_event.c
index 4ebe0f37d5..138621a758 100644
--- a/src/switch_event.c
+++ b/src/switch_event.c
@@ -219,6 +219,7 @@ static char *EVENT_NAMES[] = {
 	"CALL_SETUP_RESULT",
 	"CALL_DETAIL",
 	"DEVICE_STATE",
+	"REAL_TIME_TEXT",
 	"ALL"
 };
 
diff --git a/src/switch_ivr.c b/src/switch_ivr.c
index 54ec0ff6a3..bed71c97c6 100644
--- a/src/switch_ivr.c
+++ b/src/switch_ivr.c
@@ -2728,7 +2728,8 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_generate_xml_cdr(switch_core_session_
 	char tmp[512], *f;
 	int cdr_off = 0, v_off = 0, cd_off = 0;
 	switch_hold_record_t *hold_record = switch_channel_get_hold_record(channel), *hr;
-	
+	const char *text_buffer = NULL;
+
 	if (*xml_cdr) {
 		cdr = *xml_cdr;
 	} else {
@@ -2750,6 +2751,12 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_generate_xml_cdr(switch_core_session_
 	x_field = switch_xml_add_child_d(x_channel_data, "direction", cd_off++);
 	switch_xml_set_txt_d(x_field, switch_channel_direction(channel) == SWITCH_CALL_DIRECTION_OUTBOUND ? "outbound" : "inbound");
 
+
+	if ((text_buffer = switch_core_session_get_text_buffer(session))) {
+		x_field = switch_xml_add_child_d(x_channel_data, "textlog", cd_off++);
+		switch_xml_set_txt_d(x_field, text_buffer);
+	}
+
 	x_field = switch_xml_add_child_d(x_channel_data, "state_number", cd_off++);
 	switch_snprintf(tmp, sizeof(tmp), "%d", switch_channel_get_state(channel));
 	switch_xml_set_txt_d(x_field, tmp);
diff --git a/src/switch_ivr_async.c b/src/switch_ivr_async.c
index e52884fd99..8df35b5a0b 100644
--- a/src/switch_ivr_async.c
+++ b/src/switch_ivr_async.c
@@ -663,6 +663,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_session_echo(switch_core_session_t *s
 	}
 
 	switch_channel_set_flag(channel, CF_VIDEO_ECHO);
+	switch_channel_set_flag(channel, CF_TEXT_ECHO);
 
 	while (switch_channel_ready(channel)) {
 		status = switch_core_session_read_frame(session, &read_frame, SWITCH_IO_FLAG_NONE, 0);
@@ -717,6 +718,7 @@ SWITCH_DECLARE(switch_status_t) switch_ivr_session_echo(switch_core_session_t *s
 
 	switch_core_session_video_reset(session);
 	switch_core_session_reset(session, SWITCH_TRUE, SWITCH_TRUE);
+	switch_channel_clear_flag(channel, CF_TEXT_ECHO);
 
 	return SWITCH_STATUS_SUCCESS;
 }
@@ -1525,6 +1527,84 @@ static switch_bool_t record_callback(switch_media_bug_t *bug, void *user_data, s
 	return SWITCH_TRUE;
 }
 
+
+static switch_bool_t text_callback(switch_media_bug_t *bug, void *user_data, switch_abc_type_t type)
+{
+
+	switch (type) {
+	case SWITCH_ABC_TYPE_READ_TEXT:
+		{
+			const char *text = switch_core_media_bug_get_text(bug);
+			
+
+			if (!zstr(text)) {
+				switch_event_t *event = NULL;
+				switch_core_session_t *session = switch_core_media_bug_get_session(bug);
+				//switch_channel_t *channel = switch_core_session_get_channel(session);
+
+				if (switch_event_create(&event, SWITCH_EVENT_REAL_TIME_TEXT) == SWITCH_STATUS_SUCCESS) {
+					switch_event_add_body(event, text, SWITCH_VA_NONE);
+					
+					if (switch_true(switch_core_get_variable("fire_text_events"))) {
+						switch_event_t *clone = NULL;
+
+						switch_event_dup(&clone, event);
+						switch_event_fire(&clone);
+					}
+					
+					switch_core_session_queue_event(session, &event);
+				}
+			}
+		}
+		break;
+	default:
+		break;
+	}
+
+	return SWITCH_TRUE;
+}
+
+SWITCH_DECLARE(switch_status_t) switch_ivr_capture_text(switch_core_session_t *session, switch_bool_t on)
+{
+	switch_media_bug_t *bug;
+	switch_channel_t *channel = switch_core_session_get_channel(session);
+
+	bug = (switch_media_bug_t *) switch_channel_get_private(channel, "capture_text");
+
+	if (on) {
+
+		if (bug) {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "text bug already attached\n");
+			return SWITCH_STATUS_FALSE;
+		}
+
+
+		if (switch_core_media_bug_add(session, "capture_text", switch_core_session_get_uuid(session),
+									  text_callback, NULL, 0,
+									  SMBF_READ_TEXT_STREAM,
+									  &bug) != SWITCH_STATUS_SUCCESS) {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Cannot attach bug\n");
+			return SWITCH_STATUS_FALSE;
+		}
+
+		switch_channel_set_private(channel, "capture_text", bug);
+		return SWITCH_STATUS_SUCCESS;
+
+	} else {
+
+		if (bug) {
+			switch_channel_set_private(channel, "capture_text", NULL);
+			switch_core_media_bug_remove(session, &bug);
+			return SWITCH_STATUS_SUCCESS;
+		} else {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "text bug not attached\n");
+			return SWITCH_STATUS_FALSE;
+		}
+
+	}
+}
+
+
 SWITCH_DECLARE(switch_status_t) switch_ivr_record_session_mask(switch_core_session_t *session, const char *file, switch_bool_t on)
 {
 	switch_media_bug_t *bug;
diff --git a/src/switch_ivr_bridge.c b/src/switch_ivr_bridge.c
index 62d5daf600..735e0efc91 100644
--- a/src/switch_ivr_bridge.c
+++ b/src/switch_ivr_bridge.c
@@ -39,13 +39,39 @@ static void cleanup_proxy_mode_b(switch_core_session_t *session);
 /* Bridge Related Stuff*/
 /*********************************************************************************/
 
-#ifdef SWITCH_VIDEO_IN_THREADS
 struct vid_helper {
 	switch_core_session_t *session_a;
 	switch_core_session_t *session_b;
 	int up;
 };
 
+
+static void text_bridge_thread(switch_core_session_t *session, void *obj)
+{
+	struct vid_helper *vh = obj;
+	switch_status_t status;
+	switch_frame_t *read_frame = 0;
+	switch_channel_t *channel = switch_core_session_get_channel(vh->session_a);
+	switch_channel_t *b_channel = switch_core_session_get_channel(vh->session_b);
+
+	vh->up = 1;
+
+	while (switch_channel_up_nosig(channel) && switch_channel_up_nosig(b_channel) && vh->up == 1) {
+		status = switch_core_session_read_text_frame(vh->session_a, &read_frame, SWITCH_IO_FLAG_NONE, 0);
+
+		if (SWITCH_READ_ACCEPTABLE(status) && !switch_test_flag(read_frame, SFF_CNG)) {
+			switch_core_session_write_text_frame(vh->session_b, read_frame, 0, 0);
+		}
+
+		switch_core_session_write_text_frame(vh->session_a, NULL, 0, 0);
+	}
+
+	vh->up = 0;
+}
+
+
+#ifdef SWITCH_VIDEO_IN_THREADS
+
 static void video_bridge_thread(switch_core_session_t *session, void *obj)
 {
 	struct vid_helper *vh = obj;
@@ -223,7 +249,7 @@ static void video_bridge_thread(switch_core_session_t *session, void *obj)
 
 static void launch_video(struct vid_helper *vh)
 {
-	switch_core_media_start_video_function(vh->session_a, video_bridge_thread, vh);
+	switch_core_media_start_engine_function(vh->session_a, SWITCH_MEDIA_TYPE_VIDEO, video_bridge_thread, vh);
 }
 #endif
 
@@ -334,6 +360,8 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 	const char *exec_app = NULL;
 	const char *exec_data = NULL;
 	switch_codec_implementation_t read_impl = { 0 };
+	uint32_t txt_launch = 0;
+	struct vid_helper th = { 0 };
 
 #ifdef SWITCH_VIDEO_IN_THREADS
 	struct vid_helper vh = { 0 };
@@ -449,6 +477,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 
 	bridge_filter_dtmf = switch_true(switch_channel_get_variable(chan_a, "bridge_filter_dtmf"));
 
+
 	for (;;) {
 		switch_channel_state_t b_state;
 		switch_status_t status;
@@ -512,6 +541,16 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 			}
 			continue;
 		}
+
+		if (switch_channel_test_flag(chan_a, CF_TEXT) && switch_channel_test_flag(chan_b, CF_TEXT) && !txt_launch) {
+			txt_launch++;
+			
+			th.session_a = session_a;
+			th.session_b = session_b;
+			switch_core_media_start_engine_function(th.session_a, SWITCH_MEDIA_TYPE_TEXT, text_bridge_thread, &th);
+
+		}
+
 #ifdef SWITCH_VIDEO_IN_THREADS
 		if (switch_channel_test_flag(chan_a, CF_VIDEO) && switch_channel_test_flag(chan_b, CF_VIDEO) && !vid_launch) {
 			vid_launch++;
@@ -716,6 +755,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 
   end_of_bridge_loop:
 
+
 #ifdef SWITCH_VIDEO_IN_THREADS
 	if (vh.up > 0) {
 		vh.up = -1;
@@ -760,8 +800,18 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 
   end:
 
+
+	if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT)) {
+		if (th.up == 1) {
+			th.up = -1;
+		}
+
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending text thread.\n");
+		switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_TEXT);
+	}
+
 #ifdef SWITCH_VIDEO_IN_THREADS
-	if (switch_core_media_check_video_function(session_a)) {
+	if (switch_core_media_check_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO)) {
 		if (vh.up == 1) {
 			vh.up = -1;
 		}
@@ -772,7 +822,7 @@ static void *audio_bridge_thread(switch_thread_t *thread, void *obj)
 		switch_core_session_kill_channel(session_b, SWITCH_SIG_BREAK);
 
 		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session_a), SWITCH_LOG_DEBUG, "Ending video thread.\n");
-		switch_core_media_end_video_function(session_a);
+		switch_core_media_end_engine_function(session_a, SWITCH_MEDIA_TYPE_VIDEO);
 		switch_channel_clear_flag(chan_a, CF_NOT_READY);
 		switch_channel_clear_flag(chan_b, CF_NOT_READY);
 	}
diff --git a/src/switch_jitterbuffer.c b/src/switch_jitterbuffer.c
index b396dd4611..2d74888ce4 100644
--- a/src/switch_jitterbuffer.c
+++ b/src/switch_jitterbuffer.c
@@ -37,7 +37,7 @@
 #define PERIOD_LEN 250
 #define MAX_FRAME_PADDING 2
 #define MAX_MISSING_SEQ 20
-#define jb_debug(_jb, _level, _format, ...) if (_jb->debug_level >= _level) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(_jb->session), SWITCH_LOG_ALERT, "JB:%p:%s lv:%d ln:%.4d sz:%.3u/%.3u/%.3u/%.3u c:%.3u %.3u/%.3u/%.3u/%.3u %.2f%% ->" _format, (void *) _jb, (jb->type == SJB_AUDIO ? "aud" : "vid"), _level, __LINE__,  _jb->min_frame_len, _jb->max_frame_len, _jb->frame_len, _jb->complete_frames, _jb->period_count, _jb->consec_good_count, _jb->period_good_count, _jb->consec_miss_count, _jb->period_miss_count, _jb->period_miss_pct, __VA_ARGS__)
+#define jb_debug(_jb, _level, _format, ...) if (_jb->debug_level >= _level) switch_log_printf(SWITCH_CHANNEL_SESSION_LOG_CLEAN(_jb->session), SWITCH_LOG_ALERT, "JB:%p:%s lv:%d ln:%.4d sz:%.3u/%.3u/%.3u/%.3u c:%.3u %.3u/%.3u/%.3u/%.3u %.2f%% ->" _format, (void *) _jb, (jb->type == SJB_TEXT ? "txt" : (jb->type == SJB_AUDIO ? "aud" : "vid")), _level, __LINE__,  _jb->min_frame_len, _jb->max_frame_len, _jb->frame_len, _jb->complete_frames, _jb->period_count, _jb->consec_good_count, _jb->period_good_count, _jb->consec_miss_count, _jb->period_miss_count, _jb->period_miss_pct, __VA_ARGS__)
 
 //const char *TOKEN_1 = "ONE";
 //const char *TOKEN_2 = "TWO";
@@ -101,6 +101,8 @@ struct switch_jb_s {
 	switch_jb_type_t type;
 	switch_core_session_t *session;
 	switch_channel_t *channel;
+	uint32_t buffer_lag;
+	uint32_t flush;
 };
 
 
@@ -1117,7 +1119,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_put_packet(switch_jb_t *jb, switch_rtp
 
 	if (!want) want = got;
 
-	if (switch_test_flag(jb, SJB_QUEUE_ONLY) || jb->type == SJB_AUDIO) {
+	if (switch_test_flag(jb, SJB_QUEUE_ONLY) || jb->type == SJB_AUDIO || jb->type == SJB_TEXT) {
 		jb->next_seq = htons(got + 1);
 	} else {
 
@@ -1203,12 +1205,26 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp
 	switch_mutex_lock(jb->mutex);
 
 	if (jb->complete_frames == 0) {
+		jb->flush = 0;
 		switch_goto_status(SWITCH_STATUS_BREAK, end);
 	}
 
 	if (jb->complete_frames < jb->frame_len) {
-		jb_debug(jb, 2, "BUFFERING %u/%u\n", jb->complete_frames , jb->frame_len);
-		switch_goto_status(SWITCH_STATUS_MORE_DATA, end);
+		
+		if (jb->type == SJB_TEXT) {
+			if (jb->complete_frames && !jb->buffer_lag) {
+				jb->buffer_lag = 10;
+			}
+
+			if (jb->buffer_lag && --jb->buffer_lag == 0) {
+				jb->flush = 1;
+			}
+		}
+
+		if (!jb->flush) {
+			jb_debug(jb, 2, "BUFFERING %u/%u\n", jb->complete_frames , jb->frame_len);
+			switch_goto_status(SWITCH_STATUS_MORE_DATA, end);
+		}
 	}
 
 	jb_debug(jb, 2, "GET PACKET %u/%u n:%d\n", jb->complete_frames , jb->frame_len, jb->visible_nodes);
@@ -1254,8 +1270,8 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp
 				}
 			}
 		}
-
 	}
+	
 
 	jb->period_miss_pct = ((double)jb->period_miss_count / jb->period_count) * 100;
 
@@ -1263,7 +1279,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp
 		jb_debug(jb, 2, "Miss percent %02f too high, resetting buffer.\n", jb->period_miss_pct);
 		switch_jb_reset(jb);
 	}
-
+	
 	if ((status = jb_next_packet(jb, &node)) == SWITCH_STATUS_SUCCESS) {
 		jb_debug(jb, 2, "Found next frame cur ts: %u seq: %u\n", htonl(node->packet.header.ts), htons(node->packet.header.seq));
 
@@ -1272,7 +1288,7 @@ SWITCH_DECLARE(switch_status_t) switch_jb_get_packet(switch_jb_t *jb, switch_rtp
 			jb->highest_read_seq = node->packet.header.seq;
 		}
 		
-		if (jb->read_init && htons(node->packet.header.seq) >= htons(jb->highest_read_seq) && (ntohl(node->packet.header.ts) > ntohl(jb->highest_read_ts))) {
+		if (jb->type == SJB_TEXT || (jb->read_init && htons(node->packet.header.seq) >= htons(jb->highest_read_seq) && (ntohl(node->packet.header.ts) > ntohl(jb->highest_read_ts)))) {
 			jb->complete_frames--;
 			jb_debug(jb, 2, "READ frame ts: %u complete=%u/%u n:%u\n", ntohl(node->packet.header.ts), jb->complete_frames , jb->frame_len, jb->visible_nodes);
 			jb->highest_read_ts = node->packet.header.ts;
diff --git a/src/switch_rtp.c b/src/switch_rtp.c
index 9e61003009..05f9b63be5 100644
--- a/src/switch_rtp.c
+++ b/src/switch_rtp.c
@@ -511,7 +511,7 @@ typedef enum {
 static void do_2833(switch_rtp_t *rtp_session);
 
 
-#define rtp_type(rtp_session) rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio"
+#define rtp_type(rtp_session) rtp_session->flags[SWITCH_RTP_FLAG_TEXT] ?  "text" : (rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio")
 
 
 static void switch_rtp_change_ice_dest(switch_rtp_t *rtp_session, switch_rtp_ice_t *ice, const char *host, switch_port_t port)
@@ -1278,7 +1278,7 @@ static void handle_ice(switch_rtp_t *rtp_session, switch_rtp_ice_t *ice, void *d
 			msg.message_id = SWITCH_MESSAGE_INDICATE_STUN_ERROR;
 			switch_core_session_receive_message(rtp_session->session, &msg);			
 			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG, 
-							  "STUN/ICE binding error received on %s channel\n", rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "video" : "audio");
+							  "STUN/ICE binding error received on %s channel\n", rtp_type(rtp_session));
 		}
 
 	}
@@ -3626,7 +3626,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess
 
 			if (status == SWITCH_STATUS_SUCCESS) {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_INFO, "Activating %s Secure %s RECV\n", 
-								  rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "Video" : "Audio", idx ? "RTCP" : "RTP");
+								  rtp_type(rtp_session), idx ? "RTCP" : "RTP");
 				rtp_session->flags[SWITCH_RTP_FLAG_SECURE_RECV] = 1;
 			} else {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "Error allocating srtp [%d]\n", stat);
@@ -3648,7 +3648,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_add_crypto_key(switch_rtp_t *rtp_sess
 
 			if (status == SWITCH_STATUS_SUCCESS) {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_INFO, "Activating %s Secure %s SEND\n",
-								  rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] ? "Video" : "Audio", idx ? "RTCP" : "RTP");
+								  rtp_type(rtp_session), idx ? "RTCP" : "RTP");
 				rtp_session->flags[SWITCH_RTP_FLAG_SECURE_SEND] = 1;
 			} else {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_ERROR, "Error allocating SRTP [%d]\n", stat);
@@ -4057,11 +4057,11 @@ SWITCH_DECLARE(switch_timer_t *) switch_rtp_get_media_timer(switch_rtp_t *rtp_se
 
 SWITCH_DECLARE(switch_jb_t *) switch_rtp_get_jitter_buffer(switch_rtp_t *rtp_session)
 {
-	if (!switch_rtp_ready(rtp_session) || !rtp_session->jb) {
+	if (!switch_rtp_ready(rtp_session)) {
 		return NULL;
 	}
 
-	return rtp_session->jb;
+	return rtp_session->jb ? rtp_session->jb : rtp_session->vb;
 }
 
 SWITCH_DECLARE(switch_status_t) switch_rtp_pause_jitter_buffer(switch_rtp_t *rtp_session, switch_bool_t pause)
@@ -4123,7 +4123,7 @@ SWITCH_DECLARE(switch_status_t) switch_rtp_set_video_buffer_size(switch_rtp_t *r
 	rtp_session->last_max_vb_frames = max_frames;
 	
 	if (!rtp_session->vb) {
-		switch_jb_create(&rtp_session->vb, SJB_VIDEO, frames, max_frames, rtp_session->pool);
+		switch_jb_create(&rtp_session->vb, rtp_session->flags[SWITCH_RTP_FLAG_TEXT] ? SJB_TEXT : SJB_VIDEO, frames, max_frames, rtp_session->pool);
 		switch_jb_set_session(rtp_session->vb, rtp_session->session);
 	} else {
 		switch_jb_set_frames(rtp_session->vb, frames, max_frames);
@@ -5659,7 +5659,7 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t
 			switch_jb_destroy(&rtp_session->vb);
 		}
 	}
-
+	
 	if (rtp_session->has_rtp && *bytes) {
 		uint32_t read_ssrc = ntohl(rtp_session->last_rtp_hdr.ssrc);
 
@@ -5718,7 +5718,7 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t
 	}
 
 	if (!*bytes || rtp_session->has_rtp) {
-		
+
 		if (rtp_session->jb && !rtp_session->pause_jb && jb_valid(rtp_session)) {
 			switch_status_t jstatus = switch_jb_get_packet(rtp_session->jb, (switch_rtp_packet_t *) &rtp_session->recv_msg, bytes);
 
@@ -5778,9 +5778,21 @@ static switch_status_t read_rtp_packet(switch_rtp_t *rtp_session, switch_size_t
 			default:
 				break;
 			}
-		
+
+			if (vstatus == SWITCH_STATUS_NOTFOUND && rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) {
+				int pt = get_recv_payload(rtp_session);
+				(*flags) |= SFF_PLC;
+				status = SWITCH_STATUS_SUCCESS;
+				*bytes = switch_jb_get_last_read_len(rtp_session->vb);
+				rtp_session->last_rtp_hdr = rtp_session->recv_msg.header;
+				if (pt > -1) {
+					rtp_session->last_rtp_hdr.pt = pt;
+				}
+			}
+				
 			if (vstatus == SWITCH_STATUS_SUCCESS) {
 				rtp_session->last_rtp_hdr = rtp_session->recv_msg.header;
+
 				if (!xcheck_jitter) {
 					check_jitter(rtp_session);
 					xcheck_jitter = *bytes;
@@ -5854,6 +5866,7 @@ static void handle_nack(switch_rtp_t *rtp_session, uint32_t nack)
 					
 				}
 				//switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG1, "RE----SEND %u\n", ntohs(send_msg->header.seq));
+
 				switch_rtp_write_raw(rtp_session, (void *) &send_msg, &bytes, SWITCH_FALSE);
 			} else {
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG1, "Cannot send NACK for seq %u\n", ntohs(seq) + i);
@@ -6281,7 +6294,9 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
 				}
 			}
 
-			if (hot_socket && (rtp_session->hot_hits % 10) != 0) { 
+			if (rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) {
+				///NOOP
+			} else if (hot_socket && (rtp_session->hot_hits % 10) != 0) { 
 				switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_DEBUG10, "%s timer while HOT\n", rtp_session_name(rtp_session));
 				switch_core_timer_next(&rtp_session->timer);
 			} else if (hot_socket) {
@@ -6368,10 +6383,6 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
 			poll_status = switch_poll(rtp_session->read_pollfd, 1, &fdr, pt);
 
 
-			//if (rtp_session->flags[SWITCH_RTP_FLAG_VIDEO]) {
-			//	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(rtp_session->session), SWITCH_LOG_WARNING, "WTF Poll %d\n", poll_status);
-			//}
-			
 			if (!rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] && rtp_session->dtmf_data.out_digit_dur > 0) {
 				return_cng_frame();
 			}
@@ -6393,7 +6404,7 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
 			if (read_pretriggered) {
 				read_pretriggered = 0;
 			} else {
-
+				
 				status = read_rtp_packet(rtp_session, &bytes, flags, poll_status, SWITCH_TRUE);
 
 				if (status == SWITCH_STATUS_GENERR) {
@@ -6665,11 +6676,11 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
 
 		if (bytes && rtp_session->last_rtp_hdr.m && rtp_session->last_rtp_hdr.pt != rtp_session->recv_te && 
 			!rtp_session->flags[SWITCH_RTP_FLAG_VIDEO] &&
+			!rtp_session->flags[SWITCH_RTP_FLAG_TEXT] &&
 			!(rtp_session->rtp_bugs & RTP_BUG_IGNORE_MARK_BIT)) {
 			rtp_flush_read_buffer(rtp_session, SWITCH_RTP_FLUSH_ONCE);
 		}
 
-
 		if (rtp_session->last_rtp_hdr.pt == rtp_session->cng_pt || rtp_session->last_rtp_hdr.pt == 13) {
 			*flags |= SFF_NOT_AUDIO;
 		} else {
@@ -6752,6 +6763,19 @@ static int rtp_common_read(switch_rtp_t *rtp_session, switch_payload_t *payload_
 			}
 		}
 
+		if (rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) {
+			if (!bytes) {
+				if (rtp_session->flags[SWITCH_RTP_FLAG_USE_TIMER]) {
+					switch_core_timer_next(&rtp_session->timer);
+				}
+				return_cng_frame();
+			} else {
+				*payload_type = rtp_session->last_rtp_hdr.pt;
+				ret = (int) bytes;
+				goto end;
+			}
+		}
+
 		if (bytes && (rtp_session->flags[SWITCH_RTP_FLAG_PROXY_MEDIA] || rtp_session->flags[SWITCH_RTP_FLAG_UDPTL])) {
 			/* Fast PASS! */
 			*flags |= SFF_PROXY_PACKET;
@@ -7923,14 +7947,19 @@ SWITCH_DECLARE(int) switch_rtp_write_frame(switch_rtp_t *rtp_session, switch_fra
 #endif
 	}
 
-	if (switch_test_flag(frame, SFF_RTP_HEADER)) {
-		switch_size_t wrote = switch_rtp_write_manual(rtp_session, frame->data, frame->datalen,
-													  frame->m, frame->payload, (uint32_t) (frame->timestamp), &frame->flags);
+
+	if (switch_test_flag(frame, SFF_RTP_HEADER) || rtp_session->flags[SWITCH_RTP_FLAG_TEXT]) {
+		switch_size_t wrote;
+
+		wrote = switch_rtp_write_manual(rtp_session, frame->data, frame->datalen,
+										frame->m, frame->payload, (uint32_t) (frame->timestamp), &frame->flags);
 		
 		rtp_session->stats.outbound.raw_bytes += wrote;
 		rtp_session->stats.outbound.media_bytes += wrote;
 		rtp_session->stats.outbound.media_packet_count++;
 		rtp_session->stats.outbound.packet_count++;
+
+		return wrote;
 	}
 
 	if (frame->pmap && rtp_session->pmaps && *rtp_session->pmaps) {