2

I want to record my microphone and process in real time, eg. show an oscilloscope view of the microphone signal.

I've tried 3 ways to get the data out of PulseAudio and they all perform the same. I expect a smooth stream of binary but instead there's a pattern roughly like: Hang for ~300ms, print a ton of output, repeat.
3 different ways, 2 different machines, same results.

The 1st way was parec.

The 2nd and 3rd way are hello worlds in C and Haskell, both using the pulse-simple library.

C: https://freedesktop.org/software/pulseaudio/doxygen/parec-simple_8c-example.html#a7

Haskell:

import Sound.Pulse.Simple
import Control.Monad
import System.IO

main = do

  s <- simpleNew Nothing "example" Record Nothing
       "this is an example application"
       (SampleSpec (F32 LittleEndian) 44100 1) Nothing Nothing


  forever $ do

  let numSamples = 4410
  xs <- simpleRead s $ numSamples :: IO [Float]

  putStrLn $ "hello"

  hFlush stdout

Flushing stdout in the loop body does not make a difference.
Adding a delay in the loop changes the performance but not to what I want.

Somehow the pavucontrol VU meter gets it right. What am I missing?

EDIT: I discovered that while pavucontrol is running, I get great results, both in my example programs and with parec. Why??

Also, I looked at the source of pavucontrol and parec , and it turns out they both use the asynchronous API, and my 2 example programs use the simple API. So, the issue is not entirely due to the use of 1 API or the other, since parec behaves like the example programs.

PizzaBeer
  • 183
  • 14

1 Answers1

1

When calling pa_simple_new, set the fragsize field of the pa_buffer_attr argument to the wanted latency in bytes. PulseAudio will try to achieve that latency, but may not reach it depending on the hardware capabilities.

(pavucontrol probably asks for a low latency. PulseAudio tries to reach the lowest latency requested by all currently running programs, so that's why the recording latency is low when pavucontrol is running).

In C:

    pa_buffer_attr attr;
    attr.maxlength = (uint32_t) -1;
    attr.tlength = (uint32_t) -1;
    attr.prebuf = (uint32_t) -1;
    attr.minreq = (uint32_t) -1;
    attr.fragsize = 1600;

    pa = pa_simple_new(NULL,
                       argv[0],
                       PA_STREAM_RECORD,
                       NULL,
                       "record",
                       &ss,
                       NULL,
                       &attr,
                       &error);

See LatencyControl for more information. (Note that the PA_STREAM_ADJUST_LATENCY flag is automatically set when using the simple API).

This should give a smoother stream of samples, but because pa_simple_read is blocking (waits until the entire buffer can be filled), you'll still have added latency, even if you use a small buffer for reading. To get rid of this extra latency, you need to use the asynchronous API for non-blocking reads.

Pulse
  • 11
  • 1