paex_record_file.c

Go to the documentation of this file.
00001 
00006 /*
00007  * $Id: paex_record_file.c 1752 2011-09-08 03:21:55Z philburk $
00008  *
00009  * This program uses the PortAudio Portable Audio Library.
00010  * For more information see: http://www.portaudio.com
00011  * Copyright (c) 1999-2000 Ross Bencina and Phil Burk
00012  *
00013  * Permission is hereby granted, free of charge, to any person obtaining
00014  * a copy of this software and associated documentation files
00015  * (the "Software"), to deal in the Software without restriction,
00016  * including without limitation the rights to use, copy, modify, merge,
00017  * publish, distribute, sublicense, and/or sell copies of the Software,
00018  * and to permit persons to whom the Software is furnished to do so,
00019  * subject to the following conditions:
00020  *
00021  * The above copyright notice and this permission notice shall be
00022  * included in all copies or substantial portions of the Software.
00023  *
00024  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
00025  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
00026  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
00027  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
00028  * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
00029  * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
00030  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
00031  */
00032 
00033 /*
00034  * The text above constitutes the entire PortAudio license; however, 
00035  * the PortAudio community also makes the following non-binding requests:
00036  *
00037  * Any person wishing to distribute modifications to the Software is
00038  * requested to send the modifications to the original developer so that
00039  * they can be incorporated into the canonical version. It is also 
00040  * requested that these non-binding requests be included along with the 
00041  * license above.
00042  */
00043 
00044 #include <stdio.h>
00045 #include <stdlib.h>
00046 #include "portaudio.h"
00047 #include "pa_ringbuffer.h"
00048 #include "pa_util.h"
00049 
00050 #ifdef _WIN32
00051 #include <windows.h>
00052 #include <process.h>
00053 #endif
00054 
00055 /* #define SAMPLE_RATE  (17932) // Test failure to open with this value. */
00056 #define FILE_NAME       "audio_data.raw"
00057 #define SAMPLE_RATE  (44100)
00058 #define FRAMES_PER_BUFFER (512)
00059 #define NUM_SECONDS     (10)
00060 #define NUM_CHANNELS    (2)
00061 #define NUM_WRITES_PER_BUFFER   (4)
00062 /* #define DITHER_FLAG     (paDitherOff) */
00063 #define DITHER_FLAG     (0) 
00064 
00065 
00066 /* Select sample format. */
00067 #if 1
00068 #define PA_SAMPLE_TYPE  paFloat32
00069 typedef float SAMPLE;
00070 #define SAMPLE_SILENCE  (0.0f)
00071 #define PRINTF_S_FORMAT "%.8f"
00072 #elif 1
00073 #define PA_SAMPLE_TYPE  paInt16
00074 typedef short SAMPLE;
00075 #define SAMPLE_SILENCE  (0)
00076 #define PRINTF_S_FORMAT "%d"
00077 #elif 0
00078 #define PA_SAMPLE_TYPE  paInt8
00079 typedef char SAMPLE;
00080 #define SAMPLE_SILENCE  (0)
00081 #define PRINTF_S_FORMAT "%d"
00082 #else
00083 #define PA_SAMPLE_TYPE  paUInt8
00084 typedef unsigned char SAMPLE;
00085 #define SAMPLE_SILENCE  (128)
00086 #define PRINTF_S_FORMAT "%d"
00087 #endif
00088 
00089 typedef struct
00090 {
00091     unsigned            frameIndex;
00092     int                 threadSyncFlag;
00093     SAMPLE             *ringBufferData;
00094     PaUtilRingBuffer    ringBuffer;
00095     FILE               *file;
00096     void               *threadHandle;
00097 }
00098 paTestData;
00099 
00100 /* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */
00101 static int threadFunctionWriteToRawFile(void* ptr)
00102 {
00103     paTestData* pData = (paTestData*)ptr;
00104 
00105     /* Mark thread started */
00106     pData->threadSyncFlag = 0;
00107 
00108     while (1)
00109     {
00110         ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer);
00111         if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) ||
00112              pData->threadSyncFlag )
00113         {
00114             void* ptr[2] = {0};
00115             ring_buffer_size_t sizes[2] = {0};
00116 
00117             /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */
00118             ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
00119             if (elementsRead > 0)
00120             {
00121                 int i;
00122                 for (i = 0; i < 2 && ptr[i] != NULL; ++i)
00123                 {
00124                     fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
00125                 }
00126                 PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead);
00127             }
00128 
00129             if (pData->threadSyncFlag)
00130             {
00131                 break;
00132             }
00133         }
00134 
00135         /* Sleep a little while... */
00136         Pa_Sleep(20);
00137     }
00138 
00139     pData->threadSyncFlag = 0;
00140 
00141     return 0;
00142 }
00143 
00144 /* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file
00145    has reached EOF, a flag is set so that the play PA callback can return paComplete */
00146 static int threadFunctionReadFromRawFile(void* ptr)
00147 {
00148     paTestData* pData = (paTestData*)ptr;
00149 
00150     while (1)
00151     {
00152         ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer);
00153 
00154         if (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER)
00155         {
00156             void* ptr[2] = {0};
00157             ring_buffer_size_t sizes[2] = {0};
00158 
00159             /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */
00160             PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);
00161 
00162             if (!feof(pData->file))
00163             {
00164                 ring_buffer_size_t itemsReadFromFile = 0;
00165                 int i;
00166                 for (i = 0; i < 2 && ptr[i] != NULL; ++i)
00167                 {
00168                     itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);
00169                 }
00170                 PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile);
00171 
00172                 /* Mark thread started here, that way we "prime" the ring buffer before playback */
00173                 pData->threadSyncFlag = 0;
00174             }
00175             else
00176             {
00177                 /* No more data to read */
00178                 pData->threadSyncFlag = 1;
00179                 break;
00180             }
00181         }
00182 
00183         /* Sleep a little while... */
00184         Pa_Sleep(20);
00185     }
00186 
00187     return 0;
00188 }
00189 
00190 typedef int (*ThreadFunctionType)(void*);
00191 
00192 /* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend
00193    to posix type OSs (Linux/Mac) */
00194 static PaError startThread( paTestData* pData, ThreadFunctionType fn )
00195 {
00196 #ifdef _WIN32
00197     typedef unsigned (__stdcall* WinThreadFunctionType)(void*);
00198     pData->threadHandle = (void*)_beginthreadex(NULL, 0, (WinThreadFunctionType)fn, pData, CREATE_SUSPENDED, NULL);
00199     if (pData->threadHandle == NULL) return paUnanticipatedHostError;
00200 
00201     /* Set file thread to a little higher prio than normal */
00202     SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL);
00203 
00204     /* Start it up */
00205     pData->threadSyncFlag = 1;
00206     ResumeThread(pData->threadHandle);
00207 
00208 #endif
00209 
00210     /* Wait for thread to startup */
00211     while (pData->threadSyncFlag) {
00212         Pa_Sleep(10);
00213     }
00214 
00215     return paNoError;
00216 }
00217 
00218 static int stopThread( paTestData* pData )
00219 {
00220     pData->threadSyncFlag = 1;
00221     /* Wait for thread to stop */
00222     while (pData->threadSyncFlag) {
00223         Pa_Sleep(10);
00224     }
00225 #ifdef _WIN32
00226     CloseHandle(pData->threadHandle);
00227     pData->threadHandle = 0;
00228 #endif
00229 
00230     return paNoError;
00231 }
00232 
00233 
00234 /* This routine will be called by the PortAudio engine when audio is needed.
00235 ** It may be called at interrupt level on some machines so don't do anything
00236 ** that could mess up the system like calling malloc() or free().
00237 */
00238 static int recordCallback( const void *inputBuffer, void *outputBuffer,
00239                            unsigned long framesPerBuffer,
00240                            const PaStreamCallbackTimeInfo* timeInfo,
00241                            PaStreamCallbackFlags statusFlags,
00242                            void *userData )
00243 {
00244     paTestData *data = (paTestData*)userData;
00245     ring_buffer_size_t elementsWriteable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer);
00246     ring_buffer_size_t elementsToWrite = min(elementsWriteable, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
00247     const SAMPLE *rptr = (const SAMPLE*)inputBuffer;
00248 
00249     (void) outputBuffer; /* Prevent unused variable warnings. */
00250     (void) timeInfo;
00251     (void) statusFlags;
00252     (void) userData;
00253 
00254     data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite);
00255 
00256     return paContinue;
00257 }
00258 
00259 /* This routine will be called by the PortAudio engine when audio is needed.
00260 ** It may be called at interrupt level on some machines so don't do anything
00261 ** that could mess up the system like calling malloc() or free().
00262 */
00263 static int playCallback( const void *inputBuffer, void *outputBuffer,
00264                          unsigned long framesPerBuffer,
00265                          const PaStreamCallbackTimeInfo* timeInfo,
00266                          PaStreamCallbackFlags statusFlags,
00267                          void *userData )
00268 {
00269     paTestData *data = (paTestData*)userData;
00270     ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer);
00271     ring_buffer_size_t elementsToRead = min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS));
00272     SAMPLE* wptr = (SAMPLE*)outputBuffer;
00273 
00274     (void) inputBuffer; /* Prevent unused variable warnings. */
00275     (void) timeInfo;
00276     (void) statusFlags;
00277     (void) userData;
00278 
00279     data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead);
00280 
00281     return data->threadSyncFlag ? paComplete : paContinue;
00282 }
00283 
00284 static unsigned NextPowerOf2(unsigned val)
00285 {
00286     val--;
00287     val = (val >> 1) | val;
00288     val = (val >> 2) | val;
00289     val = (val >> 4) | val;
00290     val = (val >> 8) | val;
00291     val = (val >> 16) | val;
00292     return ++val;
00293 }
00294 
00295 /*******************************************************************/
00296 int main(void);
00297 int main(void)
00298 {
00299     PaStreamParameters  inputParameters,
00300                         outputParameters;
00301     PaStream*           stream;
00302     PaError             err = paNoError;
00303     paTestData          data = {0};
00304     unsigned            delayCntr;
00305     unsigned            numSamples;
00306     unsigned            numBytes;
00307 
00308     printf("patest_record.c\n"); fflush(stdout);
00309 
00310     /* We set the ring buffer size to about 500 ms */
00311     numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS));
00312     numBytes = numSamples * sizeof(SAMPLE);
00313     data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes );
00314     if( data.ringBufferData == NULL )
00315     {
00316         printf("Could not allocate ring buffer data.\n");
00317         goto done;
00318     }
00319 
00320     if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0)
00321     {
00322         printf("Failed to initialize ring buffer. Size is not power of 2 ??\n");
00323         goto done;
00324     }
00325 
00326     err = Pa_Initialize();
00327     if( err != paNoError ) goto done;
00328 
00329     inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */
00330     if (inputParameters.device == paNoDevice) {
00331         fprintf(stderr,"Error: No default input device.\n");
00332         goto done;
00333     }
00334     inputParameters.channelCount = 2;                    /* stereo input */
00335     inputParameters.sampleFormat = PA_SAMPLE_TYPE;
00336     inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;
00337     inputParameters.hostApiSpecificStreamInfo = NULL;
00338 
00339     /* Record some audio. -------------------------------------------- */
00340     err = Pa_OpenStream(
00341               &stream,
00342               &inputParameters,
00343               NULL,                  /* &outputParameters, */
00344               SAMPLE_RATE,
00345               FRAMES_PER_BUFFER,
00346               paClipOff,      /* we won't output out of range samples so don't bother clipping them */
00347               recordCallback,
00348               &data );
00349     if( err != paNoError ) goto done;
00350 
00351     /* Open the raw audio 'cache' file... */
00352     data.file = fopen(FILE_NAME, "wb");
00353     if (data.file == 0) goto done;
00354 
00355     /* Start the file writing thread */
00356     err = startThread(&data, threadFunctionWriteToRawFile);
00357     if( err != paNoError ) goto done;
00358 
00359     err = Pa_StartStream( stream );
00360     if( err != paNoError ) goto done;
00361     printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout);
00362 
00363     /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can
00364        increase NUM_SECONDS until you run out of disk */
00365     delayCntr = 0;
00366     while( delayCntr++ < NUM_SECONDS )
00367     {
00368         printf("index = %d\n", data.frameIndex ); fflush(stdout);
00369         Pa_Sleep(1000);
00370     }
00371     if( err < 0 ) goto done;
00372 
00373     err = Pa_CloseStream( stream );
00374     if( err != paNoError ) goto done;
00375 
00376     /* Stop the thread */
00377     err = stopThread(&data);
00378     if( err != paNoError ) goto done;
00379 
00380     /* Close file */
00381     fclose(data.file);
00382     data.file = 0;
00383 
00384     /* Playback recorded data.  -------------------------------------------- */
00385     data.frameIndex = 0;
00386 
00387     outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */
00388     if (outputParameters.device == paNoDevice) {
00389         fprintf(stderr,"Error: No default output device.\n");
00390         goto done;
00391     }
00392     outputParameters.channelCount = 2;                     /* stereo output */
00393     outputParameters.sampleFormat =  PA_SAMPLE_TYPE;
00394     outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;
00395     outputParameters.hostApiSpecificStreamInfo = NULL;
00396 
00397     printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout);
00398     err = Pa_OpenStream(
00399               &stream,
00400               NULL, /* no input */
00401               &outputParameters,
00402               SAMPLE_RATE,
00403               FRAMES_PER_BUFFER,
00404               paClipOff,      /* we won't output out of range samples so don't bother clipping them */
00405               playCallback,
00406               &data );
00407     if( err != paNoError ) goto done;
00408 
00409     if( stream )
00410     {
00411         /* Open file again for reading */
00412         data.file = fopen(FILE_NAME, "rb");
00413         if (data.file != 0)
00414         {
00415             /* Start the file reading thread */
00416             err = startThread(&data, threadFunctionReadFromRawFile);
00417             if( err != paNoError ) goto done;
00418 
00419             err = Pa_StartStream( stream );
00420             if( err != paNoError ) goto done;
00421 
00422             printf("Waiting for playback to finish.\n"); fflush(stdout);
00423 
00424             /* The playback will end when EOF is reached */
00425             while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {
00426                 printf("index = %d\n", data.frameIndex ); fflush(stdout);
00427                 Pa_Sleep(1000);
00428             }
00429             if( err < 0 ) goto done;
00430         }
00431         
00432         err = Pa_CloseStream( stream );
00433         if( err != paNoError ) goto done;
00434 
00435         fclose(data.file);
00436         
00437         printf("Done.\n"); fflush(stdout);
00438     }
00439 
00440 done:
00441     Pa_Terminate();
00442     if( data.ringBufferData )       /* Sure it is NULL or valid. */
00443         PaUtil_FreeMemory( data.ringBufferData );
00444     if( err != paNoError )
00445     {
00446         fprintf( stderr, "An error occured while using the portaudio stream\n" );
00447         fprintf( stderr, "Error number: %d\n", err );
00448         fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
00449         err = 1;          /* Always return 0 or 1, but no other return codes. */
00450     }
00451     return err;
00452 }
00453 

Generated on Sat Aug 6 19:33:25 2016 for PortAudio by  doxygen 1.5.6