Changed to use sound device factory to create PSoundChannel for
[opal/cbnco.git] / samples / jester / main.cxx
blob912e4527bba79280eed9fa6932dd194304fd00e3
1 /*
2 * main.cxx
4 * Jester - a tester of the Opal jitter buffer
6 * Copyright (c) 2006 Derek J Smithies
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/
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.
18 * The Original Code is Jester
20 * The Initial Developer of the Original Code is Derek J Smithies
22 * Contributor(s): ______________________________________.
24 * $Log$
25 * Revision 1.14 2007/02/24 09:29:08 dereksmithies
26 * Add ability to turn off the first packet in an audio burst. Test the jitter buffer
27 * can cope with missing the first packet.
29 * Revision 1.13 2007/01/14 22:18:35 dereksmithies
30 * MOdify the period when doing silence detect, to be 69sec off, 6 sec on.
32 * Revision 1.12 2007/01/14 20:52:32 dereksmithies
33 * Report available audio devices if it fails to open the specified device.
35 * Revision 1.11 2007/01/13 00:05:40 rjongbloed
36 * Fixed compilation on DevStudio 2003
38 * Revision 1.10 2007/01/12 10:00:57 dereksmithies
39 * bring it up to date so it compiles.
41 * Revision 1.9 2007/01/11 09:20:41 dereksmithies
42 * Use the new OpalJitterBufer class, allowing easy access to the jitter buffer's internal
43 * variables. Play output audio to the specified sound device.
45 * Revision 1.8 2006/12/08 09:00:20 dereksmithies
46 * Add mutex protection of a pointer.
47 * Change default to send packets at a non uniform rate, so they go at 0 20 60 80 120 140 180 etc.
48 * Add ability to suppress the sending of packets, so we simulate silence suppression.
50 * Revision 1.7 2006/12/02 07:31:00 dereksmithies
51 * Add more options - duration of each packet.
53 * Revision 1.6 2006/12/02 04:16:13 dereksmithies
54 * Get it to terminate correctly.
55 * Report on when each frame is commited, and when each frame is received.
57 * Revision 1.5 2006/11/23 07:55:15 rjongbloed
58 * Fixed sample app build due to RTP session class API breakage.
60 * Revision 1.4 2006/10/02 13:30:51 rjongbloed
61 * Added LID plug ins
63 * Revision 1.3 2006/08/25 06:04:44 dereksmithies
64 * Add to the docs on the functions. Add a new thread to generate the frames,
65 * which helps make the operation of the jitterbuffer clearer.
67 * Revision 1.2 2006/07/29 13:42:20 rjongbloed
68 * Fixed compiler warning
70 * Revision 1.1 2006/06/19 09:32:09 dereksmithies
71 * Initial cut of a program to test the jitter buffer in OPAL.
76 #ifdef P_USE_PRAGMA
77 #pragma implementation "main.h"
78 #endif
81 #include <ptlib.h>
83 #include <opal/buildopts.h>
84 #include <rtp/rtp.h>
86 #include "main.h"
87 #include "../../version.h"
90 #define new PNEW
93 PCREATE_PROCESS(JesterProcess);
95 BOOL keepRunning;
97 ///////////////////////////////////////////////////////////////
98 JesterJitterBuffer::JesterJitterBuffer():
99 IAX2JitterBuffer()
104 JesterJitterBuffer::~JesterJitterBuffer()
109 void JesterJitterBuffer::Close(BOOL /*reading */ )
111 CloseDown();
114 /////////////////////////////////////////////////////////////////////////////
116 JesterProcess::JesterProcess()
117 : PProcess("Derek Smithies Code Factory", "Jester",
118 1, 1, ReleaseCode, 0)
123 void JesterProcess::Main()
125 // Get and parse all of the command line arguments.
126 PArgList & args = GetArguments();
127 args.Parse(
128 "a-audiodevice:"
129 "j-jitter:"
130 "s-silence."
131 "h-help."
132 "m-marker."
133 #if PTRACING
134 "o-output:"
135 "t-trace."
136 #endif
137 "v-version."
138 "w-wavfile:"
139 , FALSE);
142 if (args.HasOption('h') ) {
143 cout << "Usage : " << GetName() << " [options] \n"
145 "General options:\n"
146 " -a --audiodevice : audio device to play the output on\n"
147 " -i --iterations # : number of packets to ask for (default is 80)\n"
148 " -s --silence : simulate silence suppression. - so audio is sent in bursts.\n"
149 " -j --jitter [min-]max : size of the jitter buffer in ms (100-1000) \n"
150 " -m --marker : turn some of the marker bits off, that indicate speech bursts\n"
151 #if PTRACING
152 " -t --trace : Enable trace, use multiple times for more detail.\n"
153 " -o --output : File for trace output, default is stderr.\n"
154 #endif
156 " -h --help : This help message.\n"
157 " -v --version : report version and program info.\n"
158 " -w --wavfile : audio file from which the source data is read from \n"
159 "\n"
160 "\n";
161 return;
164 if (args.HasOption('v')) {
165 cout << GetName() << endl
166 << " Version " << GetVersion(TRUE) << endl
167 << " by " << GetManufacturer() << endl
168 << " on " << GetOSClass() << ' ' << GetOSName() << endl
169 << " (" << GetOSVersion() << '-' << GetOSHardware() << ")\n\n";
170 return;
173 #if PTRACING
174 PTrace::Initialise(args.GetOptionCount('t'),
175 args.HasOption('o') ? (const char *)args.GetOptionString('o') : NULL,
176 PTrace::Timestamp|PTrace::Thread|PTrace::FileAndLine);
177 #endif
179 if (args.HasOption('a'))
180 audioDevice = args.GetOptionString('a');
181 else
182 audioDevice = PSoundChannel::GetDefaultDevice(PSoundChannel::Player);
184 minJitterSize = 100;
185 maxJitterSize = 1000;
187 if (args.HasOption('j')) {
188 unsigned minJitterNew;
189 unsigned maxJitterNew;
190 PStringArray delays = args.GetOptionString('j').Tokenise(",-");
192 if (delays.GetSize() > 1) {
193 minJitterNew = delays[0].AsUnsigned();
194 maxJitterNew = delays[1].AsUnsigned();
195 } else {
196 maxJitterNew = delays[0].AsUnsigned();
197 minJitterNew = maxJitterNew;
200 if (minJitterNew >= 20 && minJitterNew <= maxJitterNew && maxJitterNew <= 1000) {
201 minJitterSize = minJitterNew;
202 maxJitterSize = maxJitterNew;
203 } else {
204 cout << "Jitter should be between 20 milliseconds and 1 seconds, is "
205 << 20 << '-' << 1000 << endl;
208 cerr << "Set jitter buffer size to " << minJitterSize << ".." << maxJitterSize << " ms" << endl;
210 silenceSuppression = args.HasOption('s');
212 markerSuppression = args.HasOption('m');
214 if (args.HasOption('w'))
215 wavFile = args.GetOptionString('w');
216 else {
217 wavFile = "../../../contrib/openam/sample_message.wav";
220 bytesPerBlock = 640;
222 if (!PFile::Exists(wavFile)) {
223 cerr << "the audio file " << wavFile << " does not exist." << endl;
224 cerr << "Terminating now" << endl;
225 return;
228 if (!player.Open(audioDevice, PSoundChannel::Player, 1, 8000, 16)) {
229 cerr << "Failed to open the sound device " << audioDevice
230 << " to write the jittered audio to" << endl;
231 cerr << endl
232 << "available devices are " << endl;
233 PStringList namesPlay = PSoundChannel::GetDeviceNames(PSoundChannel::Player);
234 for (PINDEX i = 0; i < namesPlay.GetSize(); i++)
235 cerr << i << " " << namesPlay[i] << endl;
237 cerr << endl;
238 cerr << "Terminating now" << endl;
239 return;
242 jitterBuffer.SetDelay(8 * minJitterSize, 8 * maxJitterSize);
243 jitterBuffer.Resume();
245 keepRunning = TRUE;
246 generateTimestamp = 0;
247 consumeTimestamp = 0;
249 PThread * writer = PThread::Create(PCREATE_NOTIFIER(GenerateUdpPackets), 0,
250 PThread::NoAutoDeleteThread,
252 PThread::NormalPriority,
253 "generate");
255 PThread::Sleep(10);
257 PThread * reader = PThread::Create(PCREATE_NOTIFIER(ConsumeUdpPackets), 0,
258 PThread::NoAutoDeleteThread,
259 PThread::NormalPriority,
260 "consume");
263 ManageUserInput();
265 writer->WaitForTermination();
267 reader->WaitForTermination();
269 delete writer;
270 delete reader;
274 void JesterProcess::GenerateUdpPackets(PThread &, INT )
276 PAdaptiveDelay delay;
277 BOOL lastFrameWasSilence = TRUE;
278 PWAVFile soundFile(wavFile);
279 generateIndex = 0;
280 PINDEX talkSequenceCounter = 0;
282 while(keepRunning) {
283 generateTimestamp = (bytesPerBlock * 2) + ((generateIndex * bytesPerBlock) >> 1);
284 //Silence period, 10 seconds cycle, with 3 second on time.
285 if (silenceSuppression && ((generateIndex % 1000) > 200)) {
286 PTRACE(3, "Don't send this frame - silence period");
287 if (lastFrameWasSilence == FALSE) {
288 PTRACE(3, "Stop Audio here");
289 cout << "Stop audio at " << PTime() << endl;
290 talkSequenceCounter++;
292 lastFrameWasSilence = TRUE;
293 } else {
294 RTP_DataFrame *frame = new RTP_DataFrame;
295 if (lastFrameWasSilence) {
296 PTRACE(3, "StartAudio here");
297 cout << "Start Audio at " << PTime() << endl;
299 frame->SetMarker(lastFrameWasSilence);
300 lastFrameWasSilence = FALSE;
301 frame->SetPayloadType(RTP_DataFrame::L16_Mono);
302 frame->SetSyncSource(0x12345678);
303 frame->SetSequenceNumber((WORD)(generateIndex + 100));
304 frame->SetPayloadSize(bytesPerBlock);
306 frame->SetTimestamp(generateTimestamp);
308 PTRACE(3, "GenerateUdpPacket iteration " << generateIndex
309 << " with time of " << frame->GetTimestamp() << " rtp time units");
310 memset(frame->GetPayloadPtr(), 0, frame->GetPayloadSize());
311 if (!soundFile.Read(frame->GetPayloadPtr(), frame->GetPayloadSize())) {
312 soundFile.Close();
313 soundFile.Open();
314 PTRACE(3, "Reopen the sound file, as have reached the end of it");
316 // cerr << " " << silenceSuppression << " " << markerSuppression << " " << frame->GetMarker() << " " << (talkSequenceCounter & 1) << endl;
317 if (silenceSuppression && markerSuppression && frame->GetMarker() && (talkSequenceCounter & 1))
318 cerr << "Suppress speech frame" << endl;
319 else
320 jitterBuffer.NewFrameFromNetwork(frame);
323 delay.Delay(30);
324 #if 1
325 switch (generateIndex % 2)
327 case 0:
328 break;
329 case 1: PThread::Sleep(30);
330 break;
332 #endif
333 generateIndex++;
335 PTRACE(3, "End of generate udp packets ");
339 void JesterProcess::ConsumeUdpPackets(PThread &, INT)
341 RTP_DataFrame readFrame;
342 PBYTEArray silence(bytesPerBlock);
343 consumeTimestamp = 0;
344 consumeIndex = 0;
346 while(keepRunning) {
348 BOOL success = jitterBuffer.ReadData(consumeTimestamp, readFrame);
349 PTime lastWriteTime;
350 if (success && (readFrame.GetPayloadSize() > 0)) {
351 consumeTimestamp = readFrame.GetTimestamp();
352 PTRACE(3, "Write audio to sound device, " << readFrame.GetPayloadSize() << " bytes");
353 player.Write(readFrame.GetPayloadPtr(), readFrame.GetPayloadSize());
354 PTRACE(3, "Play audio from the buffer to sound device, ts=" << consumeTimestamp);
356 else {
357 PTRACE(3, "Write silence to sound device, " << bytesPerBlock << " bytes");
358 player.Write(silence, bytesPerBlock);
359 PTRACE(3, "Play audio from silence buffer to sound device, ts=" << consumeTimestamp);
361 PTime thisTime;
362 PTRACE(3, "Write to sound device took " << (thisTime - lastWriteTime).GetMilliSeconds() << " ms");
364 consumeTimestamp += (bytesPerBlock / 2);
365 consumeIndex++;
368 jitterBuffer.CloseDown();
370 PTRACE(3, "End of consume udp packets ");
373 void JesterProcess::ManageUserInput()
375 PConsoleChannel console(PConsoleChannel::StandardInput);
377 PStringStream help;
378 help << "Select:\n";
379 help << " X : Exit program\n"
380 << " Q : Exit program\n"
381 << " T : Read and write process report their current timestamps\n"
382 << " R : Report iteration counts\n"
383 << " J : Report some of the internal variables in the jitter buffer\n"
384 << " H : Write this help out\n";
386 PThread::Sleep(100);
389 for (;;) {
390 // display the prompt
391 cout << "(Jester) Command ? " << flush;
393 // terminate the menu loop if console finished
394 char ch = (char)console.peek();
395 if (console.eof()) {
396 cout << "\nConsole gone - menu disabled" << endl;
397 goto endAudioTest;
400 console >> ch;
401 PTRACE(3, "console in audio test is " << ch);
402 switch (tolower(ch)) {
403 case 'q' :
404 case 'x' :
405 goto endAudioTest;
406 case 'r' :
407 cout << " generate thread=" << generateIndex << " consume thread=" << consumeIndex << endl;
408 break;
409 case 'h' :
410 cout << help ;
411 break;
412 case 't' :
413 cerr << " Timestamps are " << generateTimestamp << "/" << consumeTimestamp << " (generate/consume)" << endl;
414 DWORD answer;
415 if (generateTimestamp > consumeTimestamp)
416 answer = generateTimestamp - consumeTimestamp;
417 else
418 answer = consumeTimestamp - generateTimestamp;
419 cerr << " RTP difference " << answer << " Milliseconds difference is " << (answer/8) << endl;
420 break;
421 case 'j' :
422 cerr << " Target Jitter Time is " << jitterBuffer.GetTargetJitterTime() << endl;
423 cerr << " Current depth is " << jitterBuffer.GetCurrentDepth() << endl;
424 cerr << " Current Jitter Time is " << jitterBuffer.GetCurrentJitterTime() << endl;
425 break;
426 default:
431 endAudioTest:
432 keepRunning = FALSE;
433 cout << "end audio test" << endl;
438 // End of File ///////////////////////////////////////////////////////////////