Léolol DB
Léolol DB

Reputation: 323

ALSA in C++ - Making the minimal working code

Topic

I would like to make the minimal working code to generate any kind of PCM sound using ALSA in C++ for a Linux computer.

Setup

I'm coding in C++ on Code::Blocks with Ubuntu 20.04.

Background

I used to make simple Arduino UNO programs doing sound processing and just needed to play raw PCM samples.

Issues

ALSA Project's Website is not very easy to understand.

I looked at c - ALSA tutorial required to find out that many links are expired.

I copy pasted the code of the minimal PCM example in C directly into a empty Code::Blocks project and I got that error:

||=== Build: Release in Test (compiler: GNU GCC Compiler) ===|
Test/main.cpp|5|fatal error: ../include/asoundlib.h: No such file or directory|
||=== Build failed: 1 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

Right in the first line of code which is #include "../include/asoundlib.h". I'm guessing that the issue could be because I have to download something or add a linker for the compiler.
But I also think it may be an issue of C to C++ conversion meaning that this works in C but not in C++.

Do I have to add a linker for the compiler or download something to make the code working?

Then I looked on ALSA library and downloaded alsa-lib-1.2.3.tar.bz2.
I got an archive that looked to have the right things but I don't know how to handle it.

Then I found usr/include/sound/asound.h on my computer. It looks to be part of ALSA but when I changed the code to use it, it spat out a bunch of errors when used.
The code looks like following now:

/*
 *  This extra small demo sends a random samples to your speakers.
 */
#include <sound/asound.h>
#include <cstdio>
static char *device = "default";            /* playback device */
unsigned char buffer[16*1024];              /* some random data */
int main(void)
{
    int err;
    unsigned int i;
    snd_pcm_t *handle;
    snd_pcm_sframes_t frames;
    for (i = 0; i < sizeof(buffer); i++)
        buffer[i] = random() & 0xff;
    if ((err = snd_pcm_open(&handle, device, SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
        printf("Playback open error: %s\n", snd_strerror(err));
        exit(EXIT_FAILURE);
    }
    if ((err = snd_pcm_set_params(handle,
                      SND_PCM_FORMAT_U8,
                      SND_PCM_ACCESS_RW_INTERLEAVED,
                      1,
                      48000,
                      1,
                      500000)) < 0) {   /* 0.5sec */
        printf("Playback open error: %s\n", snd_strerror(err));
        exit(EXIT_FAILURE);
    }
        for (i = 0; i < 16; i++) {
        frames = snd_pcm_writei(handle, buffer, sizeof(buffer));
        if (frames < 0)
            frames = snd_pcm_recover(handle, frames, 0);
        if (frames < 0) {
            printf("snd_pcm_writei failed: %s\n", snd_strerror(frames));
            break;
        }
        if (frames > 0 && frames < (long)sizeof(buffer))
            printf("Short write (expected %li, wrote %li)\n", (long)sizeof(buffer), frames);
    }
    /* pass the remaining samples, otherwise they're dropped in close */
    err = snd_pcm_drain(handle);
    if (err < 0)
        printf("snd_pcm_drain failed: %s\n", snd_strerror(err));
    snd_pcm_close(handle);
    return 0;
}

And the errors are like that:

||=== Build: Release in Test (compiler: GNU GCC Compiler) ===|
Test/main.cpp|6|warning: ISO C++ forbids converting a string constant to ‘char*’ [-Wwrite-strings]|
Test/main.cpp||In function ‘int main()’:|
Test/main.cpp|12|error: ‘snd_pcm_t’ was not declared in this scope; did you mean ‘snd_pcm_info’?|
Test/main.cpp|12|error: ‘handle’ was not declared in this scope|
Test/main.cpp|16|error: ‘SND_PCM_STREAM_PLAYBACK’ was not declared in this scope; did you mean ‘SNDRV_PCM_STREAM_PLAYBACK’?|
Test/main.cpp|16|error: ‘snd_pcm_open’ was not declared in this scope; did you mean ‘snd_pcm_info’?|
Test/main.cpp|17|error: ‘snd_strerror’ was not declared in this scope|
||error: %s\n", snd_strerror(err));|
Test/main.cpp|21|error: ‘SND_PCM_FORMAT_U8’ was not declared in this scope; did you mean ‘SNDRV_PCM_FORMAT_U8’?|
Test/main.cpp|22|error: ‘SND_PCM_ACCESS_RW_INTERLEAVED’ was not declared in this scope; did you mean ‘SNDRV_PCM_ACCESS_RW_INTERLEAVED’?|
Test/main.cpp|20|error: ‘snd_pcm_set_params’ was not declared in this scope; did you mean ‘snd_pcm_sw_params’?|
Test/main.cpp|27|error: ‘snd_strerror’ was not declared in this scope|
||error: %s\n", snd_strerror(err));|
Test/main.cpp|31|error: ‘snd_pcm_writei’ was not declared in this scope|
Test/main.cpp|33|error: ‘snd_pcm_recover’ was not declared in this scope|
Test/main.cpp|35|error: ‘snd_strerror’ was not declared in this scope|
Test/main.cpp|42|error: ‘snd_pcm_drain’ was not declared in this scope|
Test/main.cpp|44|error: ‘snd_strerror’ was not declared in this scope|
Test/main.cpp|45|error: ‘snd_pcm_close’ was not declared in this scope|
||=== Build failed: 17 error(s), 1 warning(s) (0 minute(s), 0 second(s)) ===|

Upvotes: 0

Views: 6847

Answers (2)

bzt
bzt

Reputation: 1

Then I found usr/include/sound/asound.h on my computer. It looks to be part of ALSA

This header isn't part of alsa-lib, it's part of the kernel.

I would like to make the minimal working code to generate any kind of PCM sound using ALSA in C++ for a Linux computer.

If it is only PCM you're after, then you can forget about asoundlib altogether. Give a try to https://gitlab.com/bztsrc/nanoalsa This is a single header library (just a header, no library needed). It also does not need alsa headers or anything else, just "sound/asound.h" for the ioctl structs and enums.

Using nanoalsa is as simple as:

#define ALSA_IMPLEMENTATION                               /* get the single header library */
#include "nanoalsa.h"

alsa_t ctx;                                               /* internal state (few bytes only) */

alsa_open(&ctx, 0, 0, SNDRV_PCM_FORMAT_S16_LE, 44100, 2); /* specify requested characteristics */
alsa_write(&ctx, buf, numframes);                         /* play the audio */
alsa_close(&ctx);                                         /* free resources */

The only con is, it only supports sample formats that your sound card supports, it does not convert samples. (On standard AC97 chips it is likely that the format you've tried, SNDRV_PCM_FORMAT_U8 mono channel will fail, you should use SNDRV_PCM_FORMAT_S16 stereo instead.)

Cheers,

bzt

Upvotes: 0

Follow these steps:

  1. Install the ALSA development package, or make sure it is already installed. The name depends on your distribution. In my case (OpenSUSE Tumbleweed) it is alsa-devel. This will install the file asoundlib.h under the directory /usr/include/alsa.
  2. Change the line in your code from #include "../include/asoundlib.h" to #include <alsa/asoundlib.h>. Notice the angular brackets instead of quotation marks.
  3. The library which you want to link against is named libsound.so, so compile the program with a command like gcc -Wall pcm_min.c -lasound -o pcm_min
  4. Run the program: ./pcm_min

Upvotes: 2

Related Questions