1 /*
2  * main.cxx
3  *
4  * OPAL application source file for sending/receiving faxes via T.38
5  *
6  * Copyright (c) 2008 Vox Lucida Pty. Ltd.
7  *
8  * The contents of this file are subject to the Mozilla Public License
9  * Version 1.0 (the "License"); you may not use this file except in
10  * compliance with the License. You may obtain a copy of the License at
11  * http://www.mozilla.org/MPL/
12  *
13  * Software distributed under the License is distributed on an "AS IS"
14  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
15  * the License for the specific language governing rights and limitations
16  * under the License.
17  *
18  * The Original Code is Open Phone Abstraction Library.
19  *
20  * The Initial Developer of the Original Code is Equivalence Pty. Ltd.
21  *
22  * Contributor(s): ______________________________________.
23  *
24  * $Revision: 28445 $
25  * $Author: rjongbloed $
26  * $Date: 2012-10-02 20:11:02 -0500 (Tue, 02 Oct 2012) $
27  */
28 
29 #include "precompile.h"
30 #include "main.h"
31 
32 
33 // -ttttt c:\temp\testfax.tif sip:fax@10.0.1.11
34 // -ttttt c:\temp\incoming.tif
35 
36 PCREATE_PROCESS(FaxOPAL);
37 
38 
FaxOPAL()39 FaxOPAL::FaxOPAL()
40   : PProcess("OPAL T.38 Fax", "FaxOPAL", OPAL_MAJOR, OPAL_MINOR, ReleaseCode, OPAL_BUILD)
41   , m_manager(NULL)
42 {
43 }
44 
45 
~FaxOPAL()46 FaxOPAL::~FaxOPAL()
47 {
48   delete m_manager;
49 }
50 
51 
Main()52 void FaxOPAL::Main()
53 {
54   SetTerminationValue(1);
55 
56   m_manager = new MyManager();
57 
58   PArgList & args = GetArguments();
59 
60   if (!args.Parse(m_manager->GetArgumentSpec() +
61                   "a-audio."
62                   "A-no-audio."
63                   "d-directory:"
64                   "-station-id:"
65                   "-header-info:"
66                   "e-switch-on-ced."
67                   "F-no-fallback."
68                   "q-quiet."
69                   "T-timeout:"
70                   "v-verbose."
71                   "X-switch-time:") ||
72        args.HasOption('h') ||
73        args.GetCount() == 0) {
74     PString name = GetFile().GetTitle();
75     cerr << "usage: " << name << " [ options ] filename [ url ]\n"
76             "\n"
77             "Available options are:\n"
78             "  -d or --directory dir      : Set default directory for fax receive.\n"
79             "        --station-id id      : Set T.30 Station Identifier string.\n"
80             "        --header-info msg    : Set transmitted fax page header string.\n"
81             "  -a or --audio              : Send fax as G.711 audio.\n"
82             "  -A or --no-audio           : No audio phase at all, starts T.38 immediately.\n"
83             "  -F or --no-fallback n      : Do not fall back to audio it T.38 switch fails.\n"
84             "  -e or --switch-on-ced      : Switch to T.38 on receipt of CED tone as caller.\n"
85             "  -X or --switch-time n      : Set fail safe T.38 switch time in seconds.\n"
86             "  -T or --timeout n          : Set timeout to wait for fax rx/tx to complete in seconds.\n"
87             "  -q or --quiet              : Only output error conditions.\n"
88             "  -v or --verbose            : Output more information on call progress.\n"
89             "\n"
90          << m_manager->GetArgumentUsage()
91          << "\n"
92             "Specific T.38 format options (using -O/--option):\n"
93             "  Station-Identifier    string (\"-\"\n"
94             "  Header-Info           string (\"\")\n"
95             "  Use-ECM               bool (1)\n"
96             "  T38FaxVersion         integer (0)\n"
97             "  T38FaxRateManagement  localTCF or transferredTCF (transferredTCF) \n"
98             "  T38MaxBitRate         integer (14400)\n"
99             "  T38FaxMaxBuffer       integer (2000\n"
100             "  T38FaxMaxDatagram     integer (528)\n"
101             "  T38FaxUdpEC           t38UDPFEC or t38UDPRedundancy (t38UDPRedundancy)\n"
102             "  T38FaxFillBitRemoval  bool (0)\n"
103             "  T38FaxTranscodingMMR  bool (0)\n"
104             "  T38FaxTranscodingJBIG bool (0)\n"
105             "\n"
106             "e.g. " << name << " --option 'T.38:Header-Info=My custom header line' send_fax.tif sip:fred@bloggs.com\n"
107             "\n"
108             "     " << name << " received_fax.tif\n\n";
109     return;
110   }
111 
112   static char const * FormatMask[] = { "!G.711*", "!@fax" };
113   m_manager->SetMediaFormatMask(PStringArray(PARRAYSIZE(FormatMask), FormatMask));
114 
115   PString prefix;
116 
117   if (args.HasOption('q'))
118     cout.rdbuf(NULL);
119 
120   cout << "Fax Mode: ";
121   if (args.HasOption('A')) {
122     OpalMediaType::Fax().GetDefinition()->SetAutoStart(OpalMediaType::ReceiveTransmit);
123     OpalMediaType::Audio().GetDefinition()->SetAutoStart(OpalMediaType::DontOffer);
124     cout << "Offer T.38 only";
125     prefix = "t38";
126   }
127   else if (args.HasOption('a')) {
128     cout << "Audio Only";
129     prefix = "fax";
130   }
131   else {
132     cout << "Switch to T.38";
133     prefix = "t38";
134   }
135   cout << '\n';
136 
137   if (!m_manager->Initialise(args, !args.HasOption('q'), prefix + ":" + args[0] + ";receive"))
138     return;
139 
140   // Create audio or T.38 fax endpoint.
141   MyFaxEndPoint * fax  = new MyFaxEndPoint(*m_manager);
142   if (args.HasOption('d'))
143     fax->SetDefaultDirectory(args.GetOptionString('d'));
144 
145   if (!fax->IsAvailable()) {
146     cerr << "No fax codecs, SpanDSP plug-in probably not installed." << endl;
147     return;
148   }
149 
150   OpalConnection::StringOptions stringOptions;
151 
152   if (args.HasOption("station-id")) {
153     PString str = args.GetOptionString("station-id");
154     cout << "Station Identifier: " << str << '\n';
155     stringOptions.SetAt(OPAL_OPT_STATION_ID, str);
156   }
157 
158   if (args.HasOption("header-info")) {
159     PString str = args.GetOptionString("header-info");
160     cout << "Transmit Header Info: " << str << '\n';
161     stringOptions.SetAt(OPAL_OPT_HEADER_INFO, str);
162   }
163 
164   if (args.HasOption('F')) {
165     stringOptions.SetBoolean(OPAL_NO_G111_FAX, true);
166     cout << "Disabled fallback to audio (G.711) mode on T.38 switch failure\n";
167   }
168 
169   if (args.HasOption('e')) {
170     stringOptions.SetBoolean(OPAL_SWITCH_ON_CED, true);
171     cout << "Enabled switch to T.38 on receipt of CED\n";
172   }
173 
174   if (args.HasOption('X')) {
175     unsigned seconds = args.GetOptionString('X').AsUnsigned();
176     stringOptions.SetInteger(OPAL_T38_SWITCH_TIME, seconds);
177     cout << "Switch to T.38 after " << seconds << "seconds\n";
178   }
179   else
180     cout << "No T.38 switch timeout set\n";
181 
182   // Wait for call to come in and finish (default one year)
183   PSimpleTimer timeout(args.GetOptionString('T', "365:0:0:0"));
184   cout << "Completion timeout is " << timeout.AsString(0, PTimeInterval::IncludeDays) << '\n';
185 
186   if (args.GetCount() == 1)
187     cout << "Receive directory: " << fax->GetDefaultDirectory() << "\n"
188             "\n"
189             "Awaiting incoming fax, saving as " << args[0];
190   else {
191     PString token;
192     if (!m_manager->SetUpCall(prefix + ":" + args[0], args[1], token, NULL, 0, &stringOptions)) {
193       cerr << "Could not start call to \"" << args[1] << '"' << endl;
194       return;
195     }
196     cout << "\n"
197             "Sending " << args[0] << " to " << args[1];
198   }
199   cout << " ..." << endl;
200 
201   SetTerminationValue(2);
202 
203   map<PString, OpalMediaStatistics> lastStatisticsByToken;
204 
205   while (!m_manager->m_completed.Wait(1000)) {
206     if (timeout.HasExpired()) {
207       cout << " no call";
208       break;
209     }
210 
211     if (args.HasOption('v')) {
212       PArray<PString> tokens = m_manager->GetAllCalls();
213       for (PINDEX i = 0; i < tokens.GetSize(); ++i) {
214         PSafePtr<OpalCall> call = m_manager->FindCallWithLock(tokens[i], PSafeReadOnly);
215         if (call != NULL) {
216           PSafePtr<OpalFaxConnection> connection = call->GetConnectionAs<OpalFaxConnection>();
217           if (connection != NULL) {
218             PSafePtr<OpalMediaStream> stream = connection->GetMediaStream(OpalMediaType::Fax(), true);
219             if (stream != NULL) {
220               OpalMediaStatistics & lastStatistics = lastStatisticsByToken[tokens[i]];
221               OpalMediaStatistics statistics;
222               stream->GetStatistics(statistics);
223 
224 #define SHOW_STAT(message, member) \
225               if (lastStatistics.m_fax.member != statistics.m_fax.member) \
226                 cout << message << ": " << statistics.m_fax.member << endl;
227               SHOW_STAT("Phase", m_phase);
228               SHOW_STAT("Station identifier", m_stationId);
229               SHOW_STAT("Tx pages", m_txPages);
230               SHOW_STAT("Rx pages", m_rxPages);
231               SHOW_STAT("Bit rate", m_bitRate);
232               SHOW_STAT("Compression", m_compression);
233               SHOW_STAT("Page width", m_pageWidth);
234               SHOW_STAT("Page height", m_pageHeight);
235 
236               lastStatistics = statistics;
237             }
238           }
239         }
240       }
241     }
242   }
243 
244   cout << "\nCompleted." << endl;
245 }
246 
247 
OnInterrupt(bool)248 bool FaxOPAL::OnInterrupt(bool)
249 {
250   if (m_manager == NULL)
251     return false;
252 
253   m_manager->m_completed.Signal();
254   return true;
255 }
256 
257 
OnClearedCall(OpalCall & call)258 void MyManager::OnClearedCall(OpalCall & call)
259 {
260   switch (call.GetCallEndReason()) {
261     case OpalConnection::EndedByLocalUser :
262     case OpalConnection::EndedByRemoteUser :
263       break;
264 
265     default :
266       cerr << "Call error: " << OpalConnection::GetCallEndReasonText(call.GetCallEndReason());
267   }
268 
269   m_completed.Signal();
270 }
271 
272 
OnFaxCompleted(OpalFaxConnection & connection,bool failed)273 void MyFaxEndPoint::OnFaxCompleted(OpalFaxConnection & connection, bool failed)
274 {
275   OpalMediaStatistics stats;
276   connection.GetStatistics(stats);
277   switch (stats.m_fax.m_result) {
278     case -2 :
279       cerr << "Failed to establish T.30";
280       break;
281     case 0 :
282       cout << "Success, "
283            << (connection.IsReceive() ? stats.m_fax.m_rxPages : stats.m_fax.m_txPages)
284            << " of " << stats.m_fax.m_totalPages << " pages";
285       PProcess::Current().SetTerminationValue(0); // Indicate success
286       break;
287     case 41 :
288       cerr << "Failed to open TIFF file";
289       break;
290     case 42 :
291     case 43 :
292     case 44 :
293     case 45 :
294     case 46 :
295       cerr << "Illegal TIFF file";
296       break;
297     default :
298       cerr << "T.30 error " << stats.m_fax.m_result;
299       if (!stats.m_fax.m_errorText.IsEmpty())
300         cerr << " (" << stats.m_fax.m_errorText << ')';
301   }
302   OpalFaxEndPoint::OnFaxCompleted(connection, failed);
303 }
304 
305 
306 // End of File ///////////////////////////////////////////////////////////////
307