0

I'm trying to learn to use pulseaudio's Simple API for C. I started to get somewhere, when I realized that the values I was receiving didn't make sense. At first they swapped between ~64-66 and 0. i.e. "65 0 64 0 65 0 66 0 64 0" like that. Then, after a little while (maybe I did something to prompt this change?) I started getting values either very very low or very very high, no in between. Even stranger, the values are in series of 4. i.e. n low numbers followed by m high numbers, where n and m are multiples of 4.

There are occasionally numbers not so extreme, but it is very rare.

Here is the code:

#include <pulse/simple.h>
#include <pulse/sample.h>
#include <pulse/error.h>
#include <stdio.h>

int main() {
    pa_simple *s;
    pa_sample_spec ss;

    ss.format = PA_SAMPLE_S16NE;
    ss.channels = 2;
    ss.rate = 44100;

    int error;

    s = pa_simple_new(
        NULL,
        "PulseAudioTest",
        PA_STREAM_RECORD,
        NULL,
        "My test",
        &ss,
        NULL,
        NULL,
        &error
    );

    if(!s)
        printf("error: %s", pa_strerror(error));

    uint8_t buf[1024];

    while(1) {
        pa_simple_read(s, buf, sizeof(buf), NULL);

        for(int i = 0; i < sizeof(buf); i++)
            printf("%d,", buf[i]);
        printf("\n-----------\n"); // just here for me separating the data, im not playing this back
    }

    if(s)
        pa_simple_free(s);
    else
        printf("error\n");

    return 0;
}

and some example output: https://pastebin.com/FgFekmf0.

I have also tried parec.c and pacat.c from http://0pointer.de/lennart/projects/pulseaudio/doxygen/parec-simple_8c-example.html, but when I play back the recording, its just static. I have also tried switching my default sink and playing music through both the default and non-default sink.

Also, if anyone has any advice for where to learn to use this (and how audio works in general), I'd love recommendations (even if just a term for me to google - it took me a while to learn that the output was "pcm" and what that even was).

1 Answers1

2

Why does the code print values separated by zeroes, i.e. "65 0 64 0 65 0 66 0 64 0 ..."?

Normally the default recording audio specification is the one written in your code:

/* Each sample is Signed 16-bit integer recorded in little-endian (the Native
 * Endian in intel x86 and x86-64).
 */
ss.format = PA_SAMPLE_S16NE;
/* There are 2 channels. */
ss.channels = 2;
/* Sample rate is 44100 Hz. */
ss.rate = 44100;
/* This is how audio samples are arranged. Given a stream of samples
 *
 * ADDR      0x0    0x2    0x4    0x6    0x8    0xA
 * SAMPLE    12345, 13579,  -185,    -3, -9876, -9999, ...
 *          |-----||-----||-----||-----||-----||-----|
 *            CH1    CH2    CH1    CH2    CH1    CH2
 *          |------------||------------||------------|
 *              FRAME         FRAME         FRAME
 */

Given a sample with value of 65, its hex is 0x0041, with little-endian, it will be stored as 41 00 in memory. Notice that your code print buf byte by byte, that's why you got 65 0. So with sample stream 65, 64, 65, ... you will print 65 0 64 0 65 0 ....

Tried parec and pacat, but got static noise only. How to make it work?

Make sure the audio input (microphone) is not muted and has suitable volume. Run pavucontrol, go to the "Input Devices" tab, unmute the microphone, and adjust the volume. Make some noise and you should see the signal bar (under the volume bar) jumping.

PulseAudio comes with several recording and playing utilities (package libpulse in arch, package pulseaudio-utils in debian): parec, parecord, pacat, paplay. Try them. They should work.

Play sound recorded by parec with pacat.

$ parec sample1.wav
^C
$ pacat sample1.wav

Play sound recorded by parecord with paplay.

$ parecord sample2.wav
^C
$ paplay sample2.wav

The main difference between them is that parec and pacat do not write and read header in the sound file, while parecord and paplay do.

After successfully running those commands, you can try to compile pacat-simple.c and parec-simple.c from the official repository, under src/tests/.

$ gcc pacat-simple.c -o pacat-simple -lpulse-simple -lpulse
$ gcc parec-simple.c -o parec-simple -lpulse-simple -lpulse

After that, try to run them.

$ ./parec-simple > sample3.wav
^C
$ ./pacat-simple < sample3.wav

It should be working. Now you can start to examine and play with parec-simple.c and pacat-simple.c. Note that parec-simple and pacat-simple do not write and read header in sound file too, just like parec and pacat.

With some modifications on the while-loop, your code will work like parec-simple. Modified version (file my-parec.c):

while(1) {
    pa_simple_read(s, buf, sizeof(buf), NULL);

    //for(int i = 0; i < sizeof(buf); i++)
    //    printf("%d,", buf[i]);
    //printf("\n-----------\n"); // just here for me separating the data, im not playing this back

    fwrite(buf, 1, sizeof(buf), stdout);
}

Compile and run it:

$ gcc my-parec.c -o my-parec -lpulse-simple -lpulse
$ my-parec > sample4.wav
^C

Are there any examples of using PulseAudio C API?

Besides parec-simple.c and pacat-simple.c, if you've read through the PulseAudio asynchronous API documentation, you can checkout pacat.c under src/utils/ as an example. Actually parec, parecord, and paplay are just symbolic links of pacat, which source code is pacat.c.

What is the header in a sound file? It was mentioned above.

If a sound file contains only data, then a player cannot know the audio specification of that file, like number of channels, number of bits (bit depth) per sample, endianness, etc. In that case it can only assume the default specification is used, which may not be the case. So, in order to save those properties into the sound file, we've created a header section in that file to store them. Refer to WAV PCM soundfile format and audio file format specifications for the header format.

To manage the header section of a sound file, the C library libsndfile can be used. pacat.c did use the library to read and write sound files with a header. libsndfile also provides some utilities such as sndfile-info that help us to inspect sound files. Those utilities are included in package libsndfile in arch, and package sndfile-programs in debian.

$ sndfile-info sample2.wav
========================================
File : sample2.wav
Length : 698668
RIFF : 698660
WAVE
fmt  : 16
  Format        : 0x1 => WAVE_FORMAT_PCM
  Channels      : 2
  Sample Rate   : 44100
  Block Align   : 4
  Bit Width     : 16
  Bytes/sec     : 176400
data : 698624
End

----------------------------------------
Sample Rate : 44100
Frames      : 174656
Channels    : 2
Format      : 0x00010002
Sections    : 1
Seekable    : TRUE
Duration    : 00:00:03.960
Signal Max  : 17226 (-5.59 dB)

To add a header into a sound file without header, the easiest way that I can think of is, copy the header from another sound file that has a header, prepend it to the sound file without header, and change some values using a hex editor (I use ghex). Beware of the endianness while editing numeric fields in the header.

# In my system, sample2.wav has the simplest header which is 44 bytes long.
head -c 44 sample2.wav > header.wav
# Prepend header to sound file without header.
cat header.wav sample3.wav > sample3-with-header.wav
# Edit the chunk size field and data chunk size field.
ghex sample3-with-header.wav

Other references

PulseAudio under the hood explained PulseAudio server quite well. Its topics on Buffering and Latency helps in understanding the API usage like pa_stream_begin_write() and pa_stream_write().

dianhenglau
  • 484
  • 5
  • 9