PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
PlatformAudioIn.cpp
Go to the documentation of this file.
1//* -*- mode:C++; tab-width:4; c-basic-offset:4; indent-tabs-mode:nil -*- */
3// Name: src/platforms/sdl/PlatformAudioIn.cpp
4// Purpose: Contains platform-specific audio recording routines
5// Author: Shane T. Mueller, Ph.D.
6// Copyright: (c) 2011-2026 Shane T. Mueller <smueller@obereed.net>
7// License: GPL 2
8//
9//
10//
11// This file is part of the PEBL project.
12//
13// PEBL is free software; you can redistribute it and/or modify
14// it under the terms of the GNU General Public License as published by
15// the Free Software Foundation; either version 2 of the License, or
16// (at your option) any later version.
17//
18// PEBL is distributed in the hope that it will be useful,
19// but WITHOUT ANY WARRANTY; without even the implied warranty of
20// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21// GNU General Public License for more details.
22//
23// You should have received a copy of the GNU General Public License
24// along with PEBL; if not, write to the Free Software
25// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27
28#include "PlatformAudioIn.h"
29#include "PlatformAudioOut.h"
30
31//#include "../../devices/PAudioIn.h"
32
33#include "../../utility/PEBLPath.h"
34#include "../../utility/PError.h"
35#include "../../libs/PEBLEnvironment.h"
36
37
38#ifdef PEBL_EMSCRIPTEN
39#include "../../base/Evaluator-es.h"
40#else
41#include "../../base/Evaluator.h"
42#endif
43
44#include "../../base/PList.h"
45#include "../../base/PComplexData.h"
46
47
48#ifdef PEBL_OSX
49#include "SDL.h"
50#else
51#include "SDL.h"
52#endif
53
54#ifdef PEBL_AUDIOIN
55//#include "SDL_audioin.h"
56
57#include <cmath>
58#include <vector>
59#include <fstream>
60
61void AudioInCallbackFill(void * udata, Uint8 * stream, int len);
62void AudioInCallbackLoop(void * udata, Uint8 * stream, int len);
63
64//initiate static data for callback.
65//extern AudioInfo *gWaveStream=NULL;
66AudioInfo *gAudioBuffer = NULL; // Global buffer pointer (definition, not declaration)
67
68using std::string;
69using std::cerr;
70// cout removed - use cerr for debug output
71using std::endl;
72using namespace std;
73
74PlatformAudioIn::PlatformAudioIn()
75 // PEBLObjectBase(CDT_AUDIOIN)
76{
77 //Initialize audio device to 0 (uninitialized)
78 mAudioDevice = 0;
79
80 //Default audio settings
81 mSampleRate = 44100;
82 mAudioFormat = AUDIO_S16;
83 mBytesPerSample = 2;
84 mSamples = 256;
85 // mWave is automatically initialized to NULL by counted_ptr constructor
86}
87
88
89
90PlatformAudioIn::~PlatformAudioIn()
91{
92 std::cerr << "~PlatformAudioIn: Destructor called\n";
93
94 // Close SDL2 audio device
95 if(mAudioDevice > 0)
96 {
97 std::cerr << "~PlatformAudioIn: Closing audio device " << mAudioDevice << "\n";
98 SDL_CloseAudioDevice(mAudioDevice);
99 mAudioDevice = 0;
100 }
101
102 // Clear the global buffer pointer if it points to our buffer
103 if(mWave.get() && mWave.get() == gAudioBuffer)
104 {
105 std::cerr << "~PlatformAudioIn: Clearing gAudioBuffer\n";
106 gAudioBuffer = NULL;
107 }
108
109 std::cerr << "~PlatformAudioIn: Destructor complete\n";
110 // counted_ptr will automatically handle reference counting and deletion
111 // No manual memory management needed!
112}
113
114
115
116
117//This must be called after the audio is initialized but before it can
118//be used for recording. It opens the audio device for capture.
119bool PlatformAudioIn::Initialize(int type)
120{
121 // SDL2 audio already initialized in main PEBL startup (SDL_Init(SDL_INIT_AUDIO))
122
123 SDL_AudioSpec want, have;
124 SDL_zero(want);
125
126 want.freq = mSampleRate; // 44100
127 want.format = mAudioFormat; // AUDIO_S16
128 want.channels = 1; // Mono
129 want.samples = mSamples; // 256
130 want.userdata = &have;
131
132 // Select which callback should be used
133 if(type == 1)
134 {
135 want.callback = AudioInCallbackFill;
136 }
137 else
138 {
139 want.callback = AudioInCallbackLoop;
140 }
141
142 // List all available recording devices
143 int numDevices = SDL_GetNumAudioDevices(SDL_TRUE);
144 // Commented out for production - enable for debugging
145 // std::cerr << "====================================\n";
146 // std::cerr << "Available audio recording devices: " << numDevices << "\n";
147 // for(int i = 0; i < numDevices; i++) {
148 // const char* name = SDL_GetAudioDeviceName(i, SDL_TRUE);
149 // std::cerr << " Device " << i << ": " << (name ? name : "NULL") << "\n";
150 // }
151
152 // Try to find the built-in digital microphone (avoid headphone jack)
153 // Look for "Digital Microphone" in the device name
154 int deviceIndex = 0; // Default to first device
155 for(int i = 0; i < numDevices; i++) {
156 const char* name = SDL_GetAudioDeviceName(i, SDL_TRUE);
157 if(name && strstr(name, "Digital Microphone")) {
158 deviceIndex = i;
159 // std::cerr << "Found Digital Microphone at index " << i << "\n";
160 break;
161 }
162 }
163
164 // Get the selected recording device
165 const char* deviceName = SDL_GetAudioDeviceName(deviceIndex, SDL_TRUE);
166 if(!deviceName)
167 {
168 PError::SignalWarning("No audio recording device found");
169 return false;
170 }
171
172 // std::cerr << "Opening device " << deviceIndex << ": " << deviceName << "\n";
173
174 // Open capture device (SDL_TRUE = recording)
175 mAudioDevice = SDL_OpenAudioDevice(deviceName, SDL_TRUE, &want, &have, 0);
176 if(mAudioDevice == 0)
177 {
178 std::string errorMsg = std::string("Cannot open audio input device: ") + SDL_GetError();
179 PError::SignalWarning(errorMsg);
180 return false;
181 }
182
183 // std::cerr << "Device opened successfully!\n";
184 // std::cerr << " Requested: " << want.freq << "Hz, " << (int)want.channels << " channels, format=" << want.format << "\n";
185 // std::cerr << " Got: " << have.freq << "Hz, " << (int)have.channels << " channels, format=" << have.format << "\n";
186 // std::cerr << " Samples: " << have.samples << "\n";
187 // std::cerr << "====================================\n";
188
189 // Update actual specs if they differ from requested
190 mSampleRate = have.freq;
191 mAudioFormat = have.format;
192
193 return true;
194}
195
196
197counted_ptr<AudioInfo> PlatformAudioIn::GetAudioOutBuffer()
198{
199 return mWave; // Return copy of counted_ptr (increments reference count)
200}
201
202
203
204
205counted_ptr<AudioInfo> PlatformAudioIn::ReleaseAudioOutBuffer()
206{
207 counted_ptr<AudioInfo> tmp = mWave; // Copy the counted_ptr
208 mWave = counted_ptr<AudioInfo>(); // Reset to NULL, releasing our reference
209
210 return tmp; // Return the counted_ptr
211}
212
213//
214// This attaches a buffer within the PlatformAudioOut to use
215
216bool PlatformAudioIn::UseBuffer( counted_ptr<AudioInfo> buffer )
217{
218 if(mWave.get())
219 {
220 PError::SignalFatalError("Attempting to add a buffer to an input stream that already has one.\n");
221 }
222 else
223 {
224 // Store the counted_ptr - this increments the reference count
225 mWave = buffer;
226 gAudioBuffer = mWave.get();
227
228 mSampleRate= mWave->spec.freq;
229 mAudioFormat=mWave->spec.format;
230 if((buffer->spec.format == AUDIO_U8) |
231 (buffer->spec.format == AUDIO_S8) )
232 {
233 mBytesPerSample = 1;
234 }
235 else if((buffer->spec.format == AUDIO_S16 )|
236 (buffer->spec.format == AUDIO_U16))
237 {
238 mBytesPerSample=2;
239 }
240 mWave->bytesPerSample = mBytesPerSample;
241 mSamples = buffer->audiolen/mBytesPerSample;
242 }
243 return true;
244}
245
246
247// This creates a buffer to capture stuff to.
248//size is the size, in ms that needs to be created,
249//at a sampling frequency determined by the class
250bool PlatformAudioIn::CreateBuffer(int size)
251{
252 if(mWave.get())
253 {
254 PError::SignalFatalError("Attempting to add a buffer to an input stream that already has one.\n");
255 }
256
257 // Create a new AudioInfo object wrapped in counted_ptr
258 mWave = counted_ptr<AudioInfo>(new AudioInfo());
259 std::cerr << "CreateBuffer: Created AudioInfo object at " << (void*)mWave.get() << "\n";
260
261 //Make a SDL_AudioSpec;
262 SDL_AudioSpec *spec = (SDL_AudioSpec *) malloc(sizeof(SDL_AudioSpec));
263 spec->freq =44100;
264 spec->format=AUDIO_S16;
265 spec->channels=1;
266 spec->silence=0x80;
267 spec->samples=256; //4096
268 spec->callback= NULL; //Don't have a callback for playing here.
269 spec->userdata=NULL;
270
271 Uint32 length = spec->freq * size/1000;
272 mWave->spec = *spec;
273
274 //allocate the buffer:
275 Uint32 bufferSize = mBytesPerSample*length;
276 mWave->audio = (Uint8*)malloc(bufferSize);
277 std::cerr << "CreateBuffer: Allocated " << bufferSize << " bytes at " << (void*)mWave->audio << "\n";
278 if(mWave->audio)
279 {
280 // cerr << "Memory allocated\n";
281 }
282 else
283 {
284 PError::SignalFatalError("Unable to allocate audio input buffer\n");
285 }
286
287 mWave->bytesPerSample= mBytesPerSample;
288 mWave->audiolen = mBytesPerSample*length;
289 mWave->audiopos = 0;
290 mWave->recordpos = 0;
291 mWave->counter = 0;
292 mWave->name = NULL;
293
294 //attach the buffer to the extern global buffer so that the callback can use it:
295 gAudioBuffer = mWave.get();
296 std::cerr << "CreateBuffer: Set gAudioBuffer to " << (void*)gAudioBuffer << "\n";
297#if 0
298 cerr << "---------------------------\n";
299 cerr << "Creating buffer: \n";
300 cerr << "Bytespersample: " << mBytesPerSample << endl;
301 cerr << "Size (samples): " << size << endl;
302 cerr << "Size (bytes): " << mWave->audiolen << endl;
303 cerr << "freq "<<mWave->spec.freq <<endl;
304 cerr << "length: " <<mWave->audiolen<< endl;
305 cerr << "---------------------------\n";
306#endif
307
308 return true;
309}
310
311
312bool PlatformAudioIn::RecordToBuffer()
313{
314 if(mAudioDevice == 0)
315 {
316 PError::SignalWarning("Audio device not initialized");
317 return false;
318 }
319
320 // std::cerr << "RecordToBuffer: Unpausing audio device " << mAudioDevice << "\n";
321 SDL_PauseAudioDevice(mAudioDevice, 0); // 0 = unpause/start recording
322
323 // Check the status (commented out for production)
324 // SDL_AudioStatus status = SDL_GetAudioDeviceStatus(mAudioDevice);
325 // std::cerr << "Audio device status after unpause: " << status
326 // << " (SDL_AUDIO_PLAYING=" << SDL_AUDIO_PLAYING << ")\n";
327
328 return true;
329}
330
331
332bool PlatformAudioIn::PauseAudioMonitor()
333{
334 if(mAudioDevice == 0)
335 {
336 return false;
337 }
338
339 SDL_PauseAudioDevice(mAudioDevice, 1); // 1 = pause recording (device remains open)
340 return true;
341}
342
343
344bool PlatformAudioIn::CloseAudio()
345{
346 if(mAudioDevice == 0)
347 {
348 return false; // Already closed
349 }
350
351 std::cerr << "CloseAudio: Starting cleanup (device=" << mAudioDevice << ")\n";
352
353 // CRITICAL: Lock the audio device to prevent callbacks from running
354 // This ensures thread-safe access to gAudioBuffer
355 SDL_LockAudioDevice(mAudioDevice);
356
357 // Pause device while locked
358 SDL_PauseAudioDevice(mAudioDevice, 1);
359
360 // Clear global buffer pointer while holding the lock
361 // Any callbacks that were queued will now see NULL and exit immediately
362 if(mWave.get() && mWave.get() == gAudioBuffer)
363 {
364 std::cerr << "CloseAudio: Clearing gAudioBuffer\n";
365 gAudioBuffer = NULL;
366 }
367
368 // Unlock to allow any queued callbacks to see NULL and exit
369 SDL_UnlockAudioDevice(mAudioDevice);
370
371 // Wait for callbacks to fully drain
372 // Increased from 50ms to 200ms to ensure all queued callbacks complete
373 std::cerr << "CloseAudio: Waiting for callbacks to drain...\n";
375
376 // Now safe to close device - all callbacks have exited
377 std::cerr << "CloseAudio: Closing SDL device\n";
378 SDL_CloseAudioDevice(mAudioDevice);
379 mAudioDevice = 0;
380
381 // Release our counted_ptr reference
382 // AudioInfo now has a destructor that will free the malloc'd buffer
383 // when the last reference is released
384 if(mWave.get())
385 {
386 std::cerr << "CloseAudio: Releasing AudioInfo counted_ptr (destructor will free buffer when refcount=0)\n";
387 mWave = counted_ptr<AudioInfo>(); // Reset to NULL, releasing our reference
388 }
389
390 std::cerr << "CloseAudio: Complete\n";
391
392 return true;
393}
394
395
396// simple voicekey. It will process a buffer, computing power
397// for 10-ms windows every 1 ms. It will 'trip' when 95% of the 1-ms windows
398// have power greater than the threshold for sustain, and stop when
399// the power goes below the threshold for 95% of the time for sustain/2.
400// It then reprocesses the power stream to find the point at which the
401// power went above the threshold.
402
403Variant PlatformAudioIn::VoiceKey(double threshold, unsigned int sustain)
404{
405
406
407
408 //how big a chunk, in samples, will 1 ms of time take up?
409
410 int binspersec=1000;
411
412 //this is the number of samples (not bytes) per chunk
413 unsigned int chunksize = mSampleRate/binspersec;
414 double msperchunk = (double)mSampleRate/chunksize/1000;
415
416 //number of samples needed for the sustain parameter.
417 unsigned int sustainSamples = sustain/msperchunk;
418
419
420 //This should be conditional:
421 //Initialize(1);
422 // CreateBuffer(samples);
423
424
425 //audio will immediately be filling up the buffer. When full,
426 //recording will stop, but we can potentially stop it any time
427 //prior to that too.
428
429
430 //buffer duration in chunks (roughly 1-ms)
431#if 0
432 cerr << "------------------------\n";
433 cerr <<"Computing buffer time\n";
434 cerr << "audiolen: " << mWave->audiolen << endl;
435 cerr << "bytespersample " << mWave->bytesPerSample << endl;
436 cerr << "bytes: " << mBytesPerSample << endl;
437
438 cerr << "chunksize: " << chunksize << endl;
439#endif
440
441 int buffertime = double(mWave->audiolen)/mBytesPerSample/chunksize;
442 //Make a power buffer equal to the number of ms in the sample buffer.
443 std::vector<double> powerbins = std::vector<double>(buffertime);
444
445 bool trip = false;
446 bool stop = 0;
447 unsigned int triptime = 0;
448 unsigned int offtime = 0; //The time the speech stops.
449 unsigned int tickID=0;
450 unsigned int sampleID=0;
451
452 mWave->recordpos =0;
453 mWave->counter = 0;
454 //we should blank out the audio that is in here.
455 memset(mWave->audio,0,mWave->audiolen);
456
457 //start audio recording.
459#if 0
460 cerr << "recording\n";
461 // cerr << "timeout: " << timeout << endl;
462 cerr << "chunksize: " << chunksize << endl;
463 cerr << "msperchunk: " << msperchunk << endl;
464 cerr << "sustain: " << sustain << endl;
465 cerr << "sustainsamples: " << sustainSamples << endl;
466 cerr << "power bins: " << buffertime << endl;
467
468#endif
469
470 int abovecount = 0;
471
472 //This is not thread-safe.
473
474
475 double powr;
476 double energy;
477 double power;
478 int signs;
479 int directions;
480 double rmssd;
481 while(!stop)
482 {
483
484
485 //process another bin as long as the recording position is greater than
486 //one bin ahead. Transform into samples first.
487 //cerr << gAudioBuffer->recordpos/mBytesPerSample << ":" << (chunksize+sampleID) << endl;
488 while((gAudioBuffer->recordpos/mBytesPerSample) > chunksize+sampleID)
489 {
490 //cerr <<" buffering "<< sampleID <<":"<< gAudioBuffer->recordpos <<" " << (gAudioBuffer->recordpos - sampleID) << " " << samples << endl;
491
492 // CRITICAL: Bounds check BEFORE any operations
493 // This prevents buffer overflow in powerbins vector
494 if(tickID >= buffertime)
495 {
496 std::cerr << "Voice key: Buffer full without detection (tickID=" << tickID
497 << " >= buffertime=" << buffertime << ")\n";
498 stop = true;
499 break;
500 }
501
502 //power[tickID] = ;
503
504 ComputeStats((Sint16*)(gAudioBuffer->audio+sampleID*mBytesPerSample),chunksize,
505 energy,power,signs,directions,rmssd);
506
507 powerbins[tickID] = energy;
508 //powr = Power((Sint16*)(gAudioBuffer->audio+sampleID*mBytesPerSample),chunksize);
509
510 //The following produces 'mini-scopes for each statistic we compute.
511#if 0
512
513 cerr << SDL_GetTicks();
514 //power
515 cerr << "[";
516 int k;
517 for(k = 0; k< 10*power;k++)cerr<<" "<<std::flush;
518 //cerr << power << endl;
519 cerr << "*";
520 for(int j=k; j<10; j++) cerr << " ";
521 cerr << "]";
522
523 //energy
524
525 cerr << "[";
526 for(k = 0; k< 10*energy;k++)cerr<<" "<<std::flush;
527 cerr << "*";
528 for(int j=k; j<10; j++) cerr << " ";
529 cerr << "]";
530
531 //signs
532 cerr << "[";
533 for(k = 0; k< 10.0*signs/chunksize;k++)cerr<<" "<<std::flush;
534 cerr << "*";
535 for(int j=k; j<10; j++) cerr << " ";
536 cerr << "]";
537
538 //directions
539 cerr << "[";
540 for(k = 0; k< 10.0*directions/chunksize;k++)cerr<<" "<<std::flush;
541 cerr << "*";
542 for(int j=k; j<10; j++) cerr << " ";
543 cerr << "]";
544 cerr << endl;
545
546
547#endif
548
549 // cerr << "X" << powr << " " << energy << " " << power << " " << signs << " " << directions << " " << rmssd << endl;
550
551 //if(powerbins[tickID] > threshold) cerr << "********** " << abovecount <<endl;
552
553
554 int incoming = (powerbins[tickID]>threshold);
555 int outgoing = (tickID < sustainSamples)? 0:powerbins[tickID-sustainSamples]>threshold;
556 abovecount += incoming - outgoing;
557 //cerr << ((double)abovecount)/sustainSamples ;
558 if(((double)abovecount)/sustainSamples > .55 &trip==false)
559 {
560 trip = true;
561 triptime = tickID -(sustainSamples*.55);
562 //cerr << "!!!!!!!!!!!!!!!!!!VOICE KEY TRIPPED!!!!!!!!!!!!!!!!!!\n" ;
563 }
564
565 if(trip)
566 {
567 //cerr << "*****************";
568 //If we have tripped, see if 50% or more of the
569 //samples are below the threshold.
570 if((double)abovecount/sustainSamples < .2)
571 {
572 //cerr << "<<<<<<<<<<<<<";
573 //stop recording.
574 PauseAudioMonitor();
575 stop = true;
576 offtime = tickID- (sustainSamples*.8);
577 }
578
579
580 }
581 //cerr << endl;
582
583
584
585 tickID++;
586 sampleID += chunksize;
587
588
589 if(sampleID+chunksize >= gAudioBuffer->audiolen/mBytesPerSample)
590 stop = true;
591
592
593 // cerr << sampleID << " > "<< gAudioBuffer->audiolen << " -----";
594 // cerr <<tickID << " " << stop;
595 }
597 }
598
599
600
601 // triptime = triptime * msperchunk
602 // tripped = trip
603 // offtime = offtime * msperchunk
604
605 std::cerr << "VoiceKey: Creating return value (triptime=" << (triptime * msperchunk)
606 << ", offtime=" << (offtime * msperchunk) << ", trip=" << trip << ")\n";
607
608 std::cerr << "VoiceKey: About to create PList...\n";
609 PList * newlist = new PList();
610 std::cerr << "VoiceKey: PList created at " << (void*)newlist << "\n";
611
612 std::cerr << "VoiceKey: Pushing back values...\n";
613 newlist->PushBack(Variant(triptime * msperchunk));
614 newlist->PushBack(Variant(offtime * msperchunk));
615 newlist->PushBack(Variant(trip));
616 // cerr << "Returning: " << *newlist << endl;
617
618 std::cerr << "VoiceKey: Creating counted_ptr<PEBLObjectBase>...\n";
620 std::cerr << "VoiceKey: counted_ptr created\n";
621
622 std::cerr << "VoiceKey: Creating PComplexData...\n";
623 PComplexData * pcd = new PComplexData(baselist);
624 std::cerr << "VoiceKey: PComplexData created\n";
625
626 //cerr << "Saving to out.wav\n";
627 //SaveBufferToWave("out.wav");
628
629 std::cerr << "VoiceKey: Returning Variant\n";
630 return Variant(pcd);
631}
632
633
634void PlatformAudioIn::SaveBufferToWave(Variant filename)
635{
636 //Code here adapted from
637 //http://www.codeproject.com/Messages/3208219/How-to-write-mic-data-to-wav-file.aspx
638
639 int bitsPerSample = mBytesPerSample*8;
640
641 //Unclear about these chunk things:
642 int subchunk1size = 16;
643 int numChannels = mWave->spec.channels;
644 int subchunk2size = mWave->recordpos;
645 int chunksize = 36+subchunk2size;
646
647
648 int audioFormat = 1; //PCM
649
650 int sampleRate = mWave->spec.freq;
651 int byteRate = mWave->spec.freq*numChannels*bitsPerSample/8;
652 int blockAlign = numChannels*bitsPerSample/8;
653
654
655 std::fstream myFile (filename.GetString().c_str(), ios::out | ios::binary);
656
657 // write the wav file per the wav file format
658 myFile.seekp (0, ios::beg);
659 myFile.write ("RIFF", 4); // chunk id
660 myFile.write ((char*) &chunksize, 4); // chunk size (36 + SubChunk2Size))
661 myFile.write ("WAVE", 4); // format
662 myFile.write ("fmt ", 4); // subchunk1ID
663 myFile.write ((char*) &subchunk1size, 4); // subchunk1size (16 for PCM)
664 myFile.write ((char*) &audioFormat, 2); // AudioFormat (1 for PCM)
665 myFile.write ((char*) &numChannels, 2); // NumChannels
666 myFile.write ((char*) &sampleRate, 4); // sample rate
667 myFile.write ((char*) &byteRate, 4); // byte rate (SampleRate * NumChannels * BitsPerSample/8)
668 myFile.write ((char*) &blockAlign, 2); // block align (NumChannels * BitsPerSample/8)
669 myFile.write ((char*) &bitsPerSample, 2); // bits per sample
670
671 myFile.write ("data", 4); // subchunk2ID
672 myFile.write ((char*) &subchunk2size, 4); // subchunk2size (NumSamples * NumChannels * BitsPerSample/8)
673
674 myFile.write ((char*)(mWave->audio), mWave->recordpos); // data
675
676
677}
678
679void AudioInCallbackFill(void * udata, Uint8 * stream, int len)
680{
681 static int callbackCount = 0;
682 callbackCount++;
683
684 //len is in bytes.
685
686 //SDL_AudioSpec *spec=(SDL_AudioSpec *)udata;
687 //Sint16 *sData=(Sint16 *)stream;
688 Uint8 * sData = stream;
689
690 // int samples=(len/2);
691
692 if(gAudioBuffer)
693 {
694 //This gives the number of left in the buffer.
695 int remaininbuffer = (gAudioBuffer->audiolen - gAudioBuffer->recordpos);
696
697
698 //We want to copy up to len bytes. But if only as many as remain in the buffer.
699 //tocopy is how many bytes we can copy:
700 int bytestocopy = (len< remaininbuffer ? len: remaininbuffer);
701
702 // Commented out for production - enable for debugging
703 // if(callbackCount % 100 == 1) { // Print every 100th callback
704 // // Check if stream has any non-zero data
705 // int nonZero = 0;
706 // for(int i = 0; i < len && i < 100; i++) {
707 // if(sData[i] != 0) nonZero++;
708 // }
709 // std::cerr << "AudioCallback #" << callbackCount
710 // << ": len=" << len
711 // << " recordpos=" << gAudioBuffer->recordpos
712 // << " tocopy=" << bytestocopy
713 // << " nonZeroInStream=" << nonZero << "/100" << std::endl;
714 // }
715
716 //stop copying if the buffer is full.
717
718 if(bytestocopy>0)
719 {
720
721 //copy to the buffer
722 memcpy(gAudioBuffer->audio+(gAudioBuffer->recordpos),
723 sData,
724 bytestocopy);
725 gAudioBuffer->recordpos += bytestocopy;
726
727 }
728 }
729 else
730 {
731 if(callbackCount == 1) {
732 std::cerr << "WARNING: AudioCallback called but gAudioBuffer is NULL!\n";
733 }
734 }
735
736
737}
738
739
740//This implements a ring buffer that continuously records, wrapping around when full.
741//This allows continuous monitoring without stopping when the buffer fills.
742void AudioInCallbackLoop(void * udata, Uint8 * stream, int len)
743{
744 static int callbackCount = 0;
745 callbackCount++;
746
747 Uint8 * sData = stream;
748
749 if(gAudioBuffer)
750 {
751 // Ring buffer implementation: write with wrap-around
752 for(int i = 0; i < len; i++)
753 {
754 // Calculate write position with modulo for wrap-around
755 Uint32 writePos = (gAudioBuffer->recordpos + i) % gAudioBuffer->audiolen;
756 gAudioBuffer->audio[writePos] = sData[i];
757 }
758
759 // Update total bytes written (this grows indefinitely and is used to extract recent samples)
760 gAudioBuffer->recordpos += len;
761
762 // Increment callback counter (can be used to track activity)
763 gAudioBuffer->counter++;
764
765 if(callbackCount % 500 == 1) { // Print every 500th callback
766 std::cerr << "AudioCallbackLoop #" << callbackCount
767 << ": total_bytes=" << gAudioBuffer->recordpos
768 << " buffer_wrap_count=" << (gAudioBuffer->recordpos / gAudioBuffer->audiolen)
769 << std::endl;
770 }
771 }
772 else
773 {
774 if(callbackCount == 1) {
775 std::cerr << "WARNING: AudioCallbackLoop called but gAudioBuffer is NULL!\n";
776 }
777 }
778}
779
780
781
782
783
784
785// Get audio statistics for the most recent N milliseconds from the ring buffer
786// This extracts recent audio samples and computes all audio statistics
787// Returns: [energy, power, rmssd, signchanges, directions] as a PEBL list
788Variant PlatformAudioIn::GetRecentAudioStats(int milliseconds)
789{
790 if(!mWave.get() || !mWave->audio) {
791 PError::SignalWarning("No audio buffer available in GetRecentAudioStats()");
792 // Return [0, 0, 0, 0, 0]
793 PList * result = new PList();
794 result->PushBack(Variant(0.0));
795 result->PushBack(Variant(0.0));
796 result->PushBack(Variant(0.0));
797 result->PushBack(Variant(0));
798 result->PushBack(Variant(0));
800 return Variant(new PComplexData(baseresult));
801 }
802
803 // CRITICAL: Lock audio device to prevent race conditions
804 // Audio callbacks run in separate thread and modify recordpos/audio[]
805 if(mAudioDevice > 0) {
806 SDL_LockAudioDevice(mAudioDevice);
807 }
808
809 // Calculate how many bytes we need for the requested time window
810 Uint32 bytesWanted = (milliseconds / 1000.0) * mSampleRate * mBytesPerSample;
811
812 // Clamp to buffer size
813 if(bytesWanted > mWave->audiolen) {
814 bytesWanted = mWave->audiolen;
815 }
816
817 // Get the total bytes written (this grows indefinitely in ring buffer mode)
818 Uint32 totalBytesWritten = mWave->recordpos;
819
820 // If we haven't written enough data yet, use what we have
821 if(totalBytesWritten < bytesWanted) {
822 bytesWanted = totalBytesWritten;
823 }
824
825 if(bytesWanted == 0) {
826 // No data yet - unlock before returning
827 if(mAudioDevice > 0) {
828 SDL_UnlockAudioDevice(mAudioDevice);
829 }
830 PList * result = new PList();
831 result->PushBack(Variant(0.0));
832 result->PushBack(Variant(0.0));
833 result->PushBack(Variant(0.0));
834 result->PushBack(Variant(0));
835 result->PushBack(Variant(0));
837 return Variant(new PComplexData(baseresult));
838 }
839
840 // Calculate end position (most recent byte) with wrap-around
841 Uint32 endPos = totalBytesWritten % mWave->audiolen;
842
843 // Calculate start position
844 Uint32 startPos;
845 if(endPos >= bytesWanted) {
846 // Simple case: no wrap-around needed
847 startPos = endPos - bytesWanted;
848 } else {
849 // Wrap-around case: start position is near the end of buffer
850 startPos = mWave->audiolen - (bytesWanted - endPos);
851 }
852
853 // Extract samples from ring buffer into temporary linear buffer
854 Sint16 * tempBuffer = (Sint16*)malloc(bytesWanted);
855 if(!tempBuffer) {
856 // Unlock before returning on error
857 if(mAudioDevice > 0) {
858 SDL_UnlockAudioDevice(mAudioDevice);
859 }
860 PError::SignalWarning("Memory allocation failed in GetRecentAudioStats()");
861 PList * result = new PList();
862 result->PushBack(Variant(0.0));
863 result->PushBack(Variant(0.0));
864 result->PushBack(Variant(0.0));
865 result->PushBack(Variant(0));
866 result->PushBack(Variant(0));
868 return Variant(new PComplexData(baseresult));
869 }
870
871 // Copy samples handling wrap-around
872 Uint32 pos = startPos;
873 for(Uint32 i = 0; i < bytesWanted; i++) {
874 ((Uint8*)tempBuffer)[i] = mWave->audio[pos];
875 pos = (pos + 1) % mWave->audiolen;
876 }
877
878 // CRITICAL: Unlock ASAP after copying data
879 // Now safe to process without holding the lock
880 if(mAudioDevice > 0) {
881 SDL_UnlockAudioDevice(mAudioDevice);
882 }
883
884 // Compute statistics using existing ComputeStats function
885 double energy, power, rmssd;
886 int signs, directions;
887
888 int numSamples = bytesWanted / mBytesPerSample;
889 ComputeStats(tempBuffer, numSamples, power, energy, signs, directions, rmssd);
890
891 // Clean up temporary buffer
892 free(tempBuffer);
893
894 // Return [energy, power, rmssd, signchanges, directions] as PEBL list
895 PList * result = new PList();
896 result->PushBack(Variant(energy));
897 result->PushBack(Variant(power));
898 result->PushBack(Variant(rmssd));
899 result->PushBack(Variant(signs));
900 result->PushBack(Variant(directions));
901
903 return Variant(new PComplexData(baseresult));
904}
905
906
907// Computes power for a specific range.
908double PlatformAudioIn::Power (Sint16 * data, int length)
909{
910
911 double sum = 0;
912 for(int i=0;i <length; i+=mBytesPerSample)
913 {
914 double tmp = (double)abs(data[i])/32768 ;
915 sum += tmp;
916 //cerr << " " <<data[i] << " "<< tmp << endl;
917 }
918
919 //cerr << "Power sum on "<<length << "bytes: " << sum << ": " ;
920
921 double power =(sum)/length;
922 return power;
923}
924
925
926
927
928
929// Computes power for a specific range.
930void PlatformAudioIn::ComputeStats (Sint16 * data, int length,
931 double & power,
932 double & energy,
933 int & signchanges,
934 int & dchanges,
935 double & rmssd )
936{
937
938 //This computes several stats related to detecting
939 //onsets of speech:
940 //sign change,
941 //power
942 //RMSSD
943
944
945 double abssum = 0;
946 double sqsum = 0;
947 int signsum = 0;
948 int dirsum = 0;
949 double rmssdsum=0;
950
951 double prev=0;
952 double scaled;
953 double delta = 0;
954 double prevdelta = 0;
955
956 for(int i=0;i <length; i+=mBytesPerSample)
957 {
958
959
960 scaled = ((double)data[i])/32768;
961 //cerr << "-----"<< scaled << "|" << data[i]<<endl;
962 abssum += abs(scaled);
963 sqsum += scaled*scaled;
964 signsum += (scaled * prev)<0;
965
966 delta = scaled - prev;
967 dirsum += (delta * prevdelta) < 0;
968
969 rmssdsum += pow(delta - prevdelta,2);
970
971
972 //update
973 prev = scaled;
974 prevdelta = delta;
975
976 }
977
978 //cerr << "Power sum on "<<length << "bytes: " << sum << ": " ;
979
980 int samples = length/mBytesPerSample;
981
982 energy = abssum/samples;
983 power = sqrt(sqsum/samples);
984 rmssd = sqrt(rmssdsum/samples);
985 signchanges = signsum;
986 dchanges=dirsum;
987
988}
989
990#endif
#define NULL
Definition BinReloc.cpp:317
Definition PList.h:45
void PushBack(const Variant &v)
Definition PList.cpp:149
virtual void Sleep(unsigned long int msecs)
std::string GetString() const
Definition Variant.cpp:1056
PlatformTimer myTimer
Variant RecordToBuffer(Variant v)
void SignalWarning(const std::string &message)
Definition PError.cpp:119
void SignalFatalError(const std::string &message)
Uint8 * audio
SDL_AudioSpec spec
Uint32 audiolen