Revision
16779
Author
sathieu
Date
2010-02-24 06:04:08 -0600 (Wed, 24 Feb 2010)

Log Message

Skinny: Milestone 3: Calls management

As of now, this is only proof of concept. How to test:
- only one phone registred
- call originate skinny/internal/7628 &delay_echo(100) from console
- answer
- test the echo
- hangup

Modified Paths

Diff

Modified: freeswitch/trunk/src/mod/endpoints/mod_skinny/mod_skinny.c (16778 => 16779)


--- freeswitch/trunk/src/mod/endpoints/mod_skinny/mod_skinny.c	2010-02-24 12:03:59 UTC (rev 16778)
+++ freeswitch/trunk/src/mod/endpoints/mod_skinny/mod_skinny.c	2010-02-24 12:04:08 UTC (rev 16779)
@@ -140,7 +140,10 @@
 	TFLAG_HANGUP = (1 << 5),
 	TFLAG_LINEAR = (1 << 6),
 	TFLAG_CODEC = (1 << 7),
-	TFLAG_BREAK = (1 << 8)
+	TFLAG_BREAK = (1 << 8),
+	
+	TFLAG_READING = (1 << 9),
+	TFLAG_WRITING = (1 << 10)
 } TFLAGS;
 
 typedef enum {
@@ -149,18 +152,34 @@
 
 struct private_object {
 	unsigned int flags;
-	switch_codec_t read_codec;
-	switch_codec_t write_codec;
 	switch_frame_t read_frame;
 	unsigned char databuf[SWITCH_RECOMMENDED_BUFFER_SIZE];
 	switch_core_session_t *session;
 	switch_caller_profile_t *caller_profile;
 	switch_mutex_t *mutex;
 	switch_mutex_t *flag_mutex;
-	char *dest;
 	/* identification */
-	skinny_profile_t *profile;
+	struct listener *listener;
+	uint32_t line;
 	uint32_t call_id;
+	uint32_t party_id;
+	/* codec */
+	char *iananame;	
+	switch_codec_t read_codec;
+	switch_codec_t write_codec;
+	switch_codec_implementation_t read_impl;
+	switch_codec_implementation_t write_impl;
+	unsigned long rm_rate;
+	uint32_t codec_ms;
+	char *rm_encoding;
+	char *rm_fmtp;
+	switch_payload_t agreed_pt;
+	/* RTP */
+	switch_rtp_t *rtp_session;
+	char *local_sdp_audio_ip;
+	switch_port_t local_sdp_audio_port;
+	char *remote_sdp_audio_ip;
+	switch_port_t remote_sdp_audio_port;
 };
 
 typedef struct private_object private_t;
@@ -801,6 +820,7 @@
 struct listener {
 	skinny_profile_t *profile;
 	char device_name[16];
+	switch_core_session_t *outgoing_session;
 
 	switch_socket_t *sock;
 	switch_memory_pool_t *pool;
@@ -1060,12 +1080,134 @@
 /* CHANNEL FUNCTIONS */
 /*****************************************************************************/
 
+static switch_status_t skinny_tech_set_codec(private_t *tech_pvt, int force)
+{
+	int ms;
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+	int resetting = 0;
+
+	if (!tech_pvt->iananame) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "No audio codec available\n");
+		switch_goto_status(SWITCH_STATUS_FALSE, end);
+	}
+
+	if (switch_core_codec_ready(&tech_pvt->read_codec)) {
+		if (!force) {
+			switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+		}
+		if (strcasecmp(tech_pvt->read_impl.iananame, tech_pvt->iananame) ||
+			tech_pvt->read_impl.samples_per_second != tech_pvt->rm_rate ||
+			tech_pvt->codec_ms != (uint32_t)tech_pvt->read_impl.microseconds_per_packet / 1000) {
+			
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Changing Codec from %s@%dms to %s@%dms\n",
+							  tech_pvt->read_impl.iananame, tech_pvt->read_impl.microseconds_per_packet / 1000, 
+							  tech_pvt->rm_encoding, tech_pvt->codec_ms);
+			
+			switch_core_session_lock_codec_write(tech_pvt->session);
+			switch_core_session_lock_codec_read(tech_pvt->session);
+			resetting = 1;
+			switch_core_codec_destroy(&tech_pvt->read_codec);
+			switch_core_codec_destroy(&tech_pvt->write_codec);
+		} else {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Already using %s\n", tech_pvt->read_impl.iananame);
+			switch_goto_status(SWITCH_STATUS_SUCCESS, end);
+		}
+	}
+	
+	if (switch_core_codec_init(&tech_pvt->read_codec,
+							   tech_pvt->iananame,
+							   tech_pvt->rm_fmtp,
+							   tech_pvt->rm_rate,
+							   tech_pvt->codec_ms,
+							   1,
+							   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE | 0 /* TODO tech_pvt->profile->codec_flags */,
+							   NULL, switch_core_session_get_pool(tech_pvt->session)) != SWITCH_STATUS_SUCCESS) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n");
+		switch_goto_status(SWITCH_STATUS_FALSE, end);
+	}
+
+	if (switch_core_codec_init(&tech_pvt->write_codec,
+							   tech_pvt->iananame,
+							   tech_pvt->rm_fmtp,
+							   tech_pvt->rm_rate,
+							   tech_pvt->codec_ms,
+							   1,
+							   SWITCH_CODEC_FLAG_ENCODE | SWITCH_CODEC_FLAG_DECODE | 0 /* TODO tech_pvt->profile->codec_flags */,
+							   NULL, switch_core_session_get_pool(tech_pvt->session)) != SWITCH_STATUS_SUCCESS) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n");
+		switch_goto_status(SWITCH_STATUS_FALSE, end);
+	}
+
+	switch_assert(tech_pvt->read_codec.implementation);
+	switch_assert(tech_pvt->write_codec.implementation);
+
+	tech_pvt->read_impl = *tech_pvt->read_codec.implementation;
+	tech_pvt->write_impl = *tech_pvt->write_codec.implementation;
+
+	switch_core_session_set_read_impl(tech_pvt->session, tech_pvt->read_codec.implementation);
+	switch_core_session_set_write_impl(tech_pvt->session, tech_pvt->write_codec.implementation);
+
+	if (switch_rtp_ready(tech_pvt->rtp_session)) {
+		switch_assert(tech_pvt->read_codec.implementation);
+
+		if (switch_rtp_change_interval(tech_pvt->rtp_session, 
+									   tech_pvt->read_impl.microseconds_per_packet,
+									   tech_pvt->read_impl.samples_per_packet
+									   ) != SWITCH_STATUS_SUCCESS) {
+			/* TODO
+			switch_channel_hangup(tech_pvt->channel, SWITCH_CAUSE_DESTINATION_OUT_OF_ORDER);
+			*/
+			switch_goto_status(SWITCH_STATUS_FALSE, end);				
+		}
+	}
+
+	tech_pvt->read_frame.rate = tech_pvt->rm_rate;
+	ms = tech_pvt->write_codec.implementation->microseconds_per_packet / 1000;
+
+	if (!switch_core_codec_ready(&tech_pvt->read_codec)) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_ERROR, "Can't load codec?\n");
+		switch_goto_status(SWITCH_STATUS_FALSE, end);
+	}
+
+	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG, "Set Codec %s %s/%ld %d ms %d samples\n",
+					  "" /* TODO switch_channel_get_name(tech_pvt->channel)*/, tech_pvt->iananame, tech_pvt->rm_rate, tech_pvt->codec_ms,
+					  tech_pvt->read_impl.samples_per_packet);
+	tech_pvt->read_frame.codec = &tech_pvt->read_codec;
+
+	tech_pvt->write_codec.agreed_pt = tech_pvt->agreed_pt;
+	tech_pvt->read_codec.agreed_pt = tech_pvt->agreed_pt;
+
+	if (force != 2) {
+		switch_core_session_set_read_codec(tech_pvt->session, &tech_pvt->read_codec);
+		switch_core_session_set_write_codec(tech_pvt->session, &tech_pvt->write_codec);
+	}
+
+	/* TODO
+	tech_pvt->fmtp_out = switch_core_session_strdup(tech_pvt->session, tech_pvt->write_codec.fmtp_out);
+	*/
+
+	/* TODO
+	if (switch_rtp_ready(tech_pvt->rtp_session)) {
+		switch_rtp_set_default_payload(tech_pvt->rtp_session, tech_pvt->pt);
+	}
+	*/
+
+ end:
+	if (resetting) {
+		switch_core_session_unlock_codec_write(tech_pvt->session);
+		switch_core_session_unlock_codec_read(tech_pvt->session);
+	}
+
+	return status;
+}
+
 static void tech_init(private_t *tech_pvt, switch_core_session_t *session)
 {
 	tech_pvt->read_frame.data = tech_pvt->databuf;
 	tech_pvt->read_frame.buflen = sizeof(tech_pvt->databuf);
 	switch_mutex_init(&tech_pvt->mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
 	switch_mutex_init(&tech_pvt->flag_mutex, SWITCH_MUTEX_NESTED, switch_core_session_get_pool(session));
+	tech_pvt->call_id = 12345; /* TODO */
 	switch_core_session_set_private(session, tech_pvt);
 	tech_pvt->session = session;
 }
@@ -1165,6 +1307,7 @@
 {
 	switch_channel_t *channel = NULL;
 	private_t *tech_pvt = NULL;
+	listener_t *listener = NULL;
 
 	channel = switch_core_session_get_channel(session);
 	assert(channel != NULL);
@@ -1172,12 +1315,38 @@
 	tech_pvt = switch_core_session_get_private(session);
 	assert(tech_pvt != NULL);
 
+	listener = tech_pvt->listener;
+	assert(listener != NULL);
+	
 	switch_clear_flag_locked(tech_pvt, TFLAG_IO);
 	switch_clear_flag_locked(tech_pvt, TFLAG_VOICE);
-	//switch_thread_cond_signal(tech_pvt->cond);
 
+	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel));
 
