PortAudio  2.0
paex_record_file.c
Go to the documentation of this file.
1 
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 /* #define SAMPLE_RATE (17932) // Test failure to open with this value. */
56 #define FILE_NAME "audio_data.raw"
57 #define SAMPLE_RATE (44100)
58 #define FRAMES_PER_BUFFER (512)
59 #define NUM_SECONDS (10)
60 #define NUM_CHANNELS (2)
61 #define NUM_WRITES_PER_BUFFER (4)
62 /* #define DITHER_FLAG (paDitherOff) */
63 #define DITHER_FLAG (0)
64 
65 
66 /* Select sample format. */
67 #if 1
68 #define PA_SAMPLE_TYPE paFloat32
69 typedef float SAMPLE;
70 #define SAMPLE_SILENCE (0.0f)
71 #define PRINTF_S_FORMAT "%.8f"
72 #elif 1
73 #define PA_SAMPLE_TYPE paInt16
74 typedef short SAMPLE;
75 #define SAMPLE_SILENCE (0)
76 #define PRINTF_S_FORMAT "%d"
77 #elif 0
78 #define PA_SAMPLE_TYPE paInt8
79 typedef char SAMPLE;
80 #define SAMPLE_SILENCE (0)
81 #define PRINTF_S_FORMAT "%d"
82 #else
83 #define PA_SAMPLE_TYPE paUInt8
84 typedef unsigned char SAMPLE;
85 #define SAMPLE_SILENCE (128)
86 #define PRINTF_S_FORMAT "%d"
87 #endif
88 
89 typedef struct
90 {
91  unsigned frameIndex;
92  int threadSyncFlag;
93  SAMPLE *ringBufferData;
94  PaUtilRingBuffer ringBuffer;
95  FILE *file;
96  void *threadHandle;
97 }
99 
100 /* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */
101 static int threadFunctionWriteToRawFile(void* ptr)
102 {
103  paTestData* pData = (paTestData*)ptr;
104 
105  /* Mark thread started */
106  pData->threadSyncFlag = 0;
107 
108  while (1)
109  {
110  ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer);
111  if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) ||
112  pData->threadSyncFlag )
113  {
114  void* ptr[2] = {0};
115  ring_buffer_size_t sizes[2] = {0};
116 
117  /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */
118  ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
119  if (elementsRead > 0)
120  {
121  int i;
122  for (i = 0; i < 2 && ptr[i] != NULL; ++i)
123  {
124  fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
125  }
126  PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead);
127  }
128 
129  if (pData->threadSyncFlag)
130  {
131  break;
132  }
133  }
134 
135  /* Sleep a little while... */
136  Pa_Sleep(20);
137  }
138 
139  pData->threadSyncFlag = 0;
140 
141  return 0;
142 }
143 
144 /* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file
145  has reached EOF, a flag is set so that the play PA callback can return paComplete */
146 static int threadFunctionReadFromRawFile(void* ptr)
147 {
148  paTestData* pData = (paTestData*)ptr;
149 
150  while (1)
151  {
152  ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer);
153 
154  if (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER)
155  {
156  void* ptr[2] = {0};
157  ring_buffer_size_t sizes[2] = {0};
158 
159  /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */
160  PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
161 
162  if (!feof(pData->file))
163  {
164  ring_buffer_size_t itemsReadFromFile = 0;
165  int i;
166  for (i = 0; i < 2 && ptr[i] != NULL; ++i)
167  {
168  itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
169  }
170  PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile);
171 
172  /* Mark thread started here, that way we "prime" the ring buffer before playback */
173  pData->threadSyncFlag = 0;
174  }
175  else
176  {
177  /* No more data to read */
178  pData->threadSyncFlag = 1;
179  break;
180  }
181  }
182 
183  /* Sleep a little while... */
184  Pa_Sleep(20);
185  }
186 
187  return 0;
188 }
189 
190 typedef int (*ThreadFunctionType)(void*);
191 
192 /* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend
193  to posix type OSs (Linux/Mac) */
194 static PaError startThread( paTestData* pData, ThreadFunctionType fn )
195 {
196 #ifdef _WIN32
197  typedef unsigned (__stdcall* WinThreadFunctionType)(void*);
198  pData->threadHandle = (void*)_beginthreadex(NULL, 0, (WinThreadFunctionType)fn, pData, CREATE_SUSPENDED, NULL);
199  if (pData->threadHandle == NULL) return paUnanticipatedHostError;
200 
201  /* Set file thread to a little higher prio than normal */
202  SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL);
203 
204  /* Start it up */
205  pData->threadSyncFlag = 1;
206  ResumeThread(pData->threadHandle);
207 
208 #endif
209 
210  /* Wait for thread to startup */
211  while (pData->threadSyncFlag) {
212  Pa_Sleep(10);
213  }
214 
215  return paNoError;
216 }
217 
218 static int stopThread( paTestData* pData )
219 {
220  pData->threadSyncFlag = 1;
221  /* Wait for thread to stop */
222  while (pData->threadSyncFlag) {
223  Pa_Sleep(10);
224  }
225 #ifdef _WIN32
226  CloseHandle(pData->threadHandle);
227  pData->threadHandle = 0;
228 #endif
229 
230  return paNoError;
231 }
232 
233 
234 /* This routine will be called by the PortAudio engine when audio is needed.
235 ** It may be called at interrupt level on some machines so don't do anything
236 ** that could mess up the system like calling malloc() or free().
237 */
238 static int recordCallback( const void *inputBuffer, void *outputBuffer,
239  unsigned long framesPerBuffer,
240  const PaStreamCallbackTimeInfo* timeInfo,
241  PaStreamCallbackFlags statusFlags,
242  void *userData )
243 {
244  paTestData *data = (paTestData*)userData;
245  ring_buffer_size_t elementsWriteable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer);
246  ring_buffer_size_t elementsToWrite = min(elementsWriteable, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
247  const SAMPLE *rptr = (const SAMPLE*)inputBuffer;
248 
249  (void) outputBuffer; /* Prevent unused variable warnings. */
250  (void) timeInfo;
251  (void) statusFlags;
252  (void) userData;
253 
254  data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite);
255 
256  return paContinue;
257 }
258 
259 /* This routine will be called by the PortAudio engine when audio is needed.
260 ** It may be called at interrupt level on some machines so don't do anything
261 ** that could mess up the system like calling malloc() or free().
262 */
263 static int playCallback( const void *inputBuffer, void *outputBuffer,
264  unsigned long framesPerBuffer,
265  const PaStreamCallbackTimeInfo* timeInfo,
266  PaStreamCallbackFlags statusFlags,
267  void *userData )
268 {
269  paTestData *data = (paTestData*)userData;
270  ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer);
271  ring_buffer_size_t elementsToRead = min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
272  SAMPLE* wptr = (SAMPLE*)outputBuffer;
273 
274  (void) inputBuffer; /* Prevent unused variable warnings. */
275  (void) timeInfo;
276  (void) statusFlags;
277  (void) userData;
278 
279  data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead);
280 
281  return data->threadSyncFlag ? paComplete : paContinue;
282 }
283 
284 static unsigned NextPowerOf2(unsigned val)
285 {
286  val--;
287  val = (val >> 1) | val;
288  val = (val >> 2) | val;
289  val = (val >> 4) | val;
290  val = (val >> 8) | val;
291  val = (val >> 16) | val;
292  return ++val;
293 }
294 
295 /*******************************************************************/
296 int main(void);
297 int main(void)
298 {
299  PaStreamParameters inputParameters,
300  outputParameters;
301  PaStream* stream;
302  PaError err = paNoError;
303  paTestData data = {0};
304  unsigned delayCntr;
305  unsigned numSamples;
306  unsigned numBytes;
307 
308  printf("patest_record.c\n"); fflush(stdout);
309 
310  /* We set the ring buffer size to about 500 ms */
311  numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS));
312  numBytes = numSamples * sizeof(SAMPLE);
313  data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes );
314  if( data.ringBufferData == NULL )
315  {
316  printf("Could not allocate ring buffer data.\n");
317  goto done;
318  }
319 
320  if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0)
321  {
322  printf("Failed to initialize ring buffer. Size is not power of 2 ??\n");
323  goto done;
324  }
325 
326  err = Pa_Initialize();
327  if( err != paNoError ) goto done;
328 
329  inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
330  if (inputParameters.device == paNoDevice) {
331  fprintf(stderr,"Error: No default input device.\n");
332  goto done;
333  }
334  inputParameters.channelCount = 2; /* stereo input */
335  inputParameters.sampleFormat = PA_SAMPLE_TYPE;
336  inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
337  inputParameters.hostApiSpecificStreamInfo = NULL;
338 
339  /* Record some audio. -------------------------------------------- */
340  err = Pa_OpenStream(
341  &stream,
342  &inputParameters,
343  NULL, /* &outputParameters, */
344  SAMPLE_RATE,
345  FRAMES_PER_BUFFER,
346  paClipOff, /* we won't output out of range samples so don't bother clipping them */
347  recordCallback,
348  &data );
349  if( err != paNoError ) goto done;
350 
351  /* Open the raw audio 'cache' file... */
352  data.file = fopen(FILE_NAME, "wb");
353  if (data.file == 0) goto done;
354 
355  /* Start the file writing thread */
356  err = startThread(&data, threadFunctionWriteToRawFile);
357  if( err != paNoError ) goto done;
358 
359  err = Pa_StartStream( stream );
360  if( err != paNoError ) goto done;
361  printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout);
362 
363  /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can
364  increase NUM_SECONDS until you run out of disk */
365  delayCntr = 0;
366  while( delayCntr++ < NUM_SECONDS )
367  {
368  printf("index = %d\n", data.frameIndex ); fflush(stdout);
369  Pa_Sleep(1000);
370  }
371  if( err < 0 ) goto done;
372 
373  err = Pa_CloseStream( stream );
374  if( err != paNoError ) goto done;
375 
376  /* Stop the thread */
377  err = stopThread(&data);
378  if( err != paNoError ) goto done;
379 
380  /* Close file */
381  fclose(data.file);
382  data.file = 0;
383 
384  /* Playback recorded data. -------------------------------------------- */
385  data.frameIndex = 0;
386 
387  outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
388  if (outputParameters.device == paNoDevice) {
389  fprintf(stderr,"Error: No default output device.\n");
390  goto done;
391  }
392  outputParameters.channelCount = 2; /* stereo output */
393  outputParameters.sampleFormat = PA_SAMPLE_TYPE;
394  outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
395  outputParameters.hostApiSpecificStreamInfo = NULL;
396 
397  printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout);
398  err = Pa_OpenStream(
399  &stream,
400  NULL, /* no input */
401  &outputParameters,
402  SAMPLE_RATE,
403  FRAMES_PER_BUFFER,
404  paClipOff, /* we won't output out of range samples so don't bother clipping them */
405  playCallback,
406  &data );
407  if( err != paNoError ) goto done;
408 
409  if( stream )
410  {
411  /* Open file again for reading */
412  data.file = fopen(FILE_NAME, "rb");
413  if (data.file != 0)
414  {
415  /* Start the file reading thread */
416  err = startThread(&data, threadFunctionReadFromRawFile);
417  if( err != paNoError ) goto done;
418 
419  err = Pa_StartStream( stream );
420  if( err != paNoError ) goto done;
421 
422  printf("Waiting for playback to finish.\n"); fflush(stdout);
423 
424  /* The playback will end when EOF is reached */
425  while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {
426  printf("index = %d\n", data.frameIndex ); fflush(stdout);
427  Pa_Sleep(1000);
428  }
429  if( err < 0 ) goto done;
430  }
431 
432  err = Pa_CloseStream( stream );
433  if( err != paNoError ) goto done;
434 
435  fclose(data.file);
436 
437  printf("Done.\n"); fflush(stdout);
438  }
439 
440 done:
441  Pa_Terminate();
442  if( data.ringBufferData ) /* Sure it is NULL or valid. */
443  PaUtil_FreeMemory( data.ringBufferData );
444  if( err != paNoError )
445  {
446  fprintf( stderr, "An error occured while using the portaudio stream\n" );
447  fprintf( stderr, "Error number: %d\n", err );
448  fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
449  err = 1; /* Always return 0 or 1, but no other return codes. */
450  }
451  return err;
452 }
453 
PaError Pa_Initialize(void)
PaDeviceIndex Pa_GetDefaultInputDevice(void)
void PaStream
Definition: portaudio.h:584
PaError Pa_OpenStream(PaStream **stream, const PaStreamParameters *inputParameters, const PaStreamParameters *outputParameters, double sampleRate, unsigned long framesPerBuffer, PaStreamFlags streamFlags, PaStreamCallback *streamCallback, void *userData)
PaTime defaultLowInputLatency
Definition: portaudio.h:459
PaError Pa_StartStream(PaStream *stream)
void * hostApiSpecificStreamInfo
Definition: portaudio.h:528
The portable PortAudio API.
PaSampleFormat sampleFormat
Definition: portaudio.h:508
#define paClipOff
Definition: portaudio.h:610
int PaError
Definition: portaudio.h:70
PaError Pa_IsStreamActive(PaStream *stream)
PaTime suggestedLatency
Definition: portaudio.h:521
unsigned long PaStreamCallbackFlags
Definition: portaudio.h:661
#define paNoDevice
Definition: portaudio.h:169
const PaDeviceInfo * Pa_GetDeviceInfo(PaDeviceIndex device)
PaDeviceIndex Pa_GetDefaultOutputDevice(void)
PaDeviceIndex device
Definition: portaudio.h:495
void Pa_Sleep(long msec)
const char * Pa_GetErrorText(PaError errorCode)
PaError Pa_CloseStream(PaStream *stream)
PaError Pa_Terminate(void)

Generated for PortAudio by  doxygen1.8.11