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