paex_record_file.c (15810B)
1 /** @file paex_record_file.c 2 @ingroup examples_src 3 @brief Record input into a file, then playback recorded data from file (Windows only at the moment) 4 @author Robert Bielik 5 */ 6 /* 7 * $Id: paex_record_file.c 1752 2011-09-08 03:21:55Z philburk $ 8 * 9 * This program uses the PortAudio Portable Audio Library. 10 * For more information see: http://www.portaudio.com 11 * Copyright (c) 1999-2000 Ross Bencina and Phil Burk 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining 14 * a copy of this software and associated documentation files 15 * (the "Software"), to deal in the Software without restriction, 16 * including without limitation the rights to use, copy, modify, merge, 17 * publish, distribute, sublicense, and/or sell copies of the Software, 18 * and to permit persons to whom the Software is furnished to do so, 19 * subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be 22 * included in all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 28 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 29 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 30 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 */ 32 33 /* 34 * The text above constitutes the entire PortAudio license; however, 35 * the PortAudio community also makes the following non-binding requests: 36 * 37 * Any person wishing to distribute modifications to the Software is 38 * requested to send the modifications to the original developer so that 39 * they can be incorporated into the canonical version. It is also 40 * requested that these non-binding requests be included along with the 41 * license above. 42 */ 43 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include "portaudio.h" 47 #include "pa_ringbuffer.h" 48 #include "pa_util.h" 49 50 #ifdef _WIN32 51 #include <windows.h> 52 #include <process.h> 53 #endif 54 55 static ring_buffer_size_t rbs_min(ring_buffer_size_t a, ring_buffer_size_t b) 56 { 57 return (a < b) ? a : b; 58 } 59 60 /* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ 61 #define FILE_NAME "audio_data.raw" 62 #define SAMPLE_RATE (44100) 63 #define FRAMES_PER_BUFFER (512) 64 #define NUM_SECONDS (10) 65 #define NUM_CHANNELS (2) 66 #define NUM_WRITES_PER_BUFFER (4) 67 /* #define DITHER_FLAG (paDitherOff) */ 68 #define DITHER_FLAG (0) /**/ 69 70 71 /* Select sample format. */ 72 #if 1 73 #define PA_SAMPLE_TYPE paFloat32 74 typedef float SAMPLE; 75 #define SAMPLE_SILENCE (0.0f) 76 #define PRINTF_S_FORMAT "%.8f" 77 #elif 1 78 #define PA_SAMPLE_TYPE paInt16 79 typedef short SAMPLE; 80 #define SAMPLE_SILENCE (0) 81 #define PRINTF_S_FORMAT "%d" 82 #elif 0 83 #define PA_SAMPLE_TYPE paInt8 84 typedef char SAMPLE; 85 #define SAMPLE_SILENCE (0) 86 #define PRINTF_S_FORMAT "%d" 87 #else 88 #define PA_SAMPLE_TYPE paUInt8 89 typedef unsigned char SAMPLE; 90 #define SAMPLE_SILENCE (128) 91 #define PRINTF_S_FORMAT "%d" 92 #endif 93 94 typedef struct 95 { 96 unsigned frameIndex; 97 int threadSyncFlag; 98 SAMPLE *ringBufferData; 99 PaUtilRingBuffer ringBuffer; 100 FILE *file; 101 void *threadHandle; 102 } 103 paTestData; 104 105 /* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */ 106 static int threadFunctionWriteToRawFile(void* ptr) 107 { 108 paTestData* pData = (paTestData*)ptr; 109 110 /* Mark thread started */ 111 pData->threadSyncFlag = 0; 112 113 while (1) 114 { 115 ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer); 116 if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) || 117 pData->threadSyncFlag ) 118 { 119 void* ptr[2] = {0}; 120 ring_buffer_size_t sizes[2] = {0}; 121 122 /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */ 123 ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1); 124 if (elementsRead > 0) 125 { 126 int i; 127 for (i = 0; i < 2 && ptr[i] != NULL; ++i) 128 { 129 fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file); 130 } 131 PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead); 132 } 133 134 if (pData->threadSyncFlag) 135 { 136 break; 137 } 138 } 139 140 /* Sleep a little while... */ 141 Pa_Sleep(20); 142 } 143 144 pData->threadSyncFlag = 0; 145 146 return 0; 147 } 148 149 /* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file 150 has reached EOF, a flag is set so that the play PA callback can return paComplete */ 151 static int threadFunctionReadFromRawFile(void* ptr) 152 { 153 paTestData* pData = (paTestData*)ptr; 154 155 while (1) 156 { 157 ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer); 158 159 if (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) 160 { 161 void* ptr[2] = {0}; 162 ring_buffer_size_t sizes[2] = {0}; 163 164 /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ 165 PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1); 166 167 if (!feof(pData->file)) 168 { 169 ring_buffer_size_t itemsReadFromFile = 0; 170 int i; 171 for (i = 0; i < 2 && ptr[i] != NULL; ++i) 172 { 173 itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file); 174 } 175 PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile); 176 177 /* Mark thread started here, that way we "prime" the ring buffer before playback */ 178 pData->threadSyncFlag = 0; 179 } 180 else 181 { 182 /* No more data to read */ 183 pData->threadSyncFlag = 1; 184 break; 185 } 186 } 187 188 /* Sleep a little while... */ 189 Pa_Sleep(20); 190 } 191 192 return 0; 193 } 194 195 typedef int (*ThreadFunctionType)(void*); 196 197 /* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend 198 to posix type OSs (Linux/Mac) */ 199 static PaError startThread( paTestData* pData, ThreadFunctionType fn ) 200 { 201 #ifdef _WIN32 202 typedef unsigned (__stdcall* WinThreadFunctionType)(void*); 203 pData->threadHandle = (void*)_beginthreadex(NULL, 0, (WinThreadFunctionType)fn, pData, CREATE_SUSPENDED, NULL); 204 if (pData->threadHandle == NULL) return paUnanticipatedHostError; 205 206 /* Set file thread to a little higher prio than normal */ 207 SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL); 208 209 /* Start it up */ 210 pData->threadSyncFlag = 1; 211 ResumeThread(pData->threadHandle); 212 213 #endif 214 215 /* Wait for thread to startup */ 216 while (pData->threadSyncFlag) { 217 Pa_Sleep(10); 218 } 219 220 return paNoError; 221 } 222 223 static int stopThread( paTestData* pData ) 224 { 225 pData->threadSyncFlag = 1; 226 /* Wait for thread to stop */ 227 while (pData->threadSyncFlag) { 228 Pa_Sleep(10); 229 } 230 #ifdef _WIN32 231 CloseHandle(pData->threadHandle); 232 pData->threadHandle = 0; 233 #endif 234 235 return paNoError; 236 } 237 238 239 /* This routine will be called by the PortAudio engine when audio is needed. 240 ** It may be called at interrupt level on some machines so don't do anything 241 ** that could mess up the system like calling malloc() or free(). 242 */ 243 static int recordCallback( const void *inputBuffer, void *outputBuffer, 244 unsigned long framesPerBuffer, 245 const PaStreamCallbackTimeInfo* timeInfo, 246 PaStreamCallbackFlags statusFlags, 247 void *userData ) 248 { 249 paTestData *data = (paTestData*)userData; 250 ring_buffer_size_t elementsWriteable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer); 251 ring_buffer_size_t elementsToWrite = rbs_min(elementsWriteable, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS)); 252 const SAMPLE *rptr = (const SAMPLE*)inputBuffer; 253 254 (void) outputBuffer; /* Prevent unused variable warnings. */ 255 (void) timeInfo; 256 (void) statusFlags; 257 (void) userData; 258 259 data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite); 260 261 return paContinue; 262 } 263 264 /* This routine will be called by the PortAudio engine when audio is needed. 265 ** It may be called at interrupt level on some machines so don't do anything 266 ** that could mess up the system like calling malloc() or free(). 267 */ 268 static int playCallback( const void *inputBuffer, void *outputBuffer, 269 unsigned long framesPerBuffer, 270 const PaStreamCallbackTimeInfo* timeInfo, 271 PaStreamCallbackFlags statusFlags, 272 void *userData ) 273 { 274 paTestData *data = (paTestData*)userData; 275 ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer); 276 ring_buffer_size_t elementsToRead = rbs_min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS)); 277 SAMPLE* wptr = (SAMPLE*)outputBuffer; 278 279 (void) inputBuffer; /* Prevent unused variable warnings. */ 280 (void) timeInfo; 281 (void) statusFlags; 282 (void) userData; 283 284 data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead); 285 286 return data->threadSyncFlag ? paComplete : paContinue; 287 } 288 289 static unsigned NextPowerOf2(unsigned val) 290 { 291 val--; 292 val = (val >> 1) | val; 293 val = (val >> 2) | val; 294 val = (val >> 4) | val; 295 val = (val >> 8) | val; 296 val = (val >> 16) | val; 297 return ++val; 298 } 299 300 /*******************************************************************/ 301 int main(void); 302 int main(void) 303 { 304 PaStreamParameters inputParameters, 305 outputParameters; 306 PaStream* stream; 307 PaError err = paNoError; 308 paTestData data = {0}; 309 unsigned delayCntr; 310 unsigned numSamples; 311 unsigned numBytes; 312 313 printf("patest_record.c\n"); fflush(stdout); 314 315 /* We set the ring buffer size to about 500 ms */ 316 numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS)); 317 numBytes = numSamples * sizeof(SAMPLE); 318 data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes ); 319 if( data.ringBufferData == NULL ) 320 { 321 printf("Could not allocate ring buffer data.\n"); 322 goto done; 323 } 324 325 if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0) 326 { 327 printf("Failed to initialize ring buffer. Size is not power of 2 ??\n"); 328 goto done; 329 } 330 331 err = Pa_Initialize(); 332 if( err != paNoError ) goto done; 333 334 inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ 335 if (inputParameters.device == paNoDevice) { 336 fprintf(stderr,"Error: No default input device.\n"); 337 goto done; 338 } 339 inputParameters.channelCount = 2; /* stereo input */ 340 inputParameters.sampleFormat = PA_SAMPLE_TYPE; 341 inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; 342 inputParameters.hostApiSpecificStreamInfo = NULL; 343 344 /* Record some audio. -------------------------------------------- */ 345 err = Pa_OpenStream( 346 &stream, 347 &inputParameters, 348 NULL, /* &outputParameters, */ 349 SAMPLE_RATE, 350 FRAMES_PER_BUFFER, 351 paClipOff, /* we won't output out of range samples so don't bother clipping them */ 352 recordCallback, 353 &data ); 354 if( err != paNoError ) goto done; 355 356 /* Open the raw audio 'cache' file... */ 357 data.file = fopen(FILE_NAME, "wb"); 358 if (data.file == 0) goto done; 359 360 /* Start the file writing thread */ 361 err = startThread(&data, threadFunctionWriteToRawFile); 362 if( err != paNoError ) goto done; 363 364 err = Pa_StartStream( stream ); 365 if( err != paNoError ) goto done; 366 printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout); 367 368 /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can 369 increase NUM_SECONDS until you run out of disk */ 370 delayCntr = 0; 371 while( delayCntr++ < NUM_SECONDS ) 372 { 373 printf("index = %d\n", data.frameIndex ); fflush(stdout); 374 Pa_Sleep(1000); 375 } 376 if( err < 0 ) goto done; 377 378 err = Pa_CloseStream( stream ); 379 if( err != paNoError ) goto done; 380 381 /* Stop the thread */ 382 err = stopThread(&data); 383 if( err != paNoError ) goto done; 384 385 /* Close file */ 386 fclose(data.file); 387 data.file = 0; 388 389 /* Playback recorded data. -------------------------------------------- */ 390 data.frameIndex = 0; 391 392 outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ 393 if (outputParameters.device == paNoDevice) { 394 fprintf(stderr,"Error: No default output device.\n"); 395 goto done; 396 } 397 outputParameters.channelCount = 2; /* stereo output */ 398 outputParameters.sampleFormat = PA_SAMPLE_TYPE; 399 outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; 400 outputParameters.hostApiSpecificStreamInfo = NULL; 401 402 printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout); 403 err = Pa_OpenStream( 404 &stream, 405 NULL, /* no input */ 406 &outputParameters, 407 SAMPLE_RATE, 408 FRAMES_PER_BUFFER, 409 paClipOff, /* we won't output out of range samples so don't bother clipping them */ 410 playCallback, 411 &data ); 412 if( err != paNoError ) goto done; 413 414 if( stream ) 415 { 416 /* Open file again for reading */ 417 data.file = fopen(FILE_NAME, "rb"); 418 if (data.file != 0) 419 { 420 /* Start the file reading thread */ 421 err = startThread(&data, threadFunctionReadFromRawFile); 422 if( err != paNoError ) goto done; 423 424 err = Pa_StartStream( stream ); 425 if( err != paNoError ) goto done; 426 427 printf("Waiting for playback to finish.\n"); fflush(stdout); 428 429 /* The playback will end when EOF is reached */ 430 while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) { 431 printf("index = %d\n", data.frameIndex ); fflush(stdout); 432 Pa_Sleep(1000); 433 } 434 if( err < 0 ) goto done; 435 } 436 437 err = Pa_CloseStream( stream ); 438 if( err != paNoError ) goto done; 439 440 fclose(data.file); 441 442 printf("Done.\n"); fflush(stdout); 443 } 444 445 done: 446 Pa_Terminate(); 447 if( data.ringBufferData ) /* Sure it is NULL or valid. */ 448 PaUtil_FreeMemory( data.ringBufferData ); 449 if( err != paNoError ) 450 { 451 fprintf( stderr, "An error occured while using the portaudio stream\n" ); 452 fprintf( stderr, "Error number: %d\n", err ); 453 fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); 454 err = 1; /* Always return 0 or 1, but no other return codes. */ 455 } 456 return err; 457 } 458