-	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL HANGUP\n", switch_channel_get_name(channel));
+	stop_tone(listener, tech_pvt->line, tech_pvt->call_id);
+	set_lamp(listener, SKINNY_BUTTON_LINE, tech_pvt->line, SKINNY_LAMP_OFF);
+	clear_prompt_status(listener, tech_pvt->line, tech_pvt->call_id);
+	close_receive_channel(listener,
+		tech_pvt->call_id, /* uint32_t conference_id, */
+		tech_pvt->party_id, /* uint32_t pass_thru_party_id, */
+		tech_pvt->call_id /* uint32_t conference_id2, */
+	);
+	stop_media_transmission(listener,
+		tech_pvt->call_id, /* uint32_t conference_id, */
+		tech_pvt->party_id, /* uint32_t pass_thru_party_id, */
+		tech_pvt->call_id /* uint32_t conference_id2, */
+	);
+	send_call_state(listener,
+		SKINNY_ON_HOOK,
+		tech_pvt->line,
+		tech_pvt->call_id);
+	send_select_soft_keys(listener, tech_pvt->line, tech_pvt->call_id,
+		SKINNY_KEY_SET_ON_HOOK, 0xffff);
+	/* TODO: DefineTimeDate */
+	set_speaker_mode(listener, SKINNY_SPEAKER_OFF);
+	set_ringer(listener, SKINNY_RING_OFF, SKINNY_RING_FOREVER, 0);
+	
+
 	switch_mutex_lock(globals.calls_mutex);
 	globals.calls--;
 	if (globals.calls < 0) {
@@ -1212,6 +1381,8 @@
 	default:
 		break;
 	}
