15
votes

This will be my poorest question ever...

On an old netbook, I installed an even older version of Debian, and toyed around a bit. One of the rather pleasing results was a very basic MP3 player (using libmpg123), integrated for adding background music to a little application doing something completely different. I grew rather fond of this little solution.

In that program, I dumped the decoded audio (from mpg123_decode()) to /dev/audio via a simple fwrite().

This worked fine - on the netbook.

Now, I came to understand that /dev/audio was something done by OSS, and is no longer supported on newer (ALSA) machines. Sure enough, my laptop (running a current Linux Mint) does not have this device.

So apparently I have to use ALSA instead. Searching the web, I've found a couple of tutorials, and they pretty much blow my mind. Modes, parameters, capabilities, access type, sample format, sample rate, number of channels, number of periods, period size... I understand that ALSA is a powerful API for the ambitious, but that's not what I am looking for (or have the time to grok). All I am looking for is how to play the output of mpg123_decode (the format of which I don't even know, not being an audio geek by a long shot).

Can anybody give me some hints on what needs to be done?

tl;dr

How do I get ALSA to play raw audio data?

4
have you looked at these sample programs? alumnos.elo.utfsm.cl/~yanez/alsa-sample-programsuser195488
@0A0D: Indeed I have not. Ah... those are simple examples? 885 lines, that's a bit more than I was hoping for... 8-ODevSolar

4 Answers

4
votes

There's an OSS compatibility layer for ALSA in the alsa-oss package. Install it and run your program inside the "aoss" program. Or, modprobe the modules listed here:

http://wiki.debian.org/SoundFAQ/#line-105

Then, you'll need to change your program to use "/dev/dsp" or "/dev/dsp0" instead of "/dev/audio". It should work how you remembered... but you might want to cross your fingers just in case.

2
votes

You could install sox and open a pipe to the play command with the correct samplerate and sample size arguments.

2
votes

Using ALSA directly is overly complicated, so I hope a Gstreamer solution is fine to you too. Gstreamer gives a nice abstraction to ALSA/OSS/Pulseaudio/you name it -- and is ubiquitous in the Linux world.

I wrote a little library that will open a FILE object where you can fwrite PCM data into: Gstreamer file. The actual code is less than 100 lines.

Use use it like that:

FILE *output = fopen_gst(rate, channels, bit_depth); // open audio output file
while (have_more_data) fwrite(data, amount, 1, output); // output audio data
fclose(output); // close the output file

I added an mpg123 example, too.

Here is the whole file (in case Github get's out of business ;-) ):

/**
 * gstreamer_file.c
 * Copyright  2012  René Kijewski  <[email protected]>
 * License: LGPL 3.0  (http://www.gnu.org/licenses/lgpl-3.0)
 */

#include "gstreamer_file.h"

#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>

#include <glib.h>
#include <gst/gst.h>

#ifndef _GNU_SOURCE
#   error "You need to add -D_GNU_SOURCE to the GCC parameters!"
#endif

/**
 * Cookie passed to the callbacks.
 */
typedef struct {
    /** { file descriptor to read from, fd to write to } */
    int pipefd[2];
    /** Gstreamer pipeline */
    GstElement *pipeline;
} cookie_t;

static ssize_t write_gst(void *cookie_, const char *buf, size_t size) {
    cookie_t *cookie = cookie_;
    return write(cookie->pipefd[1], buf, size);
}

static int close_gst(void *cookie_) {
    cookie_t *cookie = cookie_;
    gst_element_set_state(cookie->pipeline, GST_STATE_NULL); /* we are finished */
    gst_object_unref(GST_OBJECT(cookie->pipeline)); /* we won't access the pipeline anymore */
    close(cookie->pipefd[0]); /* we won't write anymore */
    close(cookie->pipefd[1]); /* we won't read anymore */
    free(cookie); /* dispose the cookie */
    return 0;
}

FILE *fopen_gst(long rate, int channels, int depth) {
    /* initialize Gstreamer */
    if (!gst_is_initialized()) {
        GError *error;
        if (!gst_init_check(NULL, NULL, &error)) {
            g_error_free(error);
            return NULL;
        }
    }

    /* get a cookie */
    cookie_t *cookie = malloc(sizeof(*cookie));
    if (!cookie) {
        return NULL;
    }

    /* open a pipe to be used between the caller and the Gstreamer pipeline */
    if (pipe(cookie->pipefd) != 0) {
        close(cookie->pipefd[0]);
        close(cookie->pipefd[1]);
        free(cookie);
        return NULL;
    }

    /* set up the pipeline */
    char description[256];
    snprintf(description, sizeof(description),
            "fdsrc fd=%d ! " /* read from a file descriptor */
            "audio/x-raw-int, rate=%ld, channels=%d, " /* get PCM data */
                "endianness=1234, width=%d, depth=%d, signed=true ! "
            "audioconvert ! audioresample ! " /* convert/resample if needed */
            "autoaudiosink", /* output to speakers (using ALSA, OSS, Pulseaudio ...) */
            cookie->pipefd[0], rate, channels, depth, depth);
    cookie->pipeline = gst_parse_launch_full(description, NULL,
            GST_PARSE_FLAG_FATAL_ERRORS, NULL);
    if (!cookie->pipeline) {
        close(cookie->pipefd[0]);
        close(cookie->pipefd[1]);
        free(cookie);
        return NULL;
    }

    /* open a FILE with specialized write and close functions */
    cookie_io_functions_t io_funcs = { NULL, write_gst, NULL, close_gst };
    FILE *result = fopencookie(cookie, "w", io_funcs);
    if (!result) {
        close_gst(cookie);
        return NULL;
    }

    /* start the pipeline (of cause it will wait for some data first) */
    gst_element_set_state(cookie->pipeline, GST_STATE_PLAYING);
    return result;
}
1
votes

Combining the comment of Artefact2 (using aplay for output) and the answer of kay (using pipe(), which I had not touched before), I came up with this "minimal" example. For the ALSA version, it creates a pipe, forks an aplay process with the appropriate parameters, and feeds the decoded audio to it.

Using lots of assert() to show the error codes associated with the individual function calls. The next step, of course, would not be the adding of -DNDEBUG (which would make this program really quick and really useless), but the replacing of the asserts with appropriate error handling including human-readable error messages.

// A small example program showing how to decode an MP3 file.

#include <assert.h>
#include <stdlib.h>

#include <mpg123.h>
#include <unistd.h>
#include <fcntl.h>

int main( int argc, char * argv[] )
{
    // buffer and counter for decoded audio data
    size_t OUTSIZE = 65536;
    unsigned char outmem[OUTSIZE];
    size_t outbytes;

    // output file descriptor
    int outfile;

    // handle, return code for mpg123
    mpg123_handle * handle;
    int rc;

    // one command line parameter, being the MP3 filename
    assert( argc == 2 );

#ifdef OSS
    assert( ( outfile = open( "/dev/audio", O_WRONLY ) ) != -1 );
#else // ALSA
    // pipe file descriptors
    int piped[2];

    assert( pipe( piped ) != -1 );

    // fork into decoder (parent) and player (child)
    if ( fork() )
    {
        // player (child)
        assert( dup2( piped[0], 0 ) != -1 ); // make pipe-in the new stdin
        assert( close( piped[1] ) == 0 ); // pipe-out, not needed
        assert( execlp( "aplay", "aplay", "-q", "-r44100", "-c2", "-fS16_LE", "-traw", NULL ) != -1 ); // should not return
    }
    else
    {
        // decoder (parent)
        close( piped[0] ); // pipe-in, not needed
        outfile = piped[1];
    }
#endif

    // initializing
    assert( mpg123_init() == MPG123_OK );
    assert( atexit( mpg123_exit ) == 0 );

    // setting up handle
    assert( ( handle = mpg123_new( NULL, NULL ) ) != NULL );

    // clearing the format list, and setting the one preferred format
    assert( mpg123_format_none( handle ) == MPG123_OK );
    assert( mpg123_format( handle, 44100, MPG123_STEREO, MPG123_ENC_SIGNED_16 ) == MPG123_OK );

    // open input MP3 file
    assert( mpg123_open( handle, argv[1] ) == MPG123_OK );

    // loop over input
    while ( rc != MPG123_DONE )
    {
        rc = mpg123_read( handle, outmem, OUTSIZE, &outbytes );
        assert( rc != MPG123_ERR && rc != MPG123_NEED_MORE );
        assert( write( outfile, outmem, outbytes ) != -1 );
    }

    // cleanup
    assert( close( outfile ) == 0 );
    mpg123_delete( handle );
    return EXIT_SUCCESS;
}

I hope this helps others with similar problems, as a template.