1 /*
2 * t38proto.cxx
3 *
4 * T.38 protocol handler
5 *
6 * Open Phone Abstraction Library
7 *
8 * Copyright (c) 1998-2002 Equivalence Pty. Ltd.
9 *
10 * The contents of this file are subject to the Mozilla Public License
11 * Version 1.0 (the "License"); you may not use this file except in
12 * compliance with the License. You may obtain a copy of the License at
13 * http://www.mozilla.org/MPL/
14 *
15 * Software distributed under the License is distributed on an "AS IS"
16 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
17 * the License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * The Original Code is Open H323 Library.
21 *
22 * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
23 *
24 * Contributor(s): Vyacheslav Frolov.
25 *
26 * $Revision: 28445 $
27 * $Author: rjongbloed $
28 * $Date: 2012-10-02 20:11:02 -0500 (Tue, 02 Oct 2012) $
29 */
30
31 #include <ptlib.h>
32
33 #ifdef __GNUC__
34 #pragma implementation "t38proto.h"
35 #endif
36
37 #include <opal/buildopts.h>
38
39 #include <t38/t38proto.h>
40 #include <opal/patch.h>
41 #include <codec/opalpluginmgr.h>
42
43
44 /////////////////////////////////////////////////////////////////////////////
45
46 #if OPAL_FAX
47
48 #include <asn/t38.h>
49
50 OPAL_DEFINE_MEDIA_COMMAND(OpalFaxTerminate, PLUGINCODEC_CONTROL_TERMINATE_CODEC);
51
52 #define new PNEW
53
54
55 static const char TIFF_File_FormatName[] = OPAL_FAX_TIFF_FILE;
56
57
58 /////////////////////////////////////////////////////////////////////////////
59
60 class OpalFaxMediaStream : public OpalNullMediaStream
61 {
62 public:
OpalFaxMediaStream(OpalFaxConnection & conn,const OpalMediaFormat & mediaFormat,unsigned sessionID,bool isSource)63 OpalFaxMediaStream(OpalFaxConnection & conn,
64 const OpalMediaFormat & mediaFormat,
65 unsigned sessionID,
66 bool isSource)
67 : OpalNullMediaStream(conn, mediaFormat, sessionID, isSource, isSource, true)
68 , m_connection(conn)
69 {
70 m_isAudio = true; // Even though we are not REALLY audio, act like we are
71 }
72
InternalClose()73 virtual void InternalClose()
74 {
75 if (m_connection.m_state == OpalFaxConnection::e_CompletedSwitch &&
76 m_connection.m_finalStatistics.m_fax.m_result < 0) {
77 // make a referenced copy so can't be deleted out from under us
78 PatchPtr patch = m_mediaPatch;
79 if (patch != NULL)
80 patch->ExecuteCommand(OpalFaxTerminate(), false);
81 GetStatistics(m_connection.m_finalStatistics);
82 PTRACE(4, "FAX\tGot final statistics: result=" << m_connection.m_finalStatistics.m_fax.m_result);
83 }
84
85 OpalNullMediaStream::InternalClose();
86 }
87
88 private:
89 OpalFaxConnection & m_connection;
90 };
91
92
93 /////////////////////////////////////////////////////////////////////////////
94
95 class T38PseudoRTP_Handler : public RTP_Encoding
96 {
97 public:
T38PseudoRTP_Handler()98 T38PseudoRTP_Handler()
99 {
100 PStringToString options;
101
102 options.SetAt("T38-UDPTL-Redundancy", "32767:1"); // re-send all ifp packets 1 time
103 options.SetAt("T38-UDPTL-Redundancy-Interval", "0"); // re-send redundancy ifp packets only with next ifp
104 options.SetAt("T38-UDPTL-Keep-Alive-Interval", "0"); // no send keep-alive packets
105 options.SetAt("T38-UDPTL-Optimise-On-Retransmit", "false"); // not optimise udptl packets on retransmit
106
107 ApplyStringOptions(options);
108 }
109
110
OnStart(RTP_Session & _rtpUDP)111 void OnStart(RTP_Session & _rtpUDP)
112 {
113 RTP_Encoding::OnStart(_rtpUDP);
114
115 rtpUDP->SetJitterBufferSize(0, 0);
116 m_consecutiveBadPackets = 0;
117 m_awaitingGoodPacket = true;
118 m_expectedSequenceNumber = 0;
119 m_secondaryPacket = -1;
120
121 m_sentPacketRedundancy.clear();
122 m_sentPacket = T38_UDPTLPacket();
123 m_sentPacket.m_error_recovery.SetTag(T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets);
124 m_sentPacket.m_seq_number = (unsigned)-1;
125 rtpUDP->SetNextSentSequenceNumber(0);
126 }
127
128
ApplyStringOptions(const PStringToString & stringOptions)129 void ApplyStringOptions(const PStringToString & stringOptions)
130 {
131 for (PINDEX i = 0 ; i < stringOptions.GetSize() ; i++) {
132 PCaselessString key = stringOptions.GetKeyAt(i);
133
134 if (key == "T38-UDPTL-Redundancy") {
135 PStringArray value = stringOptions.GetDataAt(i).Tokenise(",", FALSE);
136 PWaitAndSignal mutex(m_writeMutex);
137
138 m_redundancy.clear();
139
140 for (PINDEX i = 0 ; i < value.GetSize() ; i++) {
141 PStringArray pair = value[i].Tokenise(":", FALSE);
142
143 if (pair.GetSize() == 2) {
144 long size = pair[0].AsInteger();
145 long redundancy = pair[1].AsInteger();
146
147 if (size > INT_MAX)
148 size = INT_MAX;
149
150 if (size > 0 && redundancy >= 0) {
151 m_redundancy[(int)size] = (int)redundancy;
152 continue;
153 }
154 }
155
156 PTRACE(2, "T38_UDPTL\tIgnored redundancy \"" << value[i] << '"');
157 }
158
159 #if PTRACING
160 if (PTrace::CanTrace(3)) {
161 ostream & trace = PTrace::Begin(3, __FILE__, __LINE__);
162 trace << "T38_UDPTL\tUse redundancy \"";
163 for (std::map<int, int>::iterator it = m_redundancy.begin() ; it != m_redundancy.end() ; it++) {
164 if (it != m_redundancy.begin())
165 trace << ",";
166 trace << it->first << ':' << it->second;
167 }
168 trace << '"' << PTrace::End;
169 }
170 #endif
171 }
172 else
173 if (key == "T38-UDPTL-Redundancy-Interval") {
174 PWaitAndSignal mutex(m_writeMutex);
175 m_redundancyInterval = stringOptions.GetDataAt(i).AsUnsigned();
176 PTRACE(3, "T38_UDPTL\tUse redundancy interval " << m_redundancyInterval);
177 }
178 else
179 if (key == "T38-UDPTL-Keep-Alive-Interval") {
180 PWaitAndSignal mutex(m_writeMutex);
181 m_keepAliveInterval = stringOptions.GetDataAt(i).AsUnsigned();
182 PTRACE(3, "T38_UDPTL\tUse keep-alive interval " << m_keepAliveInterval);
183 }
184 else
185 if (key == "T38-UDPTL-Optimise-On-Retransmit") {
186 PCaselessString value = stringOptions.GetDataAt(i);
187 PWaitAndSignal mutex(m_writeMutex);
188
189 m_optimiseOnRetransmit =
190 (value.IsEmpty() || (value == "true") || (value == "yes") || value.AsInteger() != 0);
191
192 PTRACE(3, "T38_UDPTL\tUse optimise on retransmit - " << (m_optimiseOnRetransmit ? "true" : "false"));
193 }
194 else {
195 PTRACE(4, "T38_UDPTL\tIgnored option " << key << " = \"" << stringOptions.GetDataAt(i) << '"');
196 }
197 }
198 }
199
200
WriteData(RTP_DataFrame & frame,bool oob)201 PBoolean WriteData(RTP_DataFrame & frame, bool oob)
202 {
203 if (oob)
204 return false;
205
206 return RTP_Encoding::WriteData(frame, false);
207 }
208
209
OnSendData(RTP_DataFrame & frame)210 RTP_Session::SendReceiveStatus OnSendData(RTP_DataFrame & frame)
211 {
212 RTP_Session::SendReceiveStatus status = RTP_Encoding::OnSendData(frame);
213
214 if (status == RTP_Session::e_ProcessPacket && frame.GetPayloadSize() == 0)
215 status = RTP_Session::e_IgnorePacket;
216
217 return status;
218 }
219
220
WriteDataPDU(RTP_DataFrame & frame)221 bool WriteDataPDU(RTP_DataFrame & frame)
222 {
223 PINDEX plLen = frame.GetPayloadSize();
224
225 if (plLen == 0) {
226 PTRACE(2, "T38_UDPTL\tInternal error - empty payload");
227 return false;
228 }
229
230 PWaitAndSignal mutex(m_writeMutex);
231
232 if (!m_sentPacketRedundancy.empty()) {
233 T38_UDPTLPacket_error_recovery &recovery = m_sentPacket.m_error_recovery;
234
235 if (recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
236 // shift old primary ifp packet to secondary list
237
238 T38_UDPTLPacket_error_recovery_secondary_ifp_packets &secondary = recovery;
239
240 if (secondary.SetSize(secondary.GetSize() + 1)) {
241 for (int i = secondary.GetSize() - 2 ; i >= 0 ; i--) {
242 secondary[i + 1] = secondary[i];
243 secondary[i] = T38_UDPTLPacket_error_recovery_secondary_ifp_packets_subtype();
244 }
245
246 secondary[0].SetValue(m_sentPacket.m_primary_ifp_packet.GetValue());
247 m_sentPacket.m_primary_ifp_packet = T38_UDPTLPacket_primary_ifp_packet();
248 }
249 } else {
250 PTRACE(3, "T38_UDPTL\tNot implemented yet " << recovery.GetTagName());
251 }
252 }
253
254 // calculate redundancy for new ifp packet
255
256 int redundancy = 0;
257
258 for (std::map<int, int>::iterator it = m_redundancy.begin() ; it != m_redundancy.end() ; it++) {
259 if (plLen <= it->first) {
260 if (redundancy < it->second)
261 redundancy = it->second;
262
263 break;
264 }
265 }
266
267 if (redundancy > 0 || !m_sentPacketRedundancy.empty())
268 m_sentPacketRedundancy.insert(m_sentPacketRedundancy.begin(), redundancy + 1);
269
270 // set new primary ifp packet
271
272 m_sentPacket.m_seq_number = frame.GetSequenceNumber();
273 m_sentPacket.m_primary_ifp_packet.SetValue(frame.GetPayloadPtr(), plLen);
274
275 bool ok = WriteUDPTL();
276
277 DecrementSentPacketRedundancy(true);
278
279 return ok;
280 }
281
282
OnWriteDataIdle()283 void OnWriteDataIdle()
284 {
285 PWaitAndSignal mutex(m_writeMutex);
286
287 WriteUDPTL();
288
289 DecrementSentPacketRedundancy(m_optimiseOnRetransmit);
290 }
291
292
SetWriteDataIdleTimer(PTimer & timer)293 void SetWriteDataIdleTimer(PTimer & timer)
294 {
295 PWaitAndSignal mutex(m_writeMutex);
296
297 if (m_sentPacketRedundancy.empty() || m_redundancyInterval <= 0)
298 timer = m_keepAliveInterval;
299 else
300 timer = m_redundancyInterval;
301 }
302
303
DecrementSentPacketRedundancy(bool stripRedundancy)304 void DecrementSentPacketRedundancy(bool stripRedundancy)
305 {
306 int iMax = (int)m_sentPacketRedundancy.size() - 1;
307
308 for (int i = iMax ; i >= 0 ; i--) {
309 m_sentPacketRedundancy[i]--;
310
311 if (i == iMax && m_sentPacketRedundancy[i] <= 0)
312 iMax--;
313 }
314
315 m_sentPacketRedundancy.resize(iMax + 1);
316
317 if (stripRedundancy) {
318 T38_UDPTLPacket_error_recovery &recovery = m_sentPacket.m_error_recovery;
319
320 if (recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
321 T38_UDPTLPacket_error_recovery_secondary_ifp_packets &secondary = recovery;
322 secondary.SetSize(iMax > 0 ? iMax : 0);
323 } else {
324 PTRACE(3, "T38_UDPTL\tNot implemented yet " << recovery.GetTagName());
325 }
326 }
327 }
328
329
WriteUDPTL()330 bool WriteUDPTL()
331 {
332 PTRACE(5, "T38_UDPTL\tEncoded transmitted UDPTL data :\n " << setprecision(2) << m_sentPacket);
333
334 PPER_Stream rawData;
335 m_sentPacket.Encode(rawData);
336 rawData.CompleteEncoding();
337
338 PTRACE(4, "T38_UDPTL\tSending UDPTL of size " << rawData.GetSize());
339
340 return rtpUDP->WriteDataOrControlPDU(rawData.GetPointer(), rawData.GetSize(), true);
341 }
342
343
OnSendControl(RTP_ControlFrame &,PINDEX &)344 RTP_Session::SendReceiveStatus OnSendControl(RTP_ControlFrame & /*frame*/, PINDEX & /*len*/)
345 {
346 return RTP_Session::e_IgnorePacket; // Non fatal error, just ignore
347 }
348
349
WaitForPDU(PUDPSocket & dataSocket,PUDPSocket & controlSocket,const PTimeInterval &)350 int WaitForPDU(PUDPSocket & dataSocket, PUDPSocket & controlSocket, const PTimeInterval &)
351 {
352 if (m_secondaryPacket >= 0)
353 return -1; // Force immediate call to ReadDataPDU
354
355 // Break out once a second so closes down in orderly fashion
356 return PSocket::Select(dataSocket, controlSocket, 1000);
357 }
358
359
OnReadTimeout(RTP_DataFrame & frame)360 RTP_Session::SendReceiveStatus OnReadTimeout(RTP_DataFrame & frame)
361 {
362 // Override so do not do sender reports (RTP only) and push
363 // through a zero length packet so checks for orderly shut down
364 frame.SetPayloadSize(0);
365 return RTP_Session::e_ProcessPacket;
366 }
367
368
SetFrameFromIFP(RTP_DataFrame & frame,const PASN_OctetString & ifp,unsigned sequenceNumber)369 void SetFrameFromIFP(RTP_DataFrame & frame, const PASN_OctetString & ifp, unsigned sequenceNumber)
370 {
371 frame.SetPayloadSize(ifp.GetDataLength());
372 memcpy(frame.GetPayloadPtr(), (const BYTE *)ifp, ifp.GetDataLength());
373 frame.SetSequenceNumber((WORD)(sequenceNumber & 0xffff));
374 if (m_secondaryPacket <= 0)
375 m_expectedSequenceNumber = sequenceNumber+1;
376 }
377
ReadDataPDU(RTP_DataFrame & frame)378 RTP_Session::SendReceiveStatus ReadDataPDU(RTP_DataFrame & frame)
379 {
380 if (m_secondaryPacket >= 0) {
381 if (m_secondaryPacket == 0)
382 SetFrameFromIFP(frame, m_receivedPacket.m_primary_ifp_packet, m_receivedPacket.m_seq_number);
383 else {
384 T38_UDPTLPacket_error_recovery_secondary_ifp_packets & secondaryPackets = m_receivedPacket.m_error_recovery;
385 SetFrameFromIFP(frame, secondaryPackets[m_secondaryPacket-1], m_receivedPacket.m_seq_number - m_secondaryPacket);
386 }
387 --m_secondaryPacket;
388 return RTP_Session::e_ProcessPacket;
389 }
390
391 BYTE thisUDPTL[500];
392 RTP_Session::SendReceiveStatus status = rtpUDP->ReadDataOrControlPDU(thisUDPTL, sizeof(thisUDPTL), true);
393 if (status != RTP_Session::e_ProcessPacket)
394 return status;
395
396 PINDEX pduSize = rtpUDP->GetDataSocket().GetLastReadCount();
397
398 PTRACE(4, "T38_UDPTL\tRead UDPTL of size " << pduSize);
399
400 PPER_Stream rawData(thisUDPTL, pduSize);
401
402 // Decode the PDU, but not if still receiving RTP
403 bool decodeBad = !m_receivedPacket.Decode(rawData);
404 if (decodeBad || (m_awaitingGoodPacket && m_receivedPacket.m_seq_number >= 32768)) {
405 if (++m_consecutiveBadPackets > 1000) {
406 PTRACE(1, "T38_UDPTL\tRaw data decode failed 1000 times, remote probably not switched from audio, aborting!");
407 return RTP_Session::e_AbortTransport;
408 }
409
410 #if PTRACING
411 static const unsigned Level = 2;
412 if (PTrace::CanTrace(Level)) {
413 ostream & trace = PTrace::Begin(Level, __FILE__, __LINE__);
414 trace << "T38_UDPTL\t";
415 if (m_awaitingGoodPacket)
416 trace << "Probable RTP packet: " << rawData.GetSize() << " bytes.";
417 else
418 trace << "Raw data decode failure:\n "
419 << setprecision(2) << rawData << "\n UDPTL = "
420 << setprecision(2) << m_receivedPacket;
421 trace << PTrace::End;
422 }
423 #endif
424
425 return RTP_Session::e_IgnorePacket;
426 }
427
428 PTRACE_IF(3, m_awaitingGoodPacket, "T38_UDPTL\tFirst decoded UDPTL packet");
429 m_awaitingGoodPacket = false;
430 m_consecutiveBadPackets = 0;
431
432 PTRACE(5, "T38_UDPTL\tDecoded UDPTL packet:\n " << setprecision(2) << m_receivedPacket);
433
434 int missing = m_receivedPacket.m_seq_number - m_expectedSequenceNumber;
435 if (missing > 0 && m_receivedPacket.m_error_recovery.GetTag() == T38_UDPTLPacket_error_recovery::e_secondary_ifp_packets) {
436 // Packets are missing and we have redundency in the UDPTL packets
437 T38_UDPTLPacket_error_recovery_secondary_ifp_packets & secondaryPackets = m_receivedPacket.m_error_recovery;
438 if (secondaryPackets.GetSize() > 0) {
439 PTRACE(4, "T38_UDPTL\tUsing redundant data to reconstruct missing/out of order packet at SN=" << m_expectedSequenceNumber);
440 m_secondaryPacket = missing;
441 if (m_secondaryPacket > secondaryPackets.GetSize())
442 m_secondaryPacket = secondaryPackets.GetSize();
443 SetFrameFromIFP(frame, secondaryPackets[m_secondaryPacket-1], m_receivedPacket.m_seq_number - m_secondaryPacket);
444 --m_secondaryPacket;
445 return RTP_Session::e_ProcessPacket;
446 }
447 }
448
449 SetFrameFromIFP(frame, m_receivedPacket.m_primary_ifp_packet, m_receivedPacket.m_seq_number);
450 m_expectedSequenceNumber = m_receivedPacket.m_seq_number+1;
451
452 return RTP_Session::e_ProcessPacket;
453 }
454
455
456 protected:
457 int m_consecutiveBadPackets;
458 bool m_awaitingGoodPacket;
459 T38_UDPTLPacket m_receivedPacket;
460 unsigned m_expectedSequenceNumber;
461 int m_secondaryPacket;
462
463 std::map<int, int> m_redundancy;
464 PTimeInterval m_redundancyInterval;
465 PTimeInterval m_keepAliveInterval;
466 bool m_optimiseOnRetransmit;
467 std::vector<int> m_sentPacketRedundancy;
468 T38_UDPTLPacket m_sentPacket;
469 PMutex m_writeMutex;
470 };
471
472
473 PFACTORY_CREATE(PFactory<RTP_Encoding>, T38PseudoRTP_Handler, "udptl", false);
474
475
476 /////////////////////////////////////////////////////////////////////////////
477
OpalFaxEndPoint(OpalManager & mgr,const char * g711Prefix,const char * t38Prefix)478 OpalFaxEndPoint::OpalFaxEndPoint(OpalManager & mgr, const char * g711Prefix, const char * t38Prefix)
479 : OpalLocalEndPoint(mgr, g711Prefix)
480 , m_t38Prefix(t38Prefix)
481 , m_defaultDirectory(".")
482 {
483 if (t38Prefix != NULL)
484 mgr.AttachEndPoint(this, m_t38Prefix);
485
486 PTRACE(3, "Fax\tCreated Fax endpoint");
487 }
488
489
~OpalFaxEndPoint()490 OpalFaxEndPoint::~OpalFaxEndPoint()
491 {
492 PTRACE(3, "Fax\tDeleted Fax endpoint.");
493 }
494
495
MakeConnection(OpalCall & call,const PString & remoteParty,void * userData,unsigned int,OpalConnection::StringOptions * stringOptions)496 PSafePtr<OpalConnection> OpalFaxEndPoint::MakeConnection(OpalCall & call,
497 const PString & remoteParty,
498 void * userData,
499 unsigned int /*options*/,
500 OpalConnection::StringOptions * stringOptions)
501 {
502 if (!OpalMediaFormat(TIFF_File_FormatName).IsValid()) {
503 PTRACE(1, "TIFF File format not valid! Missing plugin?");
504 return NULL;
505 }
506
507 PINDEX prefixLength = remoteParty.Find(':');
508 PStringArray tokens = remoteParty.Mid(prefixLength+1).Tokenise(";", true);
509 if (tokens.IsEmpty()) {
510 PTRACE(2, "Fax\tNo filename specified!");
511 return NULL;
512 }
513
514 bool receiving = false;
515 PString stationId = GetDefaultDisplayName();
516
517 for (PINDEX i = 1; i < tokens.GetSize(); ++i) {
518 if (tokens[i] *= "receive")
519 receiving = true;
520 else if (tokens[i].Left(10) *= "stationid=")
521 stationId = tokens[i].Mid(10);
522 }
523
524 PString filename = tokens[0];
525 if (!PFilePath::IsAbsolutePath(filename))
526 filename.Splice(m_defaultDirectory, 0);
527
528 if (!receiving && !PFile::Exists(filename)) {
529 PTRACE(2, "Fax\tCannot find filename '" << filename << "'");
530 return NULL;
531 }
532
533 OpalConnection::StringOptions localOptions;
534 if (stringOptions == NULL)
535 stringOptions = &localOptions;
536
537 if ((*stringOptions)("stationid").IsEmpty())
538 stringOptions->SetAt("stationid", stationId);
539
540 stringOptions->SetAt(OPAL_OPT_DISABLE_JITTER, "1");
541
542 return AddConnection(CreateConnection(call, userData, stringOptions, filename, receiving,
543 remoteParty.Left(prefixLength) *= GetPrefixName()));
544 }
545
546
IsAvailable() const547 bool OpalFaxEndPoint::IsAvailable() const
548 {
549 return OpalMediaFormat(OPAL_FAX_TIFF_FILE).IsValid();
550 }
551
552
CreateConnection(OpalCall & call,void *,OpalConnection::StringOptions * stringOptions,const PString & filename,bool receiving,bool disableT38)553 OpalFaxConnection * OpalFaxEndPoint::CreateConnection(OpalCall & call,
554 void * /*userData*/,
555 OpalConnection::StringOptions * stringOptions,
556 const PString & filename,
557 bool receiving,
558 bool disableT38)
559 {
560 return new OpalFaxConnection(call, *this, filename, receiving, disableT38, stringOptions);
561 }
562
563
GetMediaFormats() const564 OpalMediaFormatList OpalFaxEndPoint::GetMediaFormats() const
565 {
566 OpalMediaFormatList formats;
567 formats += OpalT38;
568 formats += TIFF_File_FormatName;
569 PTRACE(4, "OpalFaxEndPoint\tGetMediaFormats for " << *this << "\n " << setfill(',') << formats << setfill(' '));
570 return formats;
571 }
572
573
OnFaxCompleted(OpalFaxConnection & connection,bool PTRACE_PARAM (failed))574 void OpalFaxEndPoint::OnFaxCompleted(OpalFaxConnection & connection, bool PTRACE_PARAM(failed))
575 {
576 PTRACE(3, "FAX\tFax " << (failed ? "failed" : "completed") << " on connection: " << connection);
577 connection.Release();
578 }
579
580
581 /////////////////////////////////////////////////////////////////////////////
582
OpalFaxConnection(OpalCall & call,OpalFaxEndPoint & ep,const PString & filename,bool receiving,bool disableT38,OpalConnection::StringOptions * stringOptions)583 OpalFaxConnection::OpalFaxConnection(OpalCall & call,
584 OpalFaxEndPoint & ep,
585 const PString & filename,
586 bool receiving,
587 bool disableT38,
588 OpalConnection::StringOptions * stringOptions)
589 : OpalLocalConnection(call, ep, NULL, 0, stringOptions, 'F')
590 , m_endpoint(ep)
591 , m_filename(filename)
592 , m_receiving(receiving)
593 , m_disableT38(disableT38)
594 , m_tiffFileFormat(TIFF_File_FormatName)
595 , m_state(disableT38 ? e_CompletedSwitch : e_AwaitingSwitchToT38)
596 {
597 SetFaxMediaFormatOptions(m_tiffFileFormat);
598
599 m_switchTimer.SetNotifier(PCREATE_NOTIFIER(OnSwitchTimeout));
600
601 PTRACE(3, "FAX\tCreated fax connection with token \"" << callToken << "\","
602 " receiving=" << receiving << ","
603 " disabledT38=" << disableT38 << ","
604 " filename=\"" << filename << '"');
605 }
606
607
~OpalFaxConnection()608 OpalFaxConnection::~OpalFaxConnection()
609 {
610 PTRACE(3, "FAX\tDeleted FAX connection.");
611 }
612
613
GetPrefixName() const614 PString OpalFaxConnection::GetPrefixName() const
615 {
616 return m_disableT38 ? m_endpoint.GetPrefixName() : m_endpoint.GetT38Prefix();
617 }
618
619
GetMediaFormats() const620 OpalMediaFormatList OpalFaxConnection::GetMediaFormats() const
621 {
622 OpalMediaFormatList formats;
623
624 if (m_filename.IsEmpty())
625 formats += OpalPCM16;
626 else
627 formats += m_tiffFileFormat;
628
629 if (!m_disableT38) {
630 formats += OpalRFC2833;
631 formats += OpalCiscoNSE;
632 }
633
634 return formats;
635 }
636
637
AdjustMediaFormats(bool local,const OpalConnection * otherConnection,OpalMediaFormatList & mediaFormats) const638 void OpalFaxConnection::AdjustMediaFormats(bool local,
639 const OpalConnection * otherConnection,
640 OpalMediaFormatList & mediaFormats) const
641 {
642 // Remove everything but G.711 or fax stuff
643 OpalMediaFormatList::iterator it = mediaFormats.begin();
644 while (it != mediaFormats.end()) {
645 if ((m_state != e_SwitchingToT38 && it->GetMediaType() == OpalMediaType::Audio()) ||
646 (*it == OpalG711_ULAW_64K || *it == OpalG711_ALAW_64K || *it == OpalRFC2833 || *it == OpalCiscoNSE))
647 ++it;
648 else if (it->GetMediaType() != OpalMediaType::Fax() || (m_disableT38 && *it == OpalT38))
649 mediaFormats -= *it++;
650 else
651 SetFaxMediaFormatOptions(*it++);
652 }
653
654 OpalConnection::AdjustMediaFormats(local, otherConnection, mediaFormats);
655 }
656
657
SetFaxMediaFormatOptions(OpalMediaFormat & mediaFormat) const658 void OpalFaxConnection::SetFaxMediaFormatOptions(OpalMediaFormat & mediaFormat) const
659 {
660 mediaFormat.SetOptionString("TIFF-File-Name", m_filename);
661 mediaFormat.SetOptionBoolean("Receiving", m_receiving);
662
663 PString str = m_stringOptions(OPAL_OPT_STATION_ID);
664 if (!str.IsEmpty()) {
665 mediaFormat.SetOptionString("Station-Identifier", str);
666 PTRACE(4, "FAX\tSet Station-Identifier: \"" << str << '"');
667 }
668
669 str = m_stringOptions(OPAL_OPT_HEADER_INFO);
670 if (!str.IsEmpty()) {
671 mediaFormat.SetOptionString("Header-Info", str);
672 PTRACE(4, "FAX\tSet Header-Info: \"" << str << '"');
673 }
674 }
675
676
OnEstablished()677 void OpalFaxConnection::OnEstablished()
678 {
679 OpalConnection::OnEstablished();
680
681 // If switched and we don't need to do CNG/CED any more, or T.38 is disabled
682 // in which case the SpanDSP will deal with CNG/CED stuff.
683 if (m_state == e_AwaitingSwitchToT38) {
684 PString str = m_stringOptions(OPAL_T38_SWITCH_TIME);
685 if (!str.IsEmpty()) {
686 m_switchTimer.SetInterval(0, str.AsUnsigned());
687 PTRACE(3, "FAX\tStarting timer for auto-switch to T.38");
688 }
689 }
690 }
691
692
OnReleased()693 void OpalFaxConnection::OnReleased()
694 {
695 m_switchTimer.Stop(false);
696 OpalConnection::OnReleased();
697 }
698
699
CreateMediaStream(const OpalMediaFormat & mediaFormat,unsigned sessionID,bool isSource)700 OpalMediaStream * OpalFaxConnection::CreateMediaStream(const OpalMediaFormat & mediaFormat, unsigned sessionID, bool isSource)
701 {
702 return new OpalFaxMediaStream(*this, mediaFormat, sessionID, isSource);
703 }
704
705
OnStartMediaPatch(OpalMediaPatch & patch)706 void OpalFaxConnection::OnStartMediaPatch(OpalMediaPatch & patch)
707 {
708 // Have switched to T.38 mode
709 if (patch.GetSink()->GetMediaFormat() == OpalT38) {
710 m_switchTimer.Stop(false);
711 m_state = e_CompletedSwitch;
712 m_finalStatistics.m_fax.m_result = OpalMediaStatistics::FaxNotStarted;
713 PTRACE(4, "FAX\tStarted fax media stream for " << m_tiffFileFormat
714 << " state=" << m_state << " switch=" << m_faxMediaStreamsSwitchState);
715 }
716
717 OpalConnection::OnStartMediaPatch(patch);
718 }
719
720
OnStopMediaPatch(OpalMediaPatch & patch)721 void OpalFaxConnection::OnStopMediaPatch(OpalMediaPatch & patch)
722 {
723 // Finished the fax transmission, look for TIFF
724 OpalMediaStream & source = patch.GetSource();
725 if (source.GetMediaFormat() == m_tiffFileFormat) {
726 m_switchTimer.Stop(false);
727
728 PTRACE(4, "FAX\tStopped fax media stream for " << m_tiffFileFormat
729 << " state=" << m_state << " switch=" << m_faxMediaStreamsSwitchState);
730
731 // Not an explicit switch, so fax plug in indicated end of fax
732 if (m_state == e_CompletedSwitch && m_faxMediaStreamsSwitchState == e_NotSwitchingFaxMediaStreams) {
733 #if OPAL_STATISTICS
734 InternalGetStatistics(m_finalStatistics, true);
735 PTRACE(3, "FAX\tGot final statistics: result=" << m_finalStatistics.m_fax.m_result);
736 OnFaxCompleted(m_finalStatistics.m_fax.m_result != OpalMediaStatistics::FaxSuccessful);
737 #else
738 OnFaxCompleted(true);
739 #endif
740 }
741 }
742
743 OpalConnection::OnStopMediaPatch(patch);
744 }
745
746
SendUserInputTone(char tone,unsigned duration)747 PBoolean OpalFaxConnection::SendUserInputTone(char tone, unsigned duration)
748 {
749 OnUserInputTone(tone, duration);
750 return true;
751 }
752
753
OnUserInputTone(char tone,unsigned)754 void OpalFaxConnection::OnUserInputTone(char tone, unsigned /*duration*/)
755 {
756 // Not yet switched and got a CNG/CED from the remote system, start switch
757 if (m_state == e_AwaitingSwitchToT38 &&
758 (m_receiving ? (tone == 'X')
759 : (tone == 'Y' && m_stringOptions.GetBoolean(OPAL_SWITCH_ON_CED)))) {
760 PTRACE(3, "FAX\tRequesting mode change in response to " << (tone == 'X' ? "CNG" : "CED"));
761 PThread::Create(PCREATE_NOTIFIER(OpenFaxStreams));
762 }
763 }
764
765
OnFaxCompleted(bool failed)766 void OpalFaxConnection::OnFaxCompleted(bool failed)
767 {
768 m_endpoint.OnFaxCompleted(*this, failed);
769
770 // Prevent to reuse filename
771 m_filename.MakeEmpty();
772 }
773
774
775 #if OPAL_STATISTICS
776
GetStatistics(OpalMediaStatistics & statistics) const777 void OpalFaxConnection::GetStatistics(OpalMediaStatistics & statistics) const
778 {
779 InternalGetStatistics(statistics, false);
780 }
781
782
InternalGetStatistics(OpalMediaStatistics & statistics,bool terminate) const783 void OpalFaxConnection::InternalGetStatistics(OpalMediaStatistics & statistics, bool terminate) const
784 {
785 if (m_finalStatistics.m_fax.m_result >= 0) {
786 statistics = m_finalStatistics;
787 return;
788 }
789
790 OpalMediaStreamPtr stream;
791 if ((stream = GetMediaStream(OpalMediaType::Fax(), false)) == NULL &&
792 (stream = GetMediaStream(OpalMediaType::Fax(), true )) == NULL) {
793
794 PSafePtr<OpalConnection> other = GetOtherPartyConnection();
795 if (other == NULL) {
796 PTRACE(2, "FAX\tNo connection to get statistics.");
797 return;
798 }
799
800 if ((stream = other->GetMediaStream(OpalMediaType::Fax(), false)) == NULL &&
801 (stream = other->GetMediaStream(OpalMediaType::Fax(), true )) == NULL) {
802 PTRACE(2, "FAX\tNo stream to get statistics.");
803 return;
804 }
805 }
806
807 if (terminate)
808 stream->ExecuteCommand(OpalFaxTerminate());
809
810 stream->GetStatistics(statistics);
811 }
812
813 #endif
814
815
OnSwitchTimeout(PTimer &,INT)816 void OpalFaxConnection::OnSwitchTimeout(PTimer &, INT)
817 {
818 if (m_state == e_AwaitingSwitchToT38) {
819 PTRACE(2, "FAX\tDid not switch to T.38 mode, forcing switch");
820 PThread::Create(PCREATE_NOTIFIER(OpenFaxStreams));
821 }
822 }
823
824
SwitchFaxMediaStreams(bool toT38)825 bool OpalFaxConnection::SwitchFaxMediaStreams(bool toT38)
826 {
827 PSafePtr<OpalConnection> other = GetOtherPartyConnection();
828 if (other != NULL && other->SwitchFaxMediaStreams(toT38))
829 return true;
830
831 PTRACE(1, "FAX\tMode change request to " << (toT38 ? "T.38" : "audio") << " failed");
832 return false;
833 }
834
835
OnSwitchedFaxMediaStreams(bool toT38,bool success)836 void OpalFaxConnection::OnSwitchedFaxMediaStreams(bool toT38, bool success)
837 {
838 if (!toT38) {
839 PTRACE(3, "FAX\tMode change request to audio");
840 return;
841 }
842
843 if (success) {
844 PTRACE(3, "FAX\tMode change request to T.38 succeeded");
845 }
846 else {
847 PTRACE(4, "FAX\tMode change request to T.38 failed, falling back to G.711");
848 if (m_stringOptions.GetBoolean(OPAL_NO_G111_FAX))
849 OnFaxCompleted(true);
850 else {
851 m_disableT38 = true;
852 SwitchFaxMediaStreams(false);
853 }
854 }
855
856 m_state = e_CompletedSwitch;
857 }
858
859
OnSwitchingFaxMediaStreams(bool toT38)860 bool OpalFaxConnection::OnSwitchingFaxMediaStreams(bool toT38)
861 {
862 return !(toT38 && m_disableT38);
863 }
864
865
OpenFaxStreams(PThread &,INT)866 void OpalFaxConnection::OpenFaxStreams(PThread &, INT)
867 {
868 if (LockReadWrite()) {
869 m_state = e_SwitchingToT38;
870 if (!SwitchFaxMediaStreams(true))
871 m_state = e_CompletedSwitch; // Couldn't, don't try again.
872 UnlockReadWrite();
873 }
874 }
875
876
877 #endif // OPAL_FAX
878
879