1 //
2 // audio.cxx
3 //
4 // Roger Hardiman
5 //
6 //
7 /*
8  * audio.cxx
9  *
10  * PWLib application source file for audio testing.
11  *
12  * Main program entry point.
13  *
14  * Copyright 2005 Roger Hardiman
15  *
16  * Copied by Derek Smithies, 1)Add soundtest code from ohphone.
17  *                           2)Add headers.
18  *
19  * $Revision: 28674 $
20  * $Author: rjongbloed $
21  * $Date: 2012-12-14 19:45:47 -0600 (Fri, 14 Dec 2012) $
22  */
23 
24 #include <ptlib.h>
25 #include <ptlib/pprocess.h>
26 #include "version.h"
27 #include "audio.h"
28 #include <ptclib/pwavfile.h>
29 
Audio()30 Audio::Audio()
31   : PProcess("Roger Hardiman & Derek Smithies code factory", "audio",
32              MAJOR_VERSION, MINOR_VERSION, BUILD_TYPE, BUILD_NUMBER)
33 {
34 
35 }
36 
PCREATE_PROCESS(Audio)37 PCREATE_PROCESS(Audio)
38 
39 void Audio::Main()
40 {
41   PArgList & args = GetArguments();
42   args.Parse("r."
43              "f."
44              "h."
45 #if PTRACING
46              "o-output:"
47              "t-trace."
48 #endif
49              "p:"
50              "v."
51              "w:"
52              "s:");
53 
54   if (args.HasOption('h')) {
55     cout << "usage: audio "
56          << endl
57          << "     -r        : report available sound devices" << endl
58          << "     -f        : do a full duplex sound test on a sound device" << endl
59          << "     -s  dev   : use this device in full duplex test " << endl
60          << "     -h        : get help on usage " << endl
61          << "     -p file   : play audio from the file out the specified sound device" << endl
62          << "     -v        : report program version " << endl
63 	 << "     -w file   : write the captured audio to this file" << endl
64 #if PTRACING
65          << "  -t --trace   : Enable trace, use multiple times for more detail" << endl
66          << "  -o --output  : File for trace output, default is stderr" << endl
67 #endif
68          << endl;
69     return;
70   }
71 
72 
73   PTrace::Initialise(args.GetOptionCount('t'),
74                      args.HasOption('o') ? (const char *)args.GetOptionString('o') : NULL,
75          PTrace::Blocks | PTrace::Timestamp | PTrace::Thread | PTrace::FileAndLine);
76 
77   if (args.HasOption('v')) {
78     cout << endl
79          << "Product Name: " <<  (const char *)GetName() << endl
80          << "Manufacturer: " <<  (const char *)GetManufacturer() << endl
81          << "Version     : " <<  (const char *)GetVersion(PTrue) << endl
82          << "System      : " <<  (const char *)GetOSName() << '-'
83          <<  (const char *)GetOSHardware() << ' '
84          <<  (const char *)GetOSVersion() << endl
85          << endl;
86     return;
87   }
88 
89 
90   cout << "Audio Test Program\n\n";
91 
92   PSoundChannel::Directions dir;
93   PStringArray namesPlay, namesRecord;
94   namesPlay = PSoundChannel::GetDeviceNames(PSoundChannel::Player);
95   namesRecord = PSoundChannel::GetDeviceNames(PSoundChannel::Recorder);
96 
97   if (args.HasOption('r')) {
98       cout << "List of play devices\n";
99       for (PINDEX i = 0; i < namesPlay.GetSize(); i++)
100 	  cout << "  \"" << namesPlay[i] << "\"\n";
101       cout << "The default play device is \""
102 	   << PSoundChannel::GetDefaultDevice(PSoundChannel::Player) << "\"\n";
103 
104       cout << "List of Record devices\n";
105       for (PINDEX i = 0; i < namesRecord.GetSize(); i++)
106 	  cout << "  \"" << namesRecord[i] << "\"\n";
107       cout << "The default record device is \""
108 	   << PSoundChannel::GetDefaultDevice(PSoundChannel::Recorder) << "\"\n\n";
109   }
110 
111   // Display the mixer settings for the default devices (or device if specified)
112   {
113       PSoundChannel sound;
114       dir = PSoundChannel::Player;
115       devName = args.GetOptionString('s');
116       if (devName.IsEmpty())
117 	  devName = PSoundChannel::GetDefaultDevice(dir);
118 
119       if (sound.Open(devName, dir)) {
120 	  unsigned int vol;
121 	  if (sound.GetVolume(vol))
122 	      cout << "Play volume is (for " << devName << ")" << vol << endl;
123 	  else
124 	      cout << "Play volume cannot be obtained (for " << devName << ")" << endl;
125 	  sound.Close();
126       } else
127 	  cerr << "Failed to open play device ("
128 	       << devName << ") to get volume settings" << endl;
129   }
130   {
131       PSoundChannel sound;
132       dir = PSoundChannel::Recorder;
133       devName = args.GetOptionString('s');
134       if (devName.IsEmpty())
135 	  devName = PSoundChannel::GetDefaultDevice(dir);
136       if (sound.Open(devName, dir)) {
137 	  unsigned int vol;
138 	  if (sound.GetVolume(vol))
139 	      cout << "Record volume is (for " << devName << ")" << vol << endl;
140 	  else
141 	      cout << "Record volume cannot be obtained (for " << devName << ")" << endl;
142 	  sound.Close();
143       } else
144 	  cerr << "Failed to open record device ("
145 	       << devName << ") to get volume settings" << endl;
146   }
147 
148   if (args.HasOption('f')) {
149     devName = args.GetOptionString('s');
150     if (devName.IsEmpty())
151       devName = PSoundChannel::GetDefaultDevice(PSoundChannel::Player);
152 
153     PString capturedAudio = args.GetOptionString('w');
154 
155     if (namesPlay.GetStringsIndex(devName) == P_MAX_INDEX) {
156       cout << "could not find " << devName << " in list of available play devices - abort test" << endl;
157       return;
158     }
159 
160     if (namesRecord.GetStringsIndex(devName) == P_MAX_INDEX) {
161       cout << "could not find " << devName << " in list of available record devices - abort test" << endl;
162       return;
163     }
164 
165     PTRACE(3, "Audio\tTest device " << devName);
166 
167     TestAudioDevice device;
168     device.Test(capturedAudio);
169     return;
170   }
171 
172   if (args.HasOption('p')) {
173       PString playFileName = args.GetOptionString('p');
174       if (playFileName.IsEmpty()) {
175 	  cerr << "The p option requires an arguement - the name of the file to play" << endl;
176 	  cerr << "Terminating" << endl;
177 	  return;
178       }
179 
180       PWAVFile audioFile;
181       BYTE buffer[500];
182       if (!audioFile.Open(playFileName)) {
183 	  cerr << "Failed to open the file \"" << playFileName << "\" to read audio from." << endl;
184 	  return;
185       }
186 
187       PSoundChannel sound;
188       dir = PSoundChannel::Player;
189       devName = args.GetOptionString('s');
190       if (devName.IsEmpty())
191 	  devName = PSoundChannel::GetDefaultDevice(dir);
192 
193       if (sound.Open(devName, PSoundChannel::Player, 1, 8000, 16)) {
194 	  PTRACE(3, "Open sound device " << devName << " to put audio to");
195 	  cerr << devName << " opened fine for playing to" << endl;
196       } else {
197 	  cerr << "Failed to open play device ("
198 	       << devName << ") for putting audio to speaker" << endl;
199 	  return;
200       }
201       sound.SetBuffers(480, 3);
202 
203       PINDEX readCounter = 0;
204       while (audioFile.Read(buffer, 480)) {
205 	  PTRACE(3, "Read buffer " << readCounter << " from audio file " << playFileName);
206 	  readCounter++;
207 	  if (!sound.Write(buffer, 480)) {
208 	      cerr << " failed to write a buffer to sound device. Ending" << endl;
209 	      return;
210 	  }
211       }
212 
213   }
214 #if PTRACING
215   if (args.GetOptionCount('t') > 0) {
216     PTrace::ClearOptions(0);
217     PTrace::SetLevel(0);
218   }
219 #endif
220 
221 }
222 
223 ////////////////////////////////////////////////////////////////////////////////
224 
~TestAudioDevice()225 TestAudioDevice::~TestAudioDevice()
226 {
227   AllowDeleteObjects();
228   access.Wait();
229   RemoveAll();
230   endNow = PTrue;
231   access.Signal();
232   PThread::Sleep(100);
233 }
234 
Test(const PString & captureFileName)235 void TestAudioDevice::Test(const PString & captureFileName)
236 {
237    endNow = PFalse;
238    PConsoleChannel console(PConsoleChannel::StandardInput);
239 
240    AllowDeleteObjects(PFalse);
241    PTRACE(3, "Start operation of TestAudioDevice");
242 
243    TestAudioRead reader(*this, captureFileName);
244    TestAudioWrite writer(*this);
245 
246 
247    PStringStream help;
248    help << "Select:\n";
249    help << "  X   : Exit program\n"
250         << "  Q   : Exit program\n"
251         << "  {}  : Increase/reduce record volume\n"
252         << "  []  : Increase/reduce playback volume\n"
253         << "  H   : Write this help out\n"
254 	<< "  R   : Report the number of 30 ms long sound samples processed\n";
255 
256    PThread::Sleep(100);
257    if (reader.IsTerminated() || writer.IsTerminated()) {
258      reader.Terminate();
259      writer.Terminate();
260 
261      goto endAudioTest;
262    }
263 
264   for (;;) {
265     // display the prompt
266     cout << "(testing sound device for full duplex) Command ? " << flush;
267 
268     // terminate the menu loop if console finished
269     char ch = (char)console.peek();
270     if (console.eof()) {
271       cout << "\nConsole gone - menu disabled" << endl;
272       goto endAudioTest;
273     }
274 
275     console >> ch;
276     PTRACE(3, "console in audio test is " << ch);
277     switch (tolower(ch)) {
278 	case '{' :
279 	    reader.LowerVolume();
280 	    break;
281 	case '}' :
282 	    reader.RaiseVolume();
283 	    break;
284 	case '[' :
285 	    writer.LowerVolume();
286 	    break;
287 	case ']' :
288 	    writer.RaiseVolume();
289 	    break;
290 	case 'r' :
291 	    reader.ReportIterations();
292 	    writer.ReportIterations();
293 	    break;
294 	case 'q' :
295 	case 'x' :
296 	    goto endAudioTest;
297 	case 'h' :
298 	    cout << help ;
299 	    break;
300         default:
301 	    ;
302     }
303   }
304 
305 endAudioTest:
306   endNow = PTrue;
307   cout  << "end audio test" << endl;
308 
309   reader.WaitForTermination();
310   writer.WaitForTermination();
311 }
312 
313 
GetNextAudioFrame()314 PBYTEArray *TestAudioDevice::GetNextAudioFrame()
315 {
316   PBYTEArray *data = NULL;
317 
318   while (data == NULL) {
319     {
320       PWaitAndSignal m(access);
321       if (GetSize() > 30)
322         data = (PBYTEArray *)RemoveAt(0);
323       if (endNow)
324         return NULL;
325     }
326 
327     if (data == NULL) {
328       PThread::Sleep(30);
329     }
330   }
331 
332   return data;
333 }
334 
WriteAudioFrame(PBYTEArray * data)335 void TestAudioDevice::WriteAudioFrame(PBYTEArray *data)
336 {
337   PWaitAndSignal mutex(access);
338   if (endNow) {
339     delete data;
340     return;
341   }
342 
343   PTRACE(5, "Buffer\tNow put one frame on the que");
344   Append(data);
345   if (GetSize() > 50) {
346     cout << "The audio reader thread is not working - exit now before memory is exhausted" << endl;
347     endNow = PTrue;
348   }
349   return;
350 }
351 
DoEndNow()352 PBoolean TestAudioDevice::DoEndNow()
353 {
354     return endNow;
355 }
356 
357 //////////////////////////////////////////////////////////////////////
358 
TestAudioRead(TestAudioDevice & master,const PString & _captureFileName)359 TestAudioRead::TestAudioRead(TestAudioDevice &master, const PString & _captureFileName)
360     :TestAudio(master),
361      captureFileName(_captureFileName)
362 {
363   PTRACE(3, "Reader\tInitiate thread for reading " );
364   Resume();
365 }
366 
ReportIterations()367 void TestAudioRead::ReportIterations()
368 {
369     cout << "Captured " << iterations << " frames of 480 bytes to the sound card" << endl;
370 }
371 
372 
373 
Main()374 void TestAudioRead::Main()
375 {
376   if (!OpenAudio(PSoundChannel::Recorder)) {
377     PTRACE(1, "TestAudioWrite\tFAILED to open read device");
378     return;
379   }
380   PWAVFile audioFile;
381   if (!captureFileName.IsEmpty()) {
382       if (!audioFile.Open(captureFileName, PFile::WriteOnly, PFile::Create | PFile::Truncate))
383 	  cerr << "Cannot create the file " << captureFileName << " to write audio to" << endl;
384   }
385 
386   PTRACE(3, "TestAduioRead\tSound device is now open, start running");
387 
388   while ((!controller.DoEndNow()) && keepGoing) {
389     PBYTEArray *data = new PBYTEArray(480);
390     sound.Read(data->GetPointer(), data->GetSize());
391     iterations++;
392     PTRACE(3, "TestAudioRead\t send one frame to the queue" << data->GetSize());
393     PTRACE(5, "Written the frame " << endl << (*data));
394 
395     if (audioFile.IsOpen())
396 	audioFile.Write(data->GetPointer(), data->GetSize());
397 
398     controller.WriteAudioFrame(data);
399   }
400 
401   audioFile.Close();
402   PTRACE(3, "End audio read thread");
403 }
404 
405 //////////////////////////////////////////////////////////////////////
406 
TestAudioWrite(TestAudioDevice & master)407 TestAudioWrite::TestAudioWrite(TestAudioDevice &master)
408    : TestAudio(master)
409 {
410   PTRACE(3, "Reader\tInitiate thread for writing " );
411   Resume();
412 }
413 
ReportIterations()414 void TestAudioWrite::ReportIterations()
415 {
416     cout << "Written " << iterations << " frames of 480 bytes to the sound card" << endl;
417 }
418 
Main()419 void TestAudioWrite::Main()
420 {
421   if (!OpenAudio(PSoundChannel::Player)) {
422     PTRACE(1, "TestAudioWrite\tFAILED to open play device");
423     return;
424   }
425   PTRACE(3, "TestAudioWrite\tSound device is now open, start running");
426 
427   while ((!controller.DoEndNow()) && keepGoing) {
428     PBYTEArray *data = controller.GetNextAudioFrame();
429     PTRACE(3, "TestAudioWrite\tHave read one audio frame ");
430     if (data != NULL) {
431       sound.Write(data->GetPointer(), data->GetSize());
432       iterations++;
433       delete data;
434     } else
435       PTRACE(1, "testAudioWrite\t next audio frame is NULL");
436   }
437 
438 
439   PTRACE(3, "End audio write thread");
440 }
441 
442 //////////////////////////////////////////////////////////////
443 
TestAudio(TestAudioDevice & master)444 TestAudio::TestAudio(TestAudioDevice &master)
445     :PThread(1000, NoAutoDeleteThread),
446      controller(master)
447 {
448     iterations = 0;
449     keepGoing = PTrue;
450 }
451 
~TestAudio()452 TestAudio::~TestAudio()
453 {
454    sound.Close();
455 }
456 
457 
OpenAudio(enum PSoundChannel::Directions dir)458 PBoolean TestAudio::OpenAudio(enum PSoundChannel::Directions dir)
459 {
460   if (dir == PSoundChannel::Recorder)
461     name = "Recorder";
462   else
463     name = "Player";
464 
465   PThread::Current()->SetThreadName(name);
466   PString devName = Audio::Current().GetTestDeviceName();
467   PTRACE(3, "TestAudio\t open audio start for " << name << " and device name of " << devName);
468 
469   PTRACE(3, "Open audio device for " << name << " and device name of " << devName);
470   if (!sound.Open(devName,
471       dir,
472       1, 8000, 16)) {
473       cerr <<  "Test:: Failed to open sound device  for " << name << endl;
474       cerr <<  "Please check that \"" << devName << "\" is a valid device name" << endl;
475       PTRACE(3, "TestAudio\tFailed to open device for " << name << " and device name of " << devName);
476 
477     return PFalse;
478   }
479 
480   currentVolume = 90;
481   sound.SetVolume(currentVolume);
482 
483   sound.SetBuffers(480, 2);
484   return PTrue;
485 }
486 
487 
RaiseVolume()488 void TestAudio::RaiseVolume()
489 {
490    if ((currentVolume + 5) < 101)
491      currentVolume += 5;
492    sound.SetVolume(currentVolume);
493    cout << name << " volume is " << currentVolume << endl;
494    PTRACE(3, "TestAudio\tRaise volume for " << name << " to " << currentVolume);
495 }
496 
LowerVolume()497 void TestAudio::LowerVolume()
498 {
499    if ((currentVolume - 5) >= 0)
500      currentVolume -= 5;
501    sound.SetVolume(currentVolume);
502    cout << name << " volume is " << currentVolume << endl;
503    PTRACE(3, "TestAudio\tLower volume for " << name << " to " << currentVolume);
504 }
505 ////////////////////////////////////////////////////////////////////////////////
506 
507 /* The comment below is magic for those who use emacs to edit this file.
508  * With the comment below, the tab key does auto indent to 4 spaces.
509  *
510  *
511  * Local Variables:
512  * mode:c
513  * c-basic-offset:4
514  * End:
515  */
516 
517 // End of hello.cxx
518