+	
+	switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_DEBUG, "%s CHANNEL KILL %d\n", switch_channel_get_name(channel), sig);
 
 	return SWITCH_STATUS_SUCCESS;
 }
@@ -1240,58 +1411,74 @@
 {
 	switch_channel_t *channel = NULL;
 	private_t *tech_pvt = NULL;
-	//switch_time_t started = switch_time_now();
-	//unsigned int elapsed;
-	switch_byte_t *data;
+	int payload = 0;
 
 	channel = switch_core_session_get_channel(session);
 	assert(channel != NULL);
 
 	tech_pvt = switch_core_session_get_private(session);
 	assert(tech_pvt != NULL);
-	tech_pvt->read_frame.flags = SFF_NONE;
-	*frame = NULL;
 
-	while (switch_test_flag(tech_pvt, TFLAG_IO)) {
-
-		if (switch_test_flag(tech_pvt, TFLAG_BREAK)) {
-			switch_clear_flag(tech_pvt, TFLAG_BREAK);
-			goto cng;
+	while (!(tech_pvt->read_codec.implementation && switch_rtp_ready(tech_pvt->rtp_session))) {
+		if (switch_channel_ready(channel)) {
+			switch_yield(10000);
+		} else {
+			return SWITCH_STATUS_GENERR;
 		}
+	}
 
-		if (!switch_test_flag(tech_pvt, TFLAG_IO)) {
-			return SWITCH_STATUS_FALSE;
-		}
+	tech_pvt->read_frame.datalen = 0;
+	switch_set_flag_locked(tech_pvt, TFLAG_READING);
 
-		if (switch_test_flag(tech_pvt, TFLAG_IO) && switch_test_flag(tech_pvt, TFLAG_VOICE)) {
-			switch_clear_flag_locked(tech_pvt, TFLAG_VOICE);
-			if (!tech_pvt->read_frame.datalen) {
-				continue;
+	if (switch_test_flag(tech_pvt, TFLAG_IO)) {
+		switch_status_t status;
+
+		switch_assert(tech_pvt->rtp_session != NULL);
+		tech_pvt->read_frame.datalen = 0;
+
+
+		while (switch_test_flag(tech_pvt, TFLAG_IO) && tech_pvt->read_frame.datalen == 0) {
+			tech_pvt->read_frame.flags = SFF_NONE;
+
+			status = switch_rtp_zerocopy_read_frame(tech_pvt->rtp_session, &tech_pvt->read_frame, flags);
+			if (status != SWITCH_STATUS_SUCCESS && status != SWITCH_STATUS_BREAK) {
+				return SWITCH_STATUS_FALSE;
 			}
-			*frame = &tech_pvt->read_frame;
-#if SWITCH_BYTE_ORDER == __BIG_ENDIAN
-			if (switch_test_flag(tech_pvt, TFLAG_LINEAR)) {
-				switch_swap_linear((*frame)->data, (int) (*frame)->datalen / 2);
+
+			payload = tech_pvt->read_frame.payload;
+
+			if (switch_rtp_has_dtmf(tech_pvt->rtp_session)) {
+				switch_dtmf_t dtmf = { 0 };
+				switch_rtp_dequeue_dtmf(tech_pvt->rtp_session, &dtmf);
+				switch_channel_queue_dtmf(channel, &dtmf);
 			}
-#endif
-			return SWITCH_STATUS_SUCCESS;
+
+
+			if (tech_pvt->read_frame.datalen > 0) {
+				size_t bytes = 0;
+				int frames = 1;
+
+				if (!switch_test_flag((&tech_pvt->read_frame), SFF_CNG)) {
+					if ((bytes = tech_pvt->read_codec.implementation->encoded_bytes_per_packet)) {
+						frames = (tech_pvt->read_frame.datalen / bytes);
+					}
+					tech_pvt->read_frame.samples = (int) (frames * tech_pvt->read_codec.implementation->samples_per_packet);
+				}
+				break;
+			}
 		}
-
-		switch_cond_next();
 	}
 
+	switch_clear_flag_locked(tech_pvt, TFLAG_READING);
 
-	return SWITCH_STATUS_FALSE;
+	if (tech_pvt->read_frame.datalen == 0) {
+		*frame = NULL;
+		return SWITCH_STATUS_GENERR;
+	}
 
-  cng:
-	data = (switch_byte_t *) tech_pvt->read_frame.data;
-	data[0] = 65;
-	data[1] = 0;
-	tech_pvt->read_frame.datalen = 2;
-	tech_pvt->read_frame.flags = SFF_CNG;
 	*frame = &tech_pvt->read_frame;
+
 	return SWITCH_STATUS_SUCCESS;
-
 }
 
 static switch_status_t channel_write_frame(switch_core_session_t *session, switch_frame_t *frame, switch_io_flag_t flags, int stream_id)
@@ -1299,7 +1486,8 @@
 	switch_channel_t *channel = NULL;
 	private_t *tech_pvt = NULL;
 	//switch_frame_t *pframe;
-
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+	
 	channel = switch_core_session_get_channel(session);
 	assert(channel != NULL);
 
@@ -1315,9 +1503,14 @@
 	}
 #endif
 
+	switch_set_flag_locked(tech_pvt, TFLAG_WRITING);
 
-	return SWITCH_STATUS_SUCCESS;
+	switch_rtp_write_frame(tech_pvt->rtp_session, frame);
 
+	switch_clear_flag_locked(tech_pvt, TFLAG_WRITING);
+
+	return status;
+
 }
 
 static switch_status_t channel_answer_channel(switch_core_session_t *session)
@@ -1413,8 +1606,6 @@
 		goto error;
 	}
 	
-	tech_pvt->profile = profile;
-	tech_pvt->dest = switch_core_session_strdup(nsession, dest);
 	snprintf(name, sizeof(name), "SKINNY/%s/%s", profile->name, dest);
 
 	channel = switch_core_session_get_channel(nsession);
@@ -1427,15 +1618,58 @@
 
 	switch_channel_set_flag(channel, CF_OUTBOUND);
 	switch_set_flag_locked(tech_pvt, TFLAG_OUTBOUND);
-	switch_channel_set_state(channel, CS_INIT);
 
-	cause = SWITCH_CAUSE_CHAN_NOT_IMPLEMENTED;
-	
-	if (!(cause == SWITCH_CAUSE_SUCCESS)) {
+	/* TODO: find listener(s) based on profile and dest */
+	tech_pvt->listener = profile->listeners;
+
+	if (!tech_pvt->listener) {
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_ERROR, "Invalid destination %s in profile %s\n", dest, profile_name);
+		cause = SWITCH_CAUSE_UNALLOCATED_NUMBER;
 		goto error;
 	}
+
+	/* TODO find line */
+	tech_pvt->line = 1;
 	
+	tech_pvt->listener->outgoing_session = nsession;
+	send_call_state(tech_pvt->listener, SKINNY_RING_IN, tech_pvt->line, tech_pvt->call_id);
+	send_select_soft_keys(tech_pvt->listener, tech_pvt->line, tech_pvt->call_id,
+		SKINNY_KEY_SET_RING_IN, 0xffff);
+	display_prompt_status(tech_pvt->listener, 0, "\200\027tel", tech_pvt->line, tech_pvt->call_id);
+	/* displayprinotifiymessage */
+	send_call_info(tech_pvt->listener,
+		"TODO", /* char calling_party_name[40], */
+		"TODO", /* char calling_party[24], */
+		"TODO", /* char called_party_name[40], */
+		"TODO", /* char called_party[24], */
+		tech_pvt->line, /* uint32_t line_instance, */
+		tech_pvt->call_id, /* uint32_t call_id, */
+		SKINNY_OUTBOUND_CALL, /* uint32_t call_type, */
+		"TODO", /* char original_called_party_name[40], */
+		"TODO", /* char original_called_party[24], */
+		"TODO", /* char last_redirecting_party_name[40], */
+		"TODO", /* char last_redirecting_party[24], */
+		0, /* uint32_t original_called_party_redirect_reason, */
+		0, /* uint32_t last_redirecting_reason, */
+		"TODO", /* char calling_party_voice_mailbox[24], */
+		"TODO", /* char called_party_voice_mailbox[24], */
+		"TODO", /* char original_called_party_voice_mailbox[24], */
+		"TODO", /* char last_redirecting_voice_mailbox[24], */
+		1, /* uint32_t call_instance, */
+		1, /* uint32_t call_security_status, */
+		0 /* uint32_t party_pi_restriction_bits */
+	);
+	set_lamp(tech_pvt->listener, SKINNY_BUTTON_LINE, tech_pvt->line, SKINNY_LAMP_BLINK);
+	set_ringer(tech_pvt->listener, SKINNY_RING_OUTSIDE, SKINNY_RING_FOREVER, 0);
+
 	*new_session = nsession;
+
+	/* ?? switch_channel_mark_ring_ready(channel); */
+
+	if (switch_channel_get_state(channel) == CS_NEW) {
+		switch_channel_set_state(channel, CS_INIT);
+	}
+
 	cause = SWITCH_CAUSE_SUCCESS;
 	goto done;
 
@@ -2561,6 +2795,165 @@
 	return SWITCH_STATUS_SUCCESS;
 }
 
+static switch_status_t skinny_handle_off_hook_message(listener_t *listener, skinny_message_t *request)
+{
+	skinny_profile_t *profile;
+
+	switch_assert(listener->profile);
+	switch_assert(listener->device_name);
+
+	profile = listener->profile;
+
+	skinny_check_data_length(request, sizeof(request->data.off_hook));
+
+	if(listener->outgoing_session) { /*answering a call */
+		private_t *tech_pvt = NULL;
+		tech_pvt = switch_core_session_get_private(listener->outgoing_session);
+		if(request->data.off_hook.line_instance) {
+			tech_pvt->line = request->data.off_hook.line_instance;
+		} else {
+			tech_pvt->line = 1;
+		}
+		set_ringer(listener, SKINNY_RING_OFF, SKINNY_RING_FOREVER, 0); /* TODO : here ? */
+		stop_tone(listener, tech_pvt->line, tech_pvt->call_id);
+		open_receive_channel(listener,
+			tech_pvt->call_id, /* uint32_t conference_id, */
+			0, /* uint32_t pass_thru_party_id, */
+			20, /* uint32_t packets, */
+			SKINNY_CODEC_ULAW_64K, /* uint32_t payload_capacity, */
+			0, /* uint32_t echo_cancel_type, */
+			0, /* uint32_t g723_bitrate, */
+			0, /* uint32_t conference_id2, */
+			0 /* uint32_t reserved[10] */
+		);
+		send_call_state(listener,
+			SKINNY_CONNECTED,
+			tech_pvt->line,
+			tech_pvt->call_id);
+		send_select_soft_keys(listener,
+			tech_pvt->line,
+			tech_pvt->call_id,
+			SKINNY_KEY_SET_CONNECTED,
+			0xffff);
+		display_prompt_status(listener,
+			0,
+			"\200\030",
+			1,
+			tech_pvt->call_id);
+	}
+	return SWITCH_STATUS_SUCCESS;
+}
+
+static switch_status_t skinny_handle_open_receive_channel_ack_message(listener_t *listener, skinny_message_t *request)
+{
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+	skinny_profile_t *profile;
+
+	switch_assert(listener->profile);
+	switch_assert(listener->device_name);
+
+	profile = listener->profile;
+
+	skinny_check_data_length(request, sizeof(request->data.open_receive_channel_ack));
+
+	if(listener->outgoing_session) {
+		const char *err = NULL;
+		private_t *tech_pvt = NULL;
+		switch_channel_t *channel = NULL;
+		struct in_addr addr;
+
+		tech_pvt = switch_core_session_get_private(listener->outgoing_session);
+		channel = switch_core_session_get_channel(listener->outgoing_session);
+
+		/* Codec */
+		tech_pvt->iananame = "PCMU"; /* TODO */
+		tech_pvt->codec_ms = 10; /* TODO */
+		tech_pvt->rm_rate = 8000; /* TODO */
+		tech_pvt->rm_fmtp = NULL; /* TODO */
+		tech_pvt->agreed_pt = (switch_payload_t) 0; /* TODO */
+		tech_pvt->rm_encoding = switch_core_strdup(switch_core_session_get_pool(listener->outgoing_session), "");
+		skinny_tech_set_codec(tech_pvt, 0);
+		if ((status = skinny_tech_set_codec(tech_pvt, 0)) != SWITCH_STATUS_SUCCESS) {
+			goto end;
+		}
+		
+		/* Request a local port from the core's allocator */
+		if (!(tech_pvt->local_sdp_audio_port = switch_rtp_request_port(listener->profile->ip))) {
+			switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_CRIT, "No RTP ports available!\n");
+			return SWITCH_STATUS_FALSE;
+		}
+		tech_pvt->local_sdp_audio_ip = switch_core_strdup(switch_core_session_get_pool(listener->outgoing_session), listener->profile->ip);
+
+		tech_pvt->remote_sdp_audio_ip = inet_ntoa(request->data.open_receive_channel_ack.ip);
+		tech_pvt->remote_sdp_audio_port = request->data.open_receive_channel_ack.port;
+
+		tech_pvt->rtp_session = switch_rtp_new(tech_pvt->local_sdp_audio_ip,
+											   tech_pvt->local_sdp_audio_port,
+											   tech_pvt->remote_sdp_audio_ip,
+											   tech_pvt->remote_sdp_audio_port,
+											   tech_pvt->agreed_pt,
+											   tech_pvt->read_impl.samples_per_packet,
+											   tech_pvt->codec_ms * 1000,
+											   (switch_rtp_flag_t) 0, "soft", &err,
+											   switch_core_session_get_pool(listener->outgoing_session));
+		switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(tech_pvt->session), SWITCH_LOG_DEBUG,
+						  "AUDIO RTP [%s] %s:%d->%s:%d codec: %u ms: %d [%s]\n",
+						  switch_channel_get_name(channel),
+						  tech_pvt->local_sdp_audio_ip,
+						  tech_pvt->local_sdp_audio_port,
+						  tech_pvt->remote_sdp_audio_ip,
+						  tech_pvt->remote_sdp_audio_port,
+						  tech_pvt->agreed_pt,
+						  tech_pvt->read_impl.microseconds_per_packet / 1000,
+						  switch_rtp_ready(tech_pvt->rtp_session) ? "SUCCESS" : err);
+		inet_aton(tech_pvt->local_sdp_audio_ip, &addr);
+		start_media_transmission(listener,
+			tech_pvt->call_id, /* uint32_t conference_id, */
+			tech_pvt->party_id, /* uint32_t pass_thru_party_id, */
+			addr.s_addr, /* uint32_t remote_ip, */
+			tech_pvt->local_sdp_audio_port, /* uint32_t remote_port, */
+			20, /* uint32_t ms_per_packet, */
+			SKINNY_CODEC_ULAW_64K, /* uint32_t payload_capacity, */
+			184, /* uint32_t precedence, */
+			0, /* uint32_t silence_suppression, */
+			0, /* uint16_t max_frames_per_packet, */
+			0 /* uint32_t g723_bitrate */
+		);
+		switch_channel_mark_answered(channel);
+	}
+end:
+	return status;
+}
+
+static switch_status_t skinny_handle_on_hook_message(listener_t *listener, skinny_message_t *request)
+{
+	switch_status_t status = SWITCH_STATUS_SUCCESS;
+	skinny_profile_t *profile;
+
+	switch_assert(listener->profile);
+	switch_assert(listener->device_name);
+
+	profile = listener->profile;
+
+	skinny_check_data_length(request, sizeof(request->data.on_hook));
+
+	if(listener->outgoing_session) {
+		switch_channel_t *channel = NULL;
+		private_t *tech_pvt = NULL;
+
+		channel = switch_core_session_get_channel(listener->outgoing_session);
+		assert(channel != NULL);
+
+		tech_pvt = switch_core_session_get_private(listener->outgoing_session);
+		assert(tech_pvt != NULL);
+
+		switch_clear_flag_locked(tech_pvt, TFLAG_IO);
+		switch_clear_flag_locked(tech_pvt, TFLAG_VOICE);
+		switch_channel_hangup(channel, SWITCH_CAUSE_NORMAL_CLEARING);
+	}
+	return status;
+}
+
 static switch_status_t skinny_handle_unregister(listener_t *listener, skinny_message_t *request)
 {
 	switch_event_t *event = NULL;
@@ -2610,83 +3003,20 @@
 		/* live phase */
 		case KEEP_ALIVE_MESSAGE:
 			return skinny_handle_keep_alive_message(listener, request);
+		case OFF_HOOK_MESSAGE:
+			return skinny_handle_off_hook_message(listener, request);
+		case OPEN_RECEIVE_CHANNEL_ACK_MESSAGE:
+			return skinny_handle_open_receive_channel_ack_message(listener, request);
+		case ON_HOOK_MESSAGE:
+			return skinny_handle_on_hook_message(listener, request);
 		/* end phase */
 		case UNREGISTER_MESSAGE:
 			return skinny_handle_unregister(listener, request);
 		case 0xABCDEF: /* the following commands are to avoid compile warnings (which are errors) */
 activate_call_plane(listener, 1 /* line */);
-send_select_soft_keys(listener, 1 /* line */, 0 /* call_id */, SKINNY_KEY_SET_RING_OUT, 0xffff);
 send_dialed_number(listener, 0 /* called_party */, 1 /* line */, 0 /* call_id */);
-send_call_state(listener, SKINNY_PROCEED, 1 /* line */, 0 /* call_id */);
-open_receive_channel(listener,
-	0, /* uint32_t conference_id, */
-	0, /* uint32_t pass_thru_party_id, */
-	20, /* uint32_t packets, */
-	SKINNY_CODEC_ULAW_64K, /* uint32_t payload_capacity, */
-	0, /* uint32_t echo_cancel_type, */
-	0, /* uint32_t g723_bitrate, */
-	0, /* uint32_t conference_id2, */
-	0 /* uint32_t reserved[10] */
-);
-start_media_transmission(listener,
-	0, /* uint32_t conference_id, */
-	0, /* uint32_t pass_thru_party_id, */
-	0, /* uint32_t remote_ip, */
-	0, /* uint32_t remote_port, */
-	20, /* uint32_t ms_per_packet, */
-	SKINNY_CODEC_ULAW_64K, /* uint32_t payload_capacity, */
-	184, /* uint32_t precedence, */
-	0, /* uint32_t silence_suppression, */
-	0, /* uint16_t max_frames_per_packet, */
-	0 /* uint32_t g723_bitrate */
-);
-close_receive_channel(listener,
-	0, /* uint32_t conference_id, */
-	0, /* uint32_t pass_thru_party_id, */
-	0 /* uint32_t conference_id2, */
-);
-stop_media_transmission(listener,
-	0, /* uint32_t conference_id, */
-	0, /* uint32_t pass_thru_party_id, */
-	0 /* uint32_t conference_id2, */
-);
 start_tone(listener, SKINNY_TONE_DIALTONE, 0, 0, 0);
-stop_tone(listener, 0, 0);
-clear_prompt_status(listener, 0, 0);
-set_speaker_mode(listener, SKINNY_SPEAKER_OFF);
 
-send_call_state(listener, SKINNY_RING_IN, 0, 0);
-send_select_soft_keys(listener, 0, 0,
-	SKINNY_KEY_SET_RING_IN, 0xffff);
-display_prompt_status(listener, 0, "\200\027tel", 0, 0);
-/* displayprinotifiymessage */
-send_call_info(listener,
-	"TODO", /* char calling_party_name[40], */
-	"TODO", /* char calling_party[24], */
-	"TODO", /* char called_party_name[40], */
-	"TODO", /* char called_party[24], */
-	0, /* uint32_t line_instance, */
-	0, /* uint32_t call_id, */
-	SKINNY_OUTBOUND_CALL, /* uint32_t call_type, */
-	"TODO", /* char original_called_party_name[40], */
-	"TODO", /* char original_called_party[24], */
-	"TODO", /* char last_redirecting_party_name[40], */
-	"TODO", /* char last_redirecting_party[24], */
-	0, /* uint32_t original_called_party_redirect_reason, */
-	0, /* uint32_t last_redirecting_reason, */
-	"TODO", /* char calling_party_voice_mailbox[24], */
-	"TODO", /* char called_party_voice_mailbox[24], */
-	"TODO", /* char original_called_party_voice_mailbox[24], */
-	"TODO", /* char last_redirecting_voice_mailbox[24], */
-	1, /* uint32_t call_instance, */
-	1, /* uint32_t call_security_status, */
-	0 /* uint32_t party_pi_restriction_bits */
-);
-set_lamp(listener, SKINNY_BUTTON_LINE, 0, SKINNY_LAMP_BLINK);
-set_ringer(listener, SKINNY_RING_OUTSIDE, SKINNY_RING_FOREVER, 0);
-
-
-
 		default:
 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, 
 				"Unknown request type: %x (length=%d).\n", request->type, request->length);