From 20bb474c0e96b554febe3de186b4e22c2664f2d8 Mon Sep 17 00:00:00 2001 From: Robert Jongbloed Date: Wed, 29 Aug 2012 12:43:46 +1000 Subject: [PATCH] Added T.38 fax support to mod_opal --- src/mod/endpoints/mod_opal/mod_opal.cpp | 147 +++++++++++++++++++++--- src/mod/endpoints/mod_opal/mod_opal.h | 13 +++ 2 files changed, 147 insertions(+), 13 deletions(-) diff --git a/src/mod/endpoints/mod_opal/mod_opal.cpp b/src/mod/endpoints/mod_opal/mod_opal.cpp index cf160ba527..a8b4b70561 100644 --- a/src/mod/endpoints/mod_opal/mod_opal.cpp +++ b/src/mod/endpoints/mod_opal/mod_opal.cpp @@ -743,6 +743,29 @@ void FSConnection::SetCodecs() m_switchMediaFormats += switchFormat; } + +#if HAVE_T38 + OpalMediaFormat t38 = OpalT38; + + /* We need to have a T.38 options for TCS, but may be before the + spandsp_mod has set it us. So, if not, we actually give to spandsp_mod. */ + switch_t38_options_t *t38_options = (switch_t38_options_t *)switch_channel_get_private(m_fsChannel, "t38_options"); + if (t38_options == NULL) + SetT38OptionsFromMediaFormat(t38); + else { + t38.SetOptionInteger("T38FaxVersion", t38_options->T38FaxVersion); + t38.SetOptionInteger("T38MaxBitRate", t38_options->T38MaxBitRate); + t38.SetOptionBoolean("T38FaxFillBitRemoval", t38_options->T38FaxFillBitRemoval); + t38.SetOptionBoolean("T38FaxTranscodingMMR", t38_options->T38FaxTranscodingMMR); + t38.SetOptionBoolean("T38FaxTranscodingJBIG", t38_options->T38FaxTranscodingJBIG); + t38.SetOptionValue("T38FaxRateManagement", t38_options->T38FaxRateManagement); + t38.SetOptionInteger("T38Version", t38_options->T38FaxMaxBuffer); + t38.SetOptionInteger("T38Version", t38_options->T38FaxMaxDatagram); + t38.SetOptionValue("T38FaxUdpEC", t38_options->T38FaxUdpEC); + } + + m_switchMediaFormats += t38; +#endif // HAVE_T38 } @@ -952,6 +975,23 @@ switch_status_t FSConnection::receive_message(switch_core_session_message_t *msg ownerCall.Transfer(msg->string_arg, GetOtherPartyConnection()); break; +#if HAVE_T38 + case SWITCH_MESSAGE_INDICATE_REQUEST_IMAGE_MEDIA: + { + PSafePtr other = GetOtherPartyConnection(); + if (other == NULL || !other->SwitchT38(true)) { + PTRACE(1, "mod_opal\tMode change request to T.38 failed"); + } + break; + } + + case SWITCH_MESSAGE_INDICATE_T38_DESCRIPTION: + break; + + case SWITCH_MESSAGE_INDICATE_UDPTL_MODE: + break; +#endif // HAVE_T38 + default: PTRACE(3, "mod_opal\tReceived unhandled message " << msg->message_id << " on connection " << *this); } @@ -977,6 +1017,76 @@ bool FSConnection::WaitForMedia() } +#if HAVE_T38 +void FSConnection::SetT38OptionsFromMediaFormat(const OpalMediaFormat & mediaFormat) +{ + switch_t38_options_t *t38_options = (switch_t38_options_t *)switch_channel_get_private(m_fsChannel, "t38_options"); + if (t38_options == NULL) + t38_options = (switch_t38_options_t *)switch_core_session_alloc(m_fsSession, sizeof(switch_t38_options_t)); + + PString value; + mediaFormat.GetOptionValue("T38FaxRateManagement", value); + t38_options->T38FaxRateManagement = switch_core_session_strdup(m_fsSession, value); + + mediaFormat.GetOptionValue("T38FaxUdpEC", value); + t38_options->T38FaxUdpEC = switch_core_session_strdup(m_fsSession, value); + + t38_options->T38MaxBitRate = mediaFormat.GetOptionInteger("T38MaxBitRate", 9600); + t38_options->T38FaxMaxBuffer = mediaFormat.GetOptionInteger("T38FaxMaxBuffer", 2000); + t38_options->T38FaxMaxDatagram = mediaFormat.GetOptionInteger("T38FaxMaxDatagram", 528); + + t38_options->T38FaxFillBitRemoval = mediaFormat.GetOptionBoolean("T38FaxFillBitRemoval") ? SWITCH_TRUE : SWITCH_FALSE; + t38_options->T38FaxTranscodingMMR = mediaFormat.GetOptionBoolean("T38FaxTranscodingMMR") ? SWITCH_TRUE : SWITCH_FALSE; + t38_options->T38FaxTranscodingJBIG = mediaFormat.GetOptionBoolean("T38FaxTranscodingJBIG") ? SWITCH_TRUE : SWITCH_FALSE; + + t38_options->T38VendorInfo = switch_core_session_strdup(m_fsSession, mediaFormat.GetOptionString("T38VendorInfo")); + + //t38_options->remote_ip = switch_core_session_strdup(session, mediaFormat.something); + //t38_options->remote_port = mediaFormat.something; + + switch_channel_set_private(m_fsChannel, "t38_options", t38_options); +} + + +void FSConnection::OnSwitchedT38(bool toT38, bool success) +{ + PTRACE(3, "mod_opal\tMode change request to fax succeeded"); + OnSwitchingT38(toT38 && success); +} + + +void FSConnection::OnSwitchingT38(bool toT38) +{ + if (!toT38) { + switch_channel_set_private(m_fsChannel, "t38_options", NULL); + switch_channel_clear_app_flag_key("T38", m_fsChannel, CF_APP_T38); + switch_channel_clear_app_flag_key("T38", m_fsChannel, CF_APP_T38_REQ); + switch_channel_set_app_flag_key("T38", m_fsChannel, CF_APP_T38_FAIL); + return; + } + + PSafePtr other = GetOtherPartyConnection(); + if (other == NULL) + return; + + OpalMediaFormatList otherFormats = other->GetMediaFormats(); + OpalMediaFormatList::const_iterator t38 = otherFormats.FindFormat(OpalT38); + if (t38 == otherFormats.end()) + return; + + SetT38OptionsFromMediaFormat(*t38); + + switch_channel_set_variable(m_fsChannel, "has_t38", "true"); + switch_channel_set_app_flag_key("T38", m_fsChannel, CF_APP_T38); + + switch_channel_execute_on(m_fsChannel, "opal_execute_on_image"); + switch_channel_api_on(m_fsChannel, "opal_api_on_image"); + + return; +} +#endif // HAVE_T38 + + switch_status_t FSConnection::receive_event(switch_event_t *event) { PTRACE(4, "mod_opal\tReceived event " << event->event_id << " on connection " << *this); @@ -1052,11 +1162,19 @@ PBoolean FSMediaStream::Open() return false; bool isAudio; - if (mediaFormat.GetMediaType() == OpalMediaType::Audio()) { + OpalMediaType mediaType = mediaFormat.GetMediaType(); + if (mediaType == OpalMediaType::Audio()) isAudio = true; - } else if (mediaFormat.GetMediaType() == OpalMediaType::Video()) { + else if (mediaType == OpalMediaType::Video()) isAudio = false; - } else { +#if HAVE_T38 + else if (mediaType == OpalMediaType::Fax()) { + m_readFrame.flags = SFF_UDPTL_PACKET; + return true; + } +#endif + else { + PTRACE(1, "mod_opal\tUnsupported media type: " << mediaType); return false; } @@ -1083,13 +1201,13 @@ PBoolean FSMediaStream::Open() switch_core_session_get_pool(fsSession)) != SWITCH_STATUS_SUCCESS) { PTRACE(1, "mod_opal\t" << switch_channel_get_name(fsChannel) << " cannot initialise " << (IsSink()? "read" : "write") << ' ' - << mediaFormat.GetMediaType() << " codec " << mediaFormat << " for connection " << *this); + << mediaType << " codec " << mediaFormat << " for connection " << *this); switch_channel_hangup(fsChannel, SWITCH_CAUSE_INCOMPATIBLE_DESTINATION); return false; } PTRACE(2, "mod_opal\t" << switch_channel_get_name(fsChannel) << " unsupported ptime of " << ptime << " on " << (IsSink()? "read" : "write") << ' ' - << mediaFormat.GetMediaType() << " codec " << mediaFormat << " for connection " << *this); + << mediaType << " codec " << mediaFormat << " for connection " << *this); } if (IsSink()) { @@ -1102,7 +1220,7 @@ PBoolean FSMediaStream::Open() switch_core_session_get_pool(fsSession)) != SWITCH_STATUS_SUCCESS) { PTRACE(1, "mod_opal\t" << switch_channel_get_name(fsChannel) << " timer init failed on " << (IsSink()? "read" : "write") << ' ' - << mediaFormat.GetMediaType() << " codec " << mediaFormat << " for connection " << *this); + << mediaType << " codec " << mediaFormat << " for connection " << *this); switch_core_codec_destroy(m_switchCodec); m_switchCodec = NULL; return false; @@ -1122,7 +1240,7 @@ PBoolean FSMediaStream::Open() PTRACE(3, "mod_opal\t" << switch_channel_get_name(fsChannel) << " initialised " << (IsSink()? "read" : "write") << ' ' - << mediaFormat.GetMediaType() << " codec " << mediaFormat << " for connection " << *this); + << mediaType << " codec " << mediaFormat << " for connection " << *this); return OpalMediaStream::Open(); } @@ -1207,9 +1325,14 @@ switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag } } + *frame = &m_readFrame; + m_readFrame.packet = m_readRTP.GetPointer(); m_readFrame.packetlen = m_readRTP.GetHeaderSize() + m_readFrame.datalen; + if ((m_readFrame.flags & (SFF_UDPTL_PACKET|SFF_RAW_RTP)) != 0) + return SWITCH_STATUS_SUCCESS; + #if IMPLEMENT_MULTI_FAME_AUDIO // Repackage frames in incoming packet to agree with what FS expects. // Not implmented yet!!!!!!!!! @@ -1228,8 +1351,6 @@ switch_status_t FSMediaStream::read_frame(switch_frame_t **frame, switch_io_flag m_readFrame.payload == RTP_DataFrame::CN || m_readFrame.payload == RTP_DataFrame::Cisco_CN ? SFF_CNG : 0; - *frame = &m_readFrame; - return SWITCH_STATUS_SUCCESS; } @@ -1244,7 +1365,7 @@ switch_status_t FSMediaStream::write_frame(const switch_frame_t *frame, switch_i return SWITCH_STATUS_SUCCESS; } - if ((frame->flags & SFF_RAW_RTP) != 0) { + if ((frame->flags & (SFF_UDPTL_PACKET|SFF_RAW_RTP)) != 0) { RTP_DataFrame rtp((const BYTE *)frame->packet, frame->packetlen, false); return mediaPatch->PushFrame(rtp) ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; } @@ -1257,11 +1378,11 @@ switch_status_t FSMediaStream::write_frame(const switch_frame_t *frame, switch_i /* Not sure what FS is going to give us! Suspect it depends on the mod on the other side sending it. */ if (frame->timestamp != 0) - timestamp = frame->timestamp; + timestamp = frame->timestamp; else if (frame->samples != 0) - timestamp += frame->samples; + timestamp += frame->samples; else - timestamp += m_switchCodec->implementation->samples_per_packet; + timestamp += m_switchCodec->implementation->samples_per_packet; rtp.SetTimestamp(timestamp); return mediaPatch->PushFrame(rtp) ? SWITCH_STATUS_SUCCESS : SWITCH_STATUS_FALSE; diff --git a/src/mod/endpoints/mod_opal/mod_opal.h b/src/mod/endpoints/mod_opal/mod_opal.h index 5edc21f8a0..2b7a553d51 100644 --- a/src/mod/endpoints/mod_opal/mod_opal.h +++ b/src/mod/endpoints/mod_opal/mod_opal.h @@ -77,6 +77,12 @@ #define MODNAME "mod_opal" +#ifndef OPAL_CHECK_VERSION + #define OPAL_CHECK_VERSION(a,b,c) 0 +#endif + +#define HAVE_T38 (OPAL_CHECK_VERSION(3,11,2) && OPAL_T38_CAPABILITY) + class FSEndPoint; class FSManager; @@ -248,6 +254,10 @@ class FSConnection : public OpalLocalConnection virtual void OnPatchMediaStream(PBoolean isSource, OpalMediaPatch & patch); virtual OpalMediaFormatList GetMediaFormats() const; virtual PBoolean SendUserInputTone(char tone, unsigned duration); +#if HAVE_T38 + virtual void OnSwitchedT38(bool toT38, bool success); + virtual void OnSwitchingT38(bool toT38); +#endif DECLARE_CALLBACK0(on_init); DECLARE_CALLBACK0(on_destroy); @@ -294,6 +304,9 @@ class FSConnection : public OpalLocalConnection protected: void SetCodecs(); bool WaitForMedia(); +#if HAVE_T38 + void SetT38OptionsFromMediaFormat(const OpalMediaFormat & mediaFormat); +#endif switch_status_t read_frame(const OpalMediaType & mediaType, switch_frame_t **frame, switch_io_flag_t flags); switch_status_t write_frame(const OpalMediaType & mediaType, const switch_frame_t *frame, switch_io_flag_t flags);