Thread: How can I record continuously using Portaudio?

  1. #1
    Registered User
    Join Date
    Aug 2021
    Posts
    5

    Question How can I record continuously using Portaudio?

    Hello,

    I would like to build a small VU meter in C for monitoring my microphone.

    I don't know anything about sound libraries that can be used to record audio, so I've turned my attention to Portaudio, only because I've heard about it.

    I rewrote one of their examples to record audio (paex_record.c), in order to understand all of the steps, but also to do it in my programming style.

    The example works, but it only fills the predefined buffer, then exits.

    I've tried many things to try to restart the recording buffer, whenever by putting some part of the code into a loop, or using gotos; but nothing have worked, and despite reading the documentation, I'm starting to run around in circle.

    My two questions are:
    1. Is Portaudio a good library for this? Are there better choices in terms of simplicity for newcomers?
    2. How can I modify my code to record continuously?


    Here is my code, that you can compile under Linux, using:
    Code:
    $ gcc file.c -lm -lportaudio
    Code:
    #include <math.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <stdlib.h>
    
    #include <portaudio.h>
    
    #define SAMPLE_RATE 44100
    #define FRAMES_PER_BUFFER 256
    #define NUM_SECONDS 5
    #define NUM_CHANNELS 2
    #define DITHER_FLAG 0
    #define WRITE_TO_FILE 0
    #define SAMPLES (SAMPLE_RATE * NUM_CHANNELS)
    
    typedef struct paTestData paTestData;
    struct paTestData
    {
      int32_t frame_index;
      int32_t frame_index_max;
      float recorded_samples[SAMPLES];
    };
    
    static int32_t
    record_callback(const void* input, void* output, size_t frames_count, const PaStreamCallbackTimeInfo* time_info, 
        PaStreamCallbackFlags status_flags, void* user_data)
    {
      (void)output;
      (void)time_info;
      (void)status_flags;
    
      paTestData* data = (paTestData*)user_data;
      const float* rptr = (const float*)input;
      float* wptr = &data->recorded_samples[data->frame_index * NUM_CHANNELS];
    
      long frames_to_calc = 0;
      int32_t finished = 0;
      size_t frames_left = data->frame_index_max - data->frame_index;
    
      if (frames_left < frames_count)
        {
          frames_to_calc = frames_left;
          finished = paComplete;
        }
      else
        {
          frames_to_calc = frames_count;
          finished = paContinue;
        }
    
      if (!input)
        {
          for (size_t i = 0; i < frames_to_calc; ++i)
            {
              *wptr++ = 0.0f;
              if (NUM_CHANNELS == 2)
                {
                  *wptr++ = 0.0f;
                }
            }
        }
      else
        {
          for (size_t i = 0; i < frames_to_calc; ++i)
            {
              *wptr++ = *rptr++;
              if (NUM_CHANNELS == 2)
                {
                  *wptr++ = *rptr++;
                }
            }
        }
      data->frame_index += frames_to_calc;
      return finished;
    }
    
    static void
    fatal_error(PaError err)
    {
      Pa_Terminate();
      exit(err);
    }
    
    static inline float
    amplitude_to_db(float amplitude)
    {
      return 20.0f * log10(amplitude);
    }
    
    int32_t
    main(int32_t argc, char* argv[])
    {
      (void)argc;
      (void)argv;
    
      fputs("PortAudio record test\n", stdout);
    
      paTestData data = {
        .frame_index_max = SAMPLE_RATE};
    
      PaError err = Pa_Initialize();
      if (err != paNoError)
        {
          fatal_error(err);
        }
    
      PaStreamParameters input_parameters = {
        .device = Pa_GetDefaultInputDevice(),
        .channelCount = NUM_CHANNELS,
        .sampleFormat = paFloat32,
      };
      if (input_parameters.device == paNoDevice)
        {
          fatal_error(0);
        }
      else
        {
          input_parameters.suggestedLatency = Pa_GetDeviceInfo(input_parameters.device)->defaultLowInputLatency;
        }
    
      // Record audio
    
      PaStream* stream = 0;
    
      err = Pa_OpenStream(&stream, &input_parameters, 0, SAMPLE_RATE, FRAMES_PER_BUFFER, 
        paClipOff, record_callback, &data);
      if (err != paNoError)
        {
          fatal_error(err);
        }
    
      err = Pa_StartStream(stream);
      if (err != paNoError)
        {
          fatal_error(err);
        }
    
      fputs("Now recording.\n", stdout);
    
      while ((err = Pa_IsStreamActive(stream)) == 1)
        {
          Pa_Sleep(16);
          printf("index: %d %f\n", data.frame_index, amplitude_to_db(data.recorded_samples[data.frame_index]));
        }
      if (err != paNoError)
        {
          fatal_error(err);
        }
    
      err = Pa_CloseStream(stream);
      if (err != paNoError)
        {
          fatal_error(err);
        }
    
      // Measure maximum peak amplitude
    
      float max = 0.0f;
      double average = 0.0;
      for (size_t i = 0; i < SAMPLES; ++i)
        {
          float val = data.recorded_samples[i];
          if (val < 0.0f)
            {
              val = -val; // abs()
            }
          if (val > max)
            {
              max = val;
            }
          average += val;
        }
    
      average /= (double)SAMPLES;
    
      printf("Sample maximum amplitude: %f\n", max);
      printf("Sample average amplitude: %lf\n", average);
      printf("Sample maximum dB: %f\n", amplitude_to_db(max));
      printf("Sample average dB: %lf\n", amplitude_to_db(average));
    
      // Done
    
      Pa_Terminate();
      return EXIT_SUCCESS;
    }
    Thank you very much for your time.

    Best regards.
    Last edited by Salem; 06-05-2022 at 10:13 PM. Reason: Line wrap the code

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,666
    > I rewrote one of their examples to record audio (paex_record.c), in order to understand all of the steps
    Did you start with the unmodified code to verify it did in fact record as described?
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Aug 2021
    Posts
    5
    Hello Salem,

    Thank you for your answer.

    I have indeed checked with running the unmodified code to verify. My software register sound the same way, and outputs the same levels. I've only deactivated playing back the sound at the end as I'm not interested with that for my project.

  4. #4
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,666
    So is it working then?

    If you want it to keep going, you shouldn't be doing this.
    finished = paComplete;
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  5. #5
    Registered User
    Join Date
    Aug 2021
    Posts
    5

    Thumbs up [FIXED] How can I record continuously using Portaudio?

    Quote Originally Posted by Salem View Post
    So is it working then?

    If you want it to keep going, you shouldn't be doing this.
    finished = paComplete;
    I think that I may have expressed myself badly. The software is working, but it stops recording when it reaches the end of the buffer.

    The fix you have suggested me wasn't the correct one, but lead me to find the solution:

    Code:
    @@ -34,20 +34,22 @@ record_callback(const void* input, void* output, size
    _t frames_count, const PaSt
       float* wptr = &data->recorded_samples[data->frame_index * NUM_CHANNELS
    ];
    
       long frames_to_calc = 0;
    -  int32_t finished = 0;
       size_t frames_left = data->frame_index_max - data->frame_index;
    
       if (frames_left < frames_count)
         {
           frames_to_calc = frames_left;
    -      finished = paComplete;
         }
       else
         {
           frames_to_calc = frames_count;
    -      finished = paContinue;
         }
    
    +  if (!frames_left)
    +  {
    +        data->frame_index = 0;
    +  }
    +
       if (!input)
         {
           for (size_t i = 0; i < frames_to_calc; ++i)
    @@ -71,10 +73,10 @@ record_callback(const void* input, void* output, size_t frames_count, const PaSt
             }
         }
       data->frame_index += frames_to_calc;
    -  return finished;
    +  return paContinue;
     }
    While I had to get rid of the finished variable, I also had to watch for the index of the buffer. When it reaches the end, I set it back to zero, and recording can therefore continue.

    This isn't the complete fix to my problem, but this is a very big step forward.

    Thank you very much for helping me, and for your time.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Creating and passing a void through for userdata with portaudio
    By stuartiannaylor in forum C++ Programming
    Replies: 1
    Last Post: 04-18-2022, 02:12 AM
  2. Linker Issue - Getting PortAudio to work with Code::Blocks
    By samGwilliam in forum C++ Programming
    Replies: 9
    Last Post: 06-16-2019, 05:17 PM
  3. Replies: 3
    Last Post: 07-11-2011, 10:40 AM
  4. Update Record & Delete Record in File.
    By unsafe_pilot1 in forum C Programming
    Replies: 13
    Last Post: 05-18-2008, 07:22 AM
  5. continuously updating timer on console display
    By revelation437 in forum C++ Programming
    Replies: 5
    Last Post: 02-24-2003, 12:28 PM

Tags for this Thread