]> Repos - portaudio/commitdiff
Added a simple example that records to/plays from a file (using callback + ring buffe...
authorrobiwan <robiwan@0f58301d-fd10-0410-b4af-bbb618454e57>
Mon, 9 Jul 2012 09:53:23 +0000 (09:53 +0000)
committerrobiwan <robiwan@0f58301d-fd10-0410-b4af-bbb618454e57>
Mon, 9 Jul 2012 09:53:23 +0000 (09:53 +0000)
examples/CMakeLists.txt
examples/paex_record_file.c [new file with mode: 0644]

index 00d7e49afb8f3ddeb59a7aacae27711a043ab815..67a2cfdb190f5863ecd00c209ad965ee0d6bc161 100644 (file)
@@ -17,6 +17,7 @@ ADD_EXAMPLE(paex_ocean_shore)
 ADD_EXAMPLE(paex_pink)
 ADD_EXAMPLE(paex_read_write_wire)
 ADD_EXAMPLE(paex_record)
+ADD_EXAMPLE(paex_record_file)
 ADD_EXAMPLE(paex_saw)
 ADD_EXAMPLE(paex_sine)
 ADD_EXAMPLE_CPP(paex_sine_c++)
diff --git a/examples/paex_record_file.c b/examples/paex_record_file.c
new file mode 100644 (file)
index 0000000..ec7eb3d
--- /dev/null
@@ -0,0 +1,452 @@
+/** @file paex_record_file.c\r
+       @ingroup examples_src\r
+       @brief Record input into a file, then playback recorded data from file (Windows only at the moment) \r
+       @author Robert Bielik\r
+*/\r
+/*\r
+ * $Id: paex_record_file.c 1752 2011-09-08 03:21:55Z philburk $\r
+ *\r
+ * This program uses the PortAudio Portable Audio Library.\r
+ * For more information see: http://www.portaudio.com\r
+ * Copyright (c) 1999-2000 Ross Bencina and Phil Burk\r
+ *\r
+ * Permission is hereby granted, free of charge, to any person obtaining\r
+ * a copy of this software and associated documentation files\r
+ * (the "Software"), to deal in the Software without restriction,\r
+ * including without limitation the rights to use, copy, modify, merge,\r
+ * publish, distribute, sublicense, and/or sell copies of the Software,\r
+ * and to permit persons to whom the Software is furnished to do so,\r
+ * subject to the following conditions:\r
+ *\r
+ * The above copyright notice and this permission notice shall be\r
+ * included in all copies or substantial portions of the Software.\r
+ *\r
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,\r
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\r
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR\r
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF\r
+ * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\r
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\r
+ */\r
+\r
+/*\r
+ * The text above constitutes the entire PortAudio license; however, \r
+ * the PortAudio community also makes the following non-binding requests:\r
+ *\r
+ * Any person wishing to distribute modifications to the Software is\r
+ * requested to send the modifications to the original developer so that\r
+ * they can be incorporated into the canonical version. It is also \r
+ * requested that these non-binding requests be included along with the \r
+ * license above.\r
+ */\r
+\r
+#include <stdio.h>\r
+#include <stdlib.h>\r
+#include "portaudio.h"\r
+#include "pa_ringbuffer.h"\r
+#include "pa_util.h"\r
+\r
+#ifdef _WIN32\r
+#include <windows.h>\r
+#include <process.h>\r
+#endif\r
+\r
+/* #define SAMPLE_RATE  (17932) // Test failure to open with this value. */\r
+#define FILE_NAME       "audio_data.raw"\r
+#define SAMPLE_RATE  (44100)\r
+#define FRAMES_PER_BUFFER (512)\r
+#define NUM_SECONDS     (10)\r
+#define NUM_CHANNELS    (2)\r
+#define NUM_WRITES_PER_BUFFER   (4)\r
+/* #define DITHER_FLAG     (paDitherOff) */\r
+#define DITHER_FLAG     (0) /**/\r
+\r
+\r
+/* Select sample format. */\r
+#if 1\r
+#define PA_SAMPLE_TYPE  paFloat32\r
+typedef float SAMPLE;\r
+#define SAMPLE_SILENCE  (0.0f)\r
+#define PRINTF_S_FORMAT "%.8f"\r
+#elif 1\r
+#define PA_SAMPLE_TYPE  paInt16\r
+typedef short SAMPLE;\r
+#define SAMPLE_SILENCE  (0)\r
+#define PRINTF_S_FORMAT "%d"\r
+#elif 0\r
+#define PA_SAMPLE_TYPE  paInt8\r
+typedef char SAMPLE;\r
+#define SAMPLE_SILENCE  (0)\r
+#define PRINTF_S_FORMAT "%d"\r
+#else\r
+#define PA_SAMPLE_TYPE  paUInt8\r
+typedef unsigned char SAMPLE;\r
+#define SAMPLE_SILENCE  (128)\r
+#define PRINTF_S_FORMAT "%d"\r
+#endif\r
+\r
+typedef struct\r
+{\r
+    unsigned            frameIndex;\r
+    int                 threadSyncFlag;\r
+    SAMPLE             *ringBufferData;\r
+    PaUtilRingBuffer    ringBuffer;\r
+    FILE               *file;\r
+    void               *threadHandle;\r
+}\r
+paTestData;\r
+\r
+/* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */\r
+static int threadFunctionWriteToRawFile(void* ptr)\r
+{\r
+    paTestData* pData = (paTestData*)ptr;\r
+\r
+    /* Mark thread started */\r
+    pData->threadSyncFlag = 0;\r
+\r
+    while (1)\r
+    {\r
+        ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer);\r
+        if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) ||\r
+             pData->threadSyncFlag )\r
+        {\r
+            void* ptr[2] = {0};\r
+            ring_buffer_size_t sizes[2] = {0};\r
+\r
+            /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */\r
+            ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);\r
+            if (elementsRead > 0)\r
+            {\r
+                int i;\r
+                for (i = 0; i < 2 && ptr[i] != NULL; ++i)\r
+                {\r
+                    fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);\r
+                }\r
+                PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead);\r
+            }\r
+\r
+            if (pData->threadSyncFlag)\r
+            {\r
+                break;\r
+            }\r
+        }\r
+\r
+        /* Sleep a little while... */\r
+        Pa_Sleep(20);\r
+    }\r
+\r
+    pData->threadSyncFlag = 0;\r
+\r
+    return 0;\r
+}\r
+\r
+/* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file\r
+   has reached EOF, a flag is set so that the play PA callback can return paComplete */\r
+static int threadFunctionReadFromRawFile(void* ptr)\r
+{\r
+    paTestData* pData = (paTestData*)ptr;\r
+\r
+    /* Mark thread started */\r
+    pData->threadSyncFlag = 0;\r
+\r
+    while (1)\r
+    {\r
+        ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer);\r
+\r
+        if (elementsInBuffer >= pData->ringBuffer.bufferSize / 4)\r
+        {\r
+            void* ptr[2] = {0};\r
+            ring_buffer_size_t sizes[2] = {0};\r
+\r
+            /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */\r
+            PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1);\r
+\r
+            if (!feof(pData->file))\r
+            {\r
+                ring_buffer_size_t itemsReadFromFile = 0;\r
+                int i;\r
+                for (i = 0; i < 2 && ptr[i] != NULL; ++i)\r
+                {\r
+                    itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file);\r
+                }\r
+                PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile);\r
+            }\r
+            else\r
+            {\r
+                /* No more data to read */\r
+                pData->threadSyncFlag = 1;\r
+                break;\r
+            }\r
+        }\r
+\r
+        /* Sleep a little while... */\r
+        Pa_Sleep(20);\r
+    }\r
+\r
+    return 0;\r
+}\r
+\r
+typedef int (*ThreadFunctionType)(void*);\r
+\r
+/* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend\r
+   to posix type OSs (Linux/Mac) */\r
+static PaError startThread( paTestData* pData, ThreadFunctionType fn )\r
+{\r
+#ifdef _WIN32\r
+    pData->threadSyncFlag = 1;\r
+    pData->threadHandle = (void*)_beginthreadex(NULL, 0, fn, pData, 0, NULL);\r
+    if (pData->threadHandle == NULL) return paUnanticipatedHostError;\r
+    /* Wait for thread to startup */\r
+    while (pData->threadSyncFlag) {\r
+        Pa_Sleep(10);\r
+    }\r
+    /* Set file thread to a little higher prio than normal */\r
+    SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL);\r
+#endif\r
+    return paNoError;\r
+}\r
+\r
+static int stopThread( paTestData* pData )\r
+{\r
+    pData->threadSyncFlag = 1;\r
+    /* Wait for thread to stop */\r
+    while (pData->threadSyncFlag) {\r
+        Pa_Sleep(10);\r
+    }\r
+#ifdef _WIN32\r
+    CloseHandle(pData->threadHandle);\r
+    pData->threadHandle = 0;\r
+#endif\r
+\r
+    return paNoError;\r
+}\r
+\r
+\r
+/* This routine will be called by the PortAudio engine when audio is needed.\r
+** It may be called at interrupt level on some machines so don't do anything\r
+** that could mess up the system like calling malloc() or free().\r
+*/\r
+static int recordCallback( const void *inputBuffer, void *outputBuffer,\r
+                           unsigned long framesPerBuffer,\r
+                           const PaStreamCallbackTimeInfo* timeInfo,\r
+                           PaStreamCallbackFlags statusFlags,\r
+                           void *userData )\r
+{\r
+    paTestData *data = (paTestData*)userData;\r
+    const SAMPLE *rptr = (const SAMPLE*)inputBuffer;\r
+\r
+    (void) outputBuffer; /* Prevent unused variable warnings. */\r
+    (void) timeInfo;\r
+    (void) statusFlags;\r
+    (void) userData;\r
+\r
+    data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, framesPerBuffer * NUM_CHANNELS);\r
+\r
+    return paContinue;\r
+}\r
+\r
+/* This routine will be called by the PortAudio engine when audio is needed.\r
+** It may be called at interrupt level on some machines so don't do anything\r
+** that could mess up the system like calling malloc() or free().\r
+*/\r
+static int playCallback( const void *inputBuffer, void *outputBuffer,\r
+                         unsigned long framesPerBuffer,\r
+                         const PaStreamCallbackTimeInfo* timeInfo,\r
+                         PaStreamCallbackFlags statusFlags,\r
+                         void *userData )\r
+{\r
+    paTestData *data = (paTestData*)userData;\r
+    ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer);\r
+    ring_buffer_size_t elementsToRead = min(elementsToPlay, (ring_buffer_size_t)framesPerBuffer);\r
+    SAMPLE* wptr = (SAMPLE*)outputBuffer;\r
+\r
+    (void) inputBuffer; /* Prevent unused variable warnings. */\r
+    (void) timeInfo;\r
+    (void) statusFlags;\r
+    (void) userData;\r
+\r
+    /* If we have less data in the ring buffer than framesPerBuffer, we must clear the first N frames where\r
+       N = (framesPerBuffer - elementsToRead) */\r
+      \r
+    if ((ring_buffer_size_t)framesPerBuffer > elementsToRead)\r
+    {\r
+        int i;\r
+        const int samplesToClear = (framesPerBuffer - elementsToRead) * NUM_CHANNELS;\r
+        for (i = 0; i < samplesToClear; ++i)\r
+        {\r
+            *wptr++ = 0;\r
+        }\r
+    }\r
+\r
+    data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead * NUM_CHANNELS);\r
+\r
+    return data->threadSyncFlag ? paComplete : paContinue;\r
+}\r
+\r
+static unsigned NextPowerOf2(unsigned val)\r
+{\r
+    val--;\r
+    val = (val >> 1) | val;\r
+    val = (val >> 2) | val;\r
+    val = (val >> 4) | val;\r
+    val = (val >> 8) | val;\r
+    val = (val >> 16) | val;\r
+    return ++val;\r
+}\r
+\r
+/*******************************************************************/\r
+int main(void);\r
+int main(void)\r
+{\r
+    PaStreamParameters  inputParameters,\r
+                        outputParameters;\r
+    PaStream*           stream;\r
+    PaError             err = paNoError;\r
+    paTestData          data = {0};\r
+    unsigned            delayCntr;\r
+    unsigned            numSamples;\r
+    unsigned            numBytes;\r
+\r
+    printf("patest_record.c\n"); fflush(stdout);\r
+\r
+    /* We set the ring buffer size to about 500 ms */\r
+    numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS));\r
+    numBytes = numSamples * sizeof(SAMPLE);\r
+    data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes ); /* From now on, recordedSamples is initialised. */\r
+    if( data.ringBufferData == NULL )\r
+    {\r
+        printf("Could not allocate record array.\n");\r
+        goto done;\r
+    }\r
+\r
+    if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0)\r
+    {\r
+        goto done;\r
+    }\r
+\r
+    err = Pa_Initialize();\r
+    if( err != paNoError ) goto done;\r
+\r
+    inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */\r
+    if (inputParameters.device == paNoDevice) {\r
+        fprintf(stderr,"Error: No default input device.\n");\r
+        goto done;\r
+    }\r
+    inputParameters.channelCount = 2;                    /* stereo input */\r
+    inputParameters.sampleFormat = PA_SAMPLE_TYPE;\r
+    inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency;\r
+    inputParameters.hostApiSpecificStreamInfo = NULL;\r
+\r
+    /* Record some audio. -------------------------------------------- */\r
+    err = Pa_OpenStream(\r
+              &stream,\r
+              &inputParameters,\r
+              NULL,                  /* &outputParameters, */\r
+              SAMPLE_RATE,\r
+              FRAMES_PER_BUFFER,\r
+              paClipOff,      /* we won't output out of range samples so don't bother clipping them */\r
+              recordCallback,\r
+              &data );\r
+    if( err != paNoError ) goto done;\r
+\r
+    /* Open the raw audio 'cache' file... */\r
+    data.file = fopen(FILE_NAME, "wb");\r
+    if (data.file == 0) goto done;\r
+\r
+    /* Start the file writing thread */\r
+    err = startThread(&data, threadFunctionWriteToRawFile);\r
+    if( err != paNoError ) goto done;\r
+\r
+    err = Pa_StartStream( stream );\r
+    if( err != paNoError ) goto done;\r
+    printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout);\r
+\r
+    /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can\r
+       increase NUM_SECONDS until you run out of disk */\r
+    delayCntr = 0;\r
+    while( delayCntr++ < NUM_SECONDS )\r
+    {\r
+        printf("index = %d\n", data.frameIndex ); fflush(stdout);\r
+        Pa_Sleep(1000);\r
+    }\r
+    if( err < 0 ) goto done;\r
+\r
+    err = Pa_CloseStream( stream );\r
+    if( err != paNoError ) goto done;\r
+\r
+    /* Stop the thread */\r
+    err = stopThread(&data);\r
+    if( err != paNoError ) goto done;\r
+\r
+    /* Close file */\r
+    fclose(data.file);\r
+    data.file = 0;\r
+\r
+    /* Playback recorded data.  -------------------------------------------- */\r
+    data.frameIndex = 0;\r
+\r
+    outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */\r
+    if (outputParameters.device == paNoDevice) {\r
+        fprintf(stderr,"Error: No default output device.\n");\r
+        goto done;\r
+    }\r
+    outputParameters.channelCount = 2;                     /* stereo output */\r
+    outputParameters.sampleFormat =  PA_SAMPLE_TYPE;\r
+    outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency;\r
+    outputParameters.hostApiSpecificStreamInfo = NULL;\r
+\r
+    printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout);\r
+    err = Pa_OpenStream(\r
+              &stream,\r
+              NULL, /* no input */\r
+              &outputParameters,\r
+              SAMPLE_RATE,\r
+              FRAMES_PER_BUFFER,\r
+              paClipOff,      /* we won't output out of range samples so don't bother clipping them */\r
+              playCallback,\r
+              &data );\r
+    if( err != paNoError ) goto done;\r
+\r
+    if( stream )\r
+    {\r
+        /* Open file again for reading */\r
+        data.file = fopen(FILE_NAME, "rb");\r
+        if (data.file == 0) goto done;\r
+\r
+        /* Start the file reading thread */\r
+        err = startThread(&data, threadFunctionReadFromRawFile);\r
+        if( err != paNoError ) goto done;\r
+\r
+        err = Pa_StartStream( stream );\r
+        if( err != paNoError ) goto done;\r
+        \r
+        printf("Waiting for playback to finish.\n"); fflush(stdout);\r
+\r
+        /* The playback will end when EOF is reached */\r
+        while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) {\r
+            printf("index = %d\n", data.frameIndex ); fflush(stdout);\r
+            Pa_Sleep(1000);\r
+        }\r
+        if( err < 0 ) goto done;\r
+        \r
+        err = Pa_CloseStream( stream );\r
+        if( err != paNoError ) goto done;\r
+        \r
+        printf("Done.\n"); fflush(stdout);\r
+    }\r
+\r
+done:\r
+    Pa_Terminate();\r
+    if( data.ringBufferData )       /* Sure it is NULL or valid. */\r
+        PaUtil_FreeMemory( data.ringBufferData );\r
+    if( err != paNoError )\r
+    {\r
+        fprintf( stderr, "An error occured while using the portaudio stream\n" );\r
+        fprintf( stderr, "Error number: %d\n", err );\r
+        fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );\r
+        err = 1;          /* Always return 0 or 1, but no other return codes. */\r
+    }\r
+    return err;\r
+}\r
+\r