Page 1 of 2

.wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 2:26 pm
by davenull
hello,
how can I play a .wav file from my C/C++ program (Raspbian) ?
I am compiling via the Geany IDE,
the files are in my pi/sounds/ subdir,
playback via headphone jack or HDMI

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 2:32 pm
by topguy
Unless you need very detailed control over the playing you can use external programs like "paplay" ( http://manpages.ubuntu.com/manpages/wil ... lay.1.html ) or maybe "aplay" (http://linux.die.net/man/1/aplay).
Basically calling these programs with the "system()" call will probably get the job done.

You can experiment with the tools in the standard shell to find the correct parameters.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 2:40 pm
by davenull
thank you, but I would prefer to use a C API command like first opening a file to read and then just play it, no shell or external programs if possible.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:29 pm
by PeterO
All you need to know....
http://www.alsa-project.org/alsa-doc/alsa-lib/
PeterO

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:34 pm
by davenull
hmmmh... thank you, but that's 2 numbers oversized.
I was thinking more of kind of a "Simple Audio Player" like I am used to for my Arduinos...:
https://www.arduino.cc/en/Tutorial/SimpleAudioPlayer

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:36 pm
by PeterO
Did you even look at the examples ?
PeterO

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:41 pm
by davenull

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:54 pm
by PeterO
I give up.
PeterO

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 3:59 pm
by davenull
perhaps something like

Code: Select all

#include <audio.h>

pFile = fopen ("mysound.wav","r");
playwav(pFile);
fclose (pFile);

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 5:17 pm
by 6by9

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Thu May 19, 2016 5:56 pm
by davenull
thank you, ALSA is still a lot more complicated than I thought it should be possible, and also there seem to be some issues about it (but I don't understand the detalis yet admittedly).
Perhaps a lib should be able to read the wav metadata automatically and then apply that on the wav data file to just play them and handle all those errors mentioned here automatically, too.
https://gist.github.com/ghedo/963382

Code: Select all

#include <audio.h>

unsigned char metadata[44];
pFile = fopen ("mysound.wav","r");
readwavmetadata(pFile, * metadata);
playwav(pFile, * metadata);
fclose (pFile);

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 3:29 pm
by davenull
I happend to find
https://github.com/tinyalsa/tinyalsa

but how to install that?
There is no description how to do it... :?

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 3:35 pm
by 6by9
davenull wrote:I happend to find
https://github.com/tinyalsa/tinyalsa

but how to install that?
There is no description how to do it... :?
Had you tried

Code: Select all

git clone https://github.com/tinyalsa/tinyalsa.git
cd tinyalsa
make
?
I don't have a Pi to hand, but that built libtinyalso.so, tinycap, tinymix, minypcminfo, and tinyplay for me in the current directory.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 3:41 pm
by davenull
yes, thank you, it did something, no errors!

now I have to find out how to make the libs work from a C program... :)

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 3:49 pm
by 6by9
If you haven't dabbled in git, do so. There's so much stuff on github, and it's the same mechanism to clone any of it.
If there's a file called "Makefile", then "make" is going to be the magic almost every time.
If there's a file called CMakeLists.txt. then "cmake" is going to be the answer, although it may need some extra runes. Cmake will create a set of Makefiles, so then you "make".
I know you use Geany as an IDE, but if you're pulling in other projects you may need to drop back to the command-line.

Look at the source for tinyplay

Code: Select all

play_sample(file, card, device, channels, rate, bits, period_size, period_count);
It's not doing the parsing of the WAV file header under play_sample, that's what the rest of tinyplay is doing.
Just make sure you add the right Makefile magic to make use of tinyalsa as a library.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 3:52 pm
by davenull
yes, I always use Geany exclusively, I will never use the command line.

Other way round,
there must be a simple and convenient wav C/C++ API lib which can be compiled and used with Geany.

I have copied the tinyplay.c file into Geany and compiled, but there is a compilation error:

g++ -Wall -pthread -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -c "tinyplay0000.c" -lshapes -lwiringPi -lrt -lpigpio -std=c++11 (im Verzeichnis: /home/pi/Desktop/pi/programs/soundprogs)
tinyplay0000.c:29:32: fatal error: tinyalsa/asoundlib.h: Datei oder Verzeichnis nicht gefunden
#include <tinyalsa/asoundlib.h>
^
compilation terminated.
Kompilierung fehlgeschlagen.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 4:18 pm
by 6by9
davenull wrote:yes, I always use Geany exclusively, I will never use the command line.

Other way round,
there must be a simple and convenient wav C/C++ API lib which can be compiled and used with Geany.
Sorry, but you've now consigned yourself to a small community using one very specific tool. That is my cue to drop out of all discussion.
davenull wrote:I have copied the tinyplay.c file into Geany and compiled, but there is a compilation error:

g++ -Wall -pthread -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -c "tinyplay0000.c" -lshapes -lwiringPi -lrt -lpigpio -std=c++11 (im Verzeichnis: /home/pi/Desktop/pi/programs/soundprogs)
tinyplay0000.c:29:32: fatal error: tinyalsa/asoundlib.h: Datei oder Verzeichnis nicht gefunden
#include <tinyalsa/asoundlib.h>
^
compilation terminated.
Kompilierung fehlgeschlagen.
Go and read up on how to add libraries and include paths within Geany. I could tell you how to do it in a Makefile (look at the example one with tinyalsa), but you're refusing to use the command-line or understand the underlying principles of how the build system works.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Fri May 20, 2016 6:51 pm
by davenull
sorry, Geany is the only option.
My project is not playing just an audio file, my project is an autonomous mobile robot, using wiringPi, Arduinos as IO multiplexers by i2c and UART and USB-BT remote control, several sensors for a SLAM application, speech recognition, and a neural net - and into that project the wav processing has to be integrated.
All this I will not be able to manage by command lines, just by the Geany IDE. So I compellingly need the complete settings for Geany.
Unfortunately the tinyalsa's API user documentation is very poor.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Sat May 21, 2016 2:59 pm
by davenull
update:
still faulty!

tinyalsa path:
/lib/tinyalsa/

g++ -Wall -pthread -I/opt/vc/include -I/opt/vc/include/interface/vmcs_host/linux -I/opt/vc/include/interface/vcos/pthreads -c "tinyplay0000.c" -lshapes -lwiringPi -lrt -lpigpio -I/lib/tinyalsa/include -L/lib/tinyalsa -ltinyalsa -std=c++11 (im Verzeichnis: /home/pi/Desktop/pi/programs/soundprogs)
tinyplay0000.c: In function ‘int check_param(pcm_params*, unsigned int, unsigned int, char*, char*)’:
tinyplay0000.c:201:43: error: invalid conversion from ‘unsigned int’ to ‘pcm_param’ [-fpermissive]
min = pcm_params_get_min(params, param);
^
In file included from tinyplay0000.c:29:0:
/lib/tinyalsa/include/tinyalsa/asoundlib.h:151:14: note: initializing argument 2 of ‘unsigned int pcm_params_get_min(pcm_params*, pcm_param)’
unsigned int pcm_params_get_min(struct pcm_params *pcm_params,
^
tinyplay0000.c:208:43: error: invalid conversion from ‘unsigned int’ to ‘pcm_param’ [-fpermissive]
max = pcm_params_get_max(params, param);
^
In file included from tinyplay0000.c:29:0:
/lib/tinyalsa/include/tinyalsa/asoundlib.h:153:14: note: initializing argument 2 of ‘unsigned int pcm_params_get_max(pcm_params*, pcm_param)’
unsigned int pcm_params_get_max(struct pcm_params *pcm_params,
^
tinyplay0000.c: In function ‘int sample_is_playable(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int)’:
tinyplay0000.c:231:77: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
can_play = check_param(params, PCM_PARAM_RATE, rate, "Sample rate", "Hz");
^
tinyplay0000.c:231:77: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
tinyplay0000.c:232:88: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, "Sample", " channels");
^
tinyplay0000.c:232:88: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
tinyplay0000.c:233:84: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, "Bitrate", " bits");
^
tinyplay0000.c:233:84: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
tinyplay0000.c:234:92: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, "Period size", "Hz");
^
tinyplay0000.c:234:92: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
tinyplay0000.c:235:90: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, "Period count", "Hz");
^
tinyplay0000.c:235:90: warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
tinyplay0000.c: In function ‘void play_sample(FILE*, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int, unsigned int)’:
tinyplay0000.c:277:25: error: invalid conversion from ‘void*’ to ‘char*’ [-fpermissive]
buffer = malloc(size);
^
Kompilierung fehlgeschlagen.

Code: Select all

/* tinyplay.c
**
**  ver tinyplay0000
**
** Copyright 2011, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of The Android Open Source Project nor the names of
**       its contributors may be used to endorse or promote products derived
**       from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/

#include <tinyalsa/asoundlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

struct riff_wave_header {
    uint32_t riff_id;
    uint32_t riff_sz;
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
};

static int close = 0;

void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels,
                 unsigned int rate, unsigned int bits, unsigned int period_size,
                 unsigned int period_count);

void stream_close(int sig)
{
    /* allow the stream to be closed gracefully */
    signal(sig, SIG_IGN);
    close = 1;
}

int main(int argc, char **argv)
{
    FILE *file;
    struct riff_wave_header riff_wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;
    unsigned int device = 0;
    unsigned int card = 0;
    unsigned int period_size = 1024;
    unsigned int period_count = 4;
    unsigned int channels = 2;
    unsigned int rate = 48000;
    unsigned int bits = 16;
    unsigned int is_raw = 0; /* Default wav file */
    char *filename;
    int more_chunks = 1;

    if (argc < 2) {
        fprintf(stderr, "Usage1: %s file.wav [-D card] [-d device] [-p period_size]"
                " [-n n_periods] \n", argv[0]);
        fprintf(stderr, "Usage2: %s file.raw [-D card] [-d device] [-p period_size] "
                "[-n n_periods] [-c channels] [-r rate] [-b bits] -i raw \n", argv[0]);
        return 1;
    }

    filename = argv[1];
    file = fopen(filename, "rb");
    if (!file) {
        fprintf(stderr, "Unable to open file '%s'\n", filename);
        return 1;
    }

    /* parse command line arguments */
    argv += 2;
    while (*argv) {
        if (strcmp(*argv, "-d") == 0) {
            argv++;
            if (*argv)
                device = atoi(*argv);
        }
        if (strcmp(*argv, "-p") == 0) {
            argv++;
            if (*argv)
                period_size = atoi(*argv);
        }
        if (strcmp(*argv, "-n") == 0) {
            argv++;
            if (*argv)
                period_count = atoi(*argv);
        }
        if (strcmp(*argv, "-D") == 0) {
            argv++;
            if (*argv)
                card = atoi(*argv);
        }
        if (strcmp(*argv, "-c") == 0) {
            argv++;
            if (*argv)
                channels = atoi(*argv);
        }
        if (strcmp(*argv, "-r") == 0) {
            argv++;
            if (*argv)
                rate = atoi(*argv);
        }
        if (strcmp(*argv, "-b") == 0) {
            argv++;
            if (*argv)
                bits = atoi(*argv);
        }
        if (strcmp(*argv, "-i") == 0) {
            argv++;
            if (*argv) {
                if (strcasecmp(*argv, "raw") == 0) {
                    is_raw = 1;
                }
            }
        }
        if (*argv)
            argv++;
    }

    if ( !is_raw ) {
        fread(&riff_wave_header, sizeof(riff_wave_header), 1, file);
        if ((riff_wave_header.riff_id != ID_RIFF) ||
            (riff_wave_header.wave_id != ID_WAVE)) {
            fprintf(stderr, "Error: '%s' is not a riff/wave file\n", filename);
            fclose(file);
            return 1;
        }
        do {
            fread(&chunk_header, sizeof(chunk_header), 1, file);
            switch (chunk_header.id) {
            case ID_FMT:
                fread(&chunk_fmt, sizeof(chunk_fmt), 1, file);
                /* If the format header is larger, skip the rest */
                if (chunk_header.sz > sizeof(chunk_fmt))
                    fseek(file, chunk_header.sz - sizeof(chunk_fmt), SEEK_CUR);
                break;
            case ID_DATA:
                /* Stop looking for chunks */
                more_chunks = 0;
                break;
            default:
                /* Unknown chunk, skip bytes */
                fseek(file, chunk_header.sz, SEEK_CUR);
            }
        } while (more_chunks);
        channels = chunk_fmt.num_channels;
        rate = chunk_fmt.sample_rate;
        bits = chunk_fmt.bits_per_sample;
    }

    play_sample(file, card, device, channels, rate, bits, period_size, period_count);

    fclose(file);

    return 0;
}

int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
                 char *param_name, char *param_unit)
{
    unsigned int min;
    unsigned int max;
    int is_within_bounds = 1;

    min = pcm_params_get_min(params, param);
    if (value < min) {
        fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
                param_unit, min, param_unit);
        is_within_bounds = 0;
    }

    max = pcm_params_get_max(params, param);
    if (value > max) {
        fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
                param_unit, max, param_unit);
        is_within_bounds = 0;
    }

    return is_within_bounds;
}

int sample_is_playable(unsigned int card, unsigned int device, unsigned int channels,
                        unsigned int rate, unsigned int bits, unsigned int period_size,
                        unsigned int period_count)
{
    struct pcm_params *params;
    int can_play;

    params = pcm_params_get(card, device, PCM_OUT);
    if (params == NULL) {
        fprintf(stderr, "Unable to open PCM device %u.\n", device);
        return 0;
    }

    can_play = check_param(params, PCM_PARAM_RATE, rate, "Sample rate", "Hz");
    can_play &= check_param(params, PCM_PARAM_CHANNELS, channels, "Sample", " channels");
    can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, bits, "Bitrate", " bits");
    can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, period_size, "Period size", "Hz");
    can_play &= check_param(params, PCM_PARAM_PERIODS, period_count, "Period count", "Hz");

    pcm_params_free(params);

    return can_play;
}

void play_sample(FILE *file, unsigned int card, unsigned int device, unsigned int channels,
                 unsigned int rate, unsigned int bits, unsigned int period_size,
                 unsigned int period_count)
{
    struct pcm_config config;
    struct pcm *pcm;
    char *buffer;
    int size;
    int num_read;

    memset(&config, 0, sizeof(config));
    config.channels = channels;
    config.rate = rate;
    config.period_size = period_size;
    config.period_count = period_count;
    if (bits == 32)
        config.format = PCM_FORMAT_S32_LE;
    else if (bits == 16)
        config.format = PCM_FORMAT_S16_LE;
    config.start_threshold = 0;
    config.stop_threshold = 0;
    config.silence_threshold = 0;

    if (!sample_is_playable(card, device, channels, rate, bits, period_size, period_count)) {
        return;
    }

    pcm = pcm_open(card, device, PCM_OUT, &config);
    if (!pcm || !pcm_is_ready(pcm)) {
        fprintf(stderr, "Unable to open PCM device %u (%s)\n",
                device, pcm_get_error(pcm));
        return;
    }

    size = pcm_frames_to_bytes(pcm, pcm_get_buffer_size(pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "Unable to allocate %d bytes\n", size);
        free(buffer);
        pcm_close(pcm);
        return;
    }

    printf("Playing sample: %u ch, %u hz, %u bit\n", channels, rate, bits);

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    do {
        num_read = fread(buffer, 1, size, file);
        if (num_read > 0) {
            if (pcm_write(pcm, buffer, num_read)) {
                fprintf(stderr, "Error playing sample\n");
                break;
            }
        }
    } while (!close && num_read > 0);

    free(buffer);
    pcm_close(pcm);
}

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Sun May 22, 2016 5:59 pm
by sheroy
To read a wav file you may refer to
http://soundfile.sapp.org/doc/WaveFormat/
for the file format and how to read, you may use pread.

use timing (from time.h) to read the sample at the right time and put out a voltage onto the DAC which a linear function of the sample read. Beware for data in 2's compliment form.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Tue Jul 18, 2017 8:13 am
by miaomiao
davenull wrote:hello,
how can I play a .wav file from my C/C++ program (Raspbian) ?
I am compiling via the Geany IDE,
the files are in my pi/sounds/ subdir,
playback via headphone jack or HDMI

Hi,I am same situation as you now. Using Geany and want to play a .wav file in C language. Have you solve the problem?Thank you

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Tue Jul 18, 2017 1:57 pm
by LdB
It's not complex it's just an error with using deprecated functions so use the new ones that replaced them.

Don't have a Pi with linux to try it but it compiles without error so you never know you could be lucky

Code: Select all

/* tinyplay.c
**
** Copyright 2011, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of The Android Open Source Project nor the names of
**       its contributors may be used to endorse or promote products derived
**       from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/

#include "tinyalsa/asoundlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>

struct cmd {
    const char *filename;
    const char *filetype;
    unsigned int card;
    unsigned int device;
    int flags;
    struct pcm_config config;
    unsigned int bits;
};

void cmd_init(struct cmd *cmd)
{
    cmd->filename = NULL;
    cmd->filetype = NULL;
    cmd->card = 0;
    cmd->device = 0;
    cmd->flags = PCM_OUT;
    cmd->config.period_size = 1024;
    cmd->config.period_count = 2;
    cmd->config.channels = 2;
    cmd->config.rate = 48000;
    cmd->config.format = PCM_FORMAT_S16_LE;
    cmd->config.silence_threshold = 1024 * 2;
    cmd->config.stop_threshold = 1024 * 2;
    cmd->config.start_threshold = 1024;
    cmd->bits = 16;
}

int cmd_parse_arg(struct cmd *cmd, int argc, const char **argv)
{
    if (argc < 1) {
        return 0;
    }

    if ((strcmp(argv[0], "-M") == 0) || (strcmp(argv[0], "--mmap") == 0)) {
        cmd->flags |= PCM_MMAP;
        return 1;
    }

    if (argv[0][0] != '-') {
        cmd->filename = argv[0];
        return 1;
    }

    if (argc < 2) {
        fprintf(stderr, "option '%s' is missing argument\n", argv[0]);
        return -1;
    }

    if ((strcmp(argv[0], "-D") == 0) || (strcmp(argv[0], "--card") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->card) != 1) {
            fprintf(stderr, "failed parsing card number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-d") == 0) || (strcmp(argv[0], "--device") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->device) != 1) {
            fprintf(stderr, "failed parsing device number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-size") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_size) != 1) {
            fprintf(stderr, "failed parsing period size '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-count") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_count) != 1) {
            fprintf(stderr, "failed parsing period count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-c") == 0) || (strcmp(argv[0], "--channels") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.channels) != 1) {
            fprintf(stderr, "failed parsing channel count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-r") == 0) || (strcmp(argv[0], "--rate") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.rate) != 1) {
            fprintf(stderr, "failed parsing rate '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-i") == 0) || (strcmp(argv[0], "--file-type") == 0)) {
        cmd->filetype = argv[1];
    } else {
        fprintf(stderr, "unknown option '%s'\n", argv[0]);
        return -1;
    }
    return 2;
}

int cmd_parse_args(struct cmd *cmd, int argc, const char **argv)
{
    int i = 0;
    while (i < argc) {
        int j = cmd_parse_arg(cmd, argc - i, &argv[i]);
        if (j < 0){
            break;
        }
        i += j;
    }

    if ((cmd->filename != NULL)
     && (cmd->filetype == NULL)) {
        cmd->filetype = strrchr(cmd->filename, '.');
        if (cmd->filetype != NULL) {
            cmd->filetype++;
        }
    }

    return i;
}

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

struct riff_wave_header {
    uint32_t riff_id;
    uint32_t riff_sz;
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
};

struct ctx {
    struct pcm *pcm;

    struct riff_wave_header wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;

    FILE *file;
};

int ctx_init(struct ctx* ctx, const struct cmd *cmd)
{
    unsigned int bits = cmd->bits;
    struct pcm_config config = cmd->config;

    if (cmd->filename == NULL) {
        fprintf(stderr, "filename not specified\n");
        return -1;
    }

    ctx->file = fopen(cmd->filename, "rb");
    if (ctx->file == NULL) {
        fprintf(stderr, "failed to open '%s'\n", cmd->filename);
        return -1;
    }

    if ((cmd->filetype != NULL) && (strcmp(cmd->filetype, "wav") == 0)) {
        if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
            fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
        if ((ctx->wave_header.riff_id != ID_RIFF) ||
            (ctx->wave_header.wave_id != ID_WAVE)) {
            fprintf(stderr, "error: '%s' is not a riff/wave file\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
	unsigned int more_chunks = 1;
        do {
            if (fread(&ctx->chunk_header, sizeof(ctx->chunk_header), 1, ctx->file) != 1){
                fprintf(stderr, "error: '%s' does not contain a data chunk\n", cmd->filename);
                fclose(ctx->file);
                return -1;
            }
            switch (ctx->chunk_header.id) {
            case ID_FMT:
                if (fread(&ctx->chunk_fmt, sizeof(ctx->chunk_fmt), 1, ctx->file) != 1){
                    fprintf(stderr, "error: '%s' has incomplete format chunk\n", cmd->filename);
                    fclose(ctx->file);
                    return -1;
                }
                /* If the format header is larger, skip the rest */
                if (ctx->chunk_header.sz > sizeof(ctx->chunk_fmt))
                    fseek(ctx->file, ctx->chunk_header.sz - sizeof(ctx->chunk_fmt), SEEK_CUR);
                break;
            case ID_DATA:
                /* Stop looking for chunks */
                more_chunks = 0;
                break;
            default:
                /* Unknown chunk, skip bytes */
                fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
            }
        } while (more_chunks);
        config.channels = ctx->chunk_fmt.num_channels;
        config.rate = ctx->chunk_fmt.sample_rate;
        bits = ctx->chunk_fmt.bits_per_sample;
    }

    if (bits == 8) {
        config.format = PCM_FORMAT_S8;
    } else if (bits == 16) {
        config.format = PCM_FORMAT_S16_LE;
    } else if (bits == 24) {
        config.format = PCM_FORMAT_S24_3LE;
    } else if (bits == 32) {
        config.format = PCM_FORMAT_S32_LE;
    } else {
        fprintf(stderr, "bit count '%u' not supported\n", bits);
        fclose(ctx->file);
        return -1;
    }

    ctx->pcm = pcm_open(cmd->card,
                        cmd->device,
                        cmd->flags,
                        &config);
    if (ctx->pcm == NULL) {
        fprintf(stderr, "failed to allocate memory for pcm\n");
        fclose(ctx->file);
        return -1;
    } else if (!pcm_is_ready(ctx->pcm)) {
        fprintf(stderr, "failed to open for pcm %u,%u\n", cmd->card, cmd->device);
        fclose(ctx->file);
        pcm_close(ctx->pcm);
        return -1;
    }

    return 0;
}

void ctx_free(struct ctx *ctx)
{
    if (ctx == NULL) {
        return;
    }
    if (ctx->pcm != NULL) {
        pcm_close(ctx->pcm);
    }
    if (ctx->file != NULL) {
        fclose(ctx->file);
    }
}

static int close = 0;

int play_sample(struct ctx *ctx);

void stream_close(int sig)
{
    /* allow the stream to be closed gracefully */
    signal(sig, SIG_IGN);
    close = 1;
}

void print_usage(const char *argv0)
{
    fprintf(stderr, "usage: %s file.wav [options]\n", argv0);
    fprintf(stderr, "options:\n");
    fprintf(stderr, "-D | --card   <card number>    The device to receive the audio\n");
    fprintf(stderr, "-d | --device <device number>  The card to receive the audio\n");
    fprintf(stderr, "-p | --period-size <size>      The size of the PCM's period\n");
    fprintf(stderr, "-n | --period-count <count>    The number of PCM periods\n");
    fprintf(stderr, "-i | --file-type <file-type >  The type of file to read (raw or wav)\n");
    fprintf(stderr, "-c | --channels <count>        The amount of channels per frame\n");
    fprintf(stderr, "-r | --rate <rate>             The amount of frames per second\n");
    fprintf(stderr, "-b | --bits <bit-count>        The number of bits in one sample\n");
    fprintf(stderr, "-M | --mmap                    Use memory mapped IO to play audio\n");
}


int main(int argc, const char **argv)
{
    struct cmd cmd;
    struct ctx ctx;

    if (argc < 2) {
        print_usage(argv[0]);
        return EXIT_FAILURE;
    }

    cmd_init(&cmd);
    if (cmd_parse_args(&cmd, argc - 1, &argv[1]) < 0) {
        return EXIT_FAILURE;
    }

    if (ctx_init(&ctx, &cmd) < 0) {
        return EXIT_FAILURE;
    }

    /* TODO get parameters from context */
    printf("playing '%s': %u ch, %u hz, %u bit\n",
           cmd.filename,
           cmd.config.channels,
           cmd.config.rate,
           cmd.bits);

    if (play_sample(&ctx) < 0) {
        ctx_free(&ctx);
        return EXIT_FAILURE;
    }

    ctx_free(&ctx);
    return EXIT_SUCCESS;
}

int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
                 char *param_name, char *param_unit)
{
    unsigned int min;
    unsigned int max;
    int is_within_bounds = 1;

    min = pcm_params_get_min(params, param);
    if (value < min) {
        fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
                param_unit, min, param_unit);
        is_within_bounds = 0;
    }

    max = pcm_params_get_max(params, param);
    if (value > max) {
        fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
                param_unit, max, param_unit);
        is_within_bounds = 0;
    }

    return is_within_bounds;
}

int sample_is_playable(const struct cmd *cmd)
{
    struct pcm_params *params;
    int can_play;

    params = pcm_params_get(cmd->card, cmd->device, PCM_OUT);
    if (params == NULL) {
        fprintf(stderr, "unable to open PCM %u,%u\n", cmd->card, cmd->device);
        return 0;
    }

    can_play = check_param(params, PCM_PARAM_RATE, cmd->config.rate, "sample rate", "hz");
    can_play &= check_param(params, PCM_PARAM_CHANNELS, cmd->config.channels, "sample", " channels");
    can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, cmd->bits, "bits", " bits");
    can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, cmd->config.period_size, "period size", "");
    can_play &= check_param(params, PCM_PARAM_PERIODS, cmd->config.period_count, "period count", "");

    pcm_params_free(params);

    return can_play;
}

int play_sample(struct ctx *ctx)
{
    char *buffer;
    int size;
    int num_read;

    size = pcm_frames_to_bytes(ctx->pcm, pcm_get_buffer_size(ctx->pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "unable to allocate %d bytes\n", size);
        return -1;
    }

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    do {
        num_read = fread(buffer, 1, size, ctx->file);
        if (num_read > 0) {
		if (pcm_writei(ctx->pcm, buffer,
			pcm_bytes_to_frames(ctx->pcm, num_read)) < 0) {
                fprintf(stderr, "error playing sample\n");
                break;
            }
        }
    } while (!close && num_read > 0);

    free(buffer);
    return 0;
}

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Wed Jul 19, 2017 1:41 am
by miaomiao
LdB wrote:It's not complex it's just an error with using deprecated functions so use the new ones that replaced them.

Don't have a Pi with linux to try it but it compiles without error so you never know you could be lucky

Code: Select all

/* tinyplay.c
**
** Copyright 2011, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of The Android Open Source Project nor the names of
**       its contributors may be used to endorse or promote products derived
**       from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/

#include "tinyalsa/asoundlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>

struct cmd {
    const char *filename;
    const char *filetype;
    unsigned int card;
    unsigned int device;
    int flags;
    struct pcm_config config;
    unsigned int bits;
};

void cmd_init(struct cmd *cmd)
{
    cmd->filename = NULL;
    cmd->filetype = NULL;
    cmd->card = 0;
    cmd->device = 0;
    cmd->flags = PCM_OUT;
    cmd->config.period_size = 1024;
    cmd->config.period_count = 2;
    cmd->config.channels = 2;
    cmd->config.rate = 48000;
    cmd->config.format = PCM_FORMAT_S16_LE;
    cmd->config.silence_threshold = 1024 * 2;
    cmd->config.stop_threshold = 1024 * 2;
    cmd->config.start_threshold = 1024;
    cmd->bits = 16;
}

int cmd_parse_arg(struct cmd *cmd, int argc, const char **argv)
{
    if (argc < 1) {
        return 0;
    }

    if ((strcmp(argv[0], "-M") == 0) || (strcmp(argv[0], "--mmap") == 0)) {
        cmd->flags |= PCM_MMAP;
        return 1;
    }

    if (argv[0][0] != '-') {
        cmd->filename = argv[0];
        return 1;
    }

    if (argc < 2) {
        fprintf(stderr, "option '%s' is missing argument\n", argv[0]);
        return -1;
    }

    if ((strcmp(argv[0], "-D") == 0) || (strcmp(argv[0], "--card") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->card) != 1) {
            fprintf(stderr, "failed parsing card number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-d") == 0) || (strcmp(argv[0], "--device") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->device) != 1) {
            fprintf(stderr, "failed parsing device number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-size") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_size) != 1) {
            fprintf(stderr, "failed parsing period size '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-count") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_count) != 1) {
            fprintf(stderr, "failed parsing period count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-c") == 0) || (strcmp(argv[0], "--channels") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.channels) != 1) {
            fprintf(stderr, "failed parsing channel count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-r") == 0) || (strcmp(argv[0], "--rate") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.rate) != 1) {
            fprintf(stderr, "failed parsing rate '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-i") == 0) || (strcmp(argv[0], "--file-type") == 0)) {
        cmd->filetype = argv[1];
    } else {
        fprintf(stderr, "unknown option '%s'\n", argv[0]);
        return -1;
    }
    return 2;
}

int cmd_parse_args(struct cmd *cmd, int argc, const char **argv)
{
    int i = 0;
    while (i < argc) {
        int j = cmd_parse_arg(cmd, argc - i, &argv[i]);
        if (j < 0){
            break;
        }
        i += j;
    }

    if ((cmd->filename != NULL)
     && (cmd->filetype == NULL)) {
        cmd->filetype = strrchr(cmd->filename, '.');
        if (cmd->filetype != NULL) {
            cmd->filetype++;
        }
    }

    return i;
}

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

struct riff_wave_header {
    uint32_t riff_id;
    uint32_t riff_sz;
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
};

struct ctx {
    struct pcm *pcm;

    struct riff_wave_header wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;

    FILE *file;
};

int ctx_init(struct ctx* ctx, const struct cmd *cmd)
{
    unsigned int bits = cmd->bits;
    struct pcm_config config = cmd->config;

    if (cmd->filename == NULL) {
        fprintf(stderr, "filename not specified\n");
        return -1;
    }

    ctx->file = fopen(cmd->filename, "rb");
    if (ctx->file == NULL) {
        fprintf(stderr, "failed to open '%s'\n", cmd->filename);
        return -1;
    }

    if ((cmd->filetype != NULL) && (strcmp(cmd->filetype, "wav") == 0)) {
        if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
            fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
        if ((ctx->wave_header.riff_id != ID_RIFF) ||
            (ctx->wave_header.wave_id != ID_WAVE)) {
            fprintf(stderr, "error: '%s' is not a riff/wave file\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
	unsigned int more_chunks = 1;
        do {
            if (fread(&ctx->chunk_header, sizeof(ctx->chunk_header), 1, ctx->file) != 1){
                fprintf(stderr, "error: '%s' does not contain a data chunk\n", cmd->filename);
                fclose(ctx->file);
                return -1;
            }
            switch (ctx->chunk_header.id) {
            case ID_FMT:
                if (fread(&ctx->chunk_fmt, sizeof(ctx->chunk_fmt), 1, ctx->file) != 1){
                    fprintf(stderr, "error: '%s' has incomplete format chunk\n", cmd->filename);
                    fclose(ctx->file);
                    return -1;
                }
                /* If the format header is larger, skip the rest */
                if (ctx->chunk_header.sz > sizeof(ctx->chunk_fmt))
                    fseek(ctx->file, ctx->chunk_header.sz - sizeof(ctx->chunk_fmt), SEEK_CUR);
                break;
            case ID_DATA:
                /* Stop looking for chunks */
                more_chunks = 0;
                break;
            default:
                /* Unknown chunk, skip bytes */
                fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
            }
        } while (more_chunks);
        config.channels = ctx->chunk_fmt.num_channels;
        config.rate = ctx->chunk_fmt.sample_rate;
        bits = ctx->chunk_fmt.bits_per_sample;
    }

    if (bits == 8) {
        config.format = PCM_FORMAT_S8;
    } else if (bits == 16) {
        config.format = PCM_FORMAT_S16_LE;
    } else if (bits == 24) {
        config.format = PCM_FORMAT_S24_3LE;
    } else if (bits == 32) {
        config.format = PCM_FORMAT_S32_LE;
    } else {
        fprintf(stderr, "bit count '%u' not supported\n", bits);
        fclose(ctx->file);
        return -1;
    }

    ctx->pcm = pcm_open(cmd->card,
                        cmd->device,
                        cmd->flags,
                        &config);
    if (ctx->pcm == NULL) {
        fprintf(stderr, "failed to allocate memory for pcm\n");
        fclose(ctx->file);
        return -1;
    } else if (!pcm_is_ready(ctx->pcm)) {
        fprintf(stderr, "failed to open for pcm %u,%u\n", cmd->card, cmd->device);
        fclose(ctx->file);
        pcm_close(ctx->pcm);
        return -1;
    }

    return 0;
}

void ctx_free(struct ctx *ctx)
{
    if (ctx == NULL) {
        return;
    }
    if (ctx->pcm != NULL) {
        pcm_close(ctx->pcm);
    }
    if (ctx->file != NULL) {
        fclose(ctx->file);
    }
}

static int close = 0;

int play_sample(struct ctx *ctx);

void stream_close(int sig)
{
    /* allow the stream to be closed gracefully */
    signal(sig, SIG_IGN);
    close = 1;
}

void print_usage(const char *argv0)
{
    fprintf(stderr, "usage: %s file.wav [options]\n", argv0);
    fprintf(stderr, "options:\n");
    fprintf(stderr, "-D | --card   <card number>    The device to receive the audio\n");
    fprintf(stderr, "-d | --device <device number>  The card to receive the audio\n");
    fprintf(stderr, "-p | --period-size <size>      The size of the PCM's period\n");
    fprintf(stderr, "-n | --period-count <count>    The number of PCM periods\n");
    fprintf(stderr, "-i | --file-type <file-type >  The type of file to read (raw or wav)\n");
    fprintf(stderr, "-c | --channels <count>        The amount of channels per frame\n");
    fprintf(stderr, "-r | --rate <rate>             The amount of frames per second\n");
    fprintf(stderr, "-b | --bits <bit-count>        The number of bits in one sample\n");
    fprintf(stderr, "-M | --mmap                    Use memory mapped IO to play audio\n");
}


int main(int argc, const char **argv)
{
    struct cmd cmd;
    struct ctx ctx;

    if (argc < 2) {
        print_usage(argv[0]);
        return EXIT_FAILURE;
    }

    cmd_init(&cmd);
    if (cmd_parse_args(&cmd, argc - 1, &argv[1]) < 0) {
        return EXIT_FAILURE;
    }

    if (ctx_init(&ctx, &cmd) < 0) {
        return EXIT_FAILURE;
    }

    /* TODO get parameters from context */
    printf("playing '%s': %u ch, %u hz, %u bit\n",
           cmd.filename,
           cmd.config.channels,
           cmd.config.rate,
           cmd.bits);

    if (play_sample(&ctx) < 0) {
        ctx_free(&ctx);
        return EXIT_FAILURE;
    }

    ctx_free(&ctx);
    return EXIT_SUCCESS;
}

int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
                 char *param_name, char *param_unit)
{
    unsigned int min;
    unsigned int max;
    int is_within_bounds = 1;

    min = pcm_params_get_min(params, param);
    if (value < min) {
        fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
                param_unit, min, param_unit);
        is_within_bounds = 0;
    }

    max = pcm_params_get_max(params, param);
    if (value > max) {
        fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
                param_unit, max, param_unit);
        is_within_bounds = 0;
    }

    return is_within_bounds;
}

int sample_is_playable(const struct cmd *cmd)
{
    struct pcm_params *params;
    int can_play;

    params = pcm_params_get(cmd->card, cmd->device, PCM_OUT);
    if (params == NULL) {
        fprintf(stderr, "unable to open PCM %u,%u\n", cmd->card, cmd->device);
        return 0;
    }

    can_play = check_param(params, PCM_PARAM_RATE, cmd->config.rate, "sample rate", "hz");
    can_play &= check_param(params, PCM_PARAM_CHANNELS, cmd->config.channels, "sample", " channels");
    can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, cmd->bits, "bits", " bits");
    can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, cmd->config.period_size, "period size", "");
    can_play &= check_param(params, PCM_PARAM_PERIODS, cmd->config.period_count, "period count", "");

    pcm_params_free(params);

    return can_play;
}

int play_sample(struct ctx *ctx)
{
    char *buffer;
    int size;
    int num_read;

    size = pcm_frames_to_bytes(ctx->pcm, pcm_get_buffer_size(ctx->pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "unable to allocate %d bytes\n", size);
        return -1;
    }

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    do {
        num_read = fread(buffer, 1, size, ctx->file);
        if (num_read > 0) {
		if (pcm_writei(ctx->pcm, buffer,
			pcm_bytes_to_frames(ctx->pcm, num_read)) < 0) {
                fprintf(stderr, "error playing sample\n");
                break;
            }
        }
    } while (!close && num_read > 0);

    free(buffer);
    return 0;
}

Thank you for reply,but I need to download a extra "tinyalsa/asoundlib.h" librabry is it?

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Wed Jul 19, 2017 3:56 am
by LdB
You should have got all that when you installed the package .. see the instructions
(https://github.com/tinyalsa/tinyalsa)

sudo apt-add-repository ppa:taylorcholberton/tinyalsa
sudo apt-get update
sudo apt-get install tinyalsa
sudo apt-get install libtinyalsa-dev

The files just use the linux API there really isn't much to them.

Re: .wav file: play from a C/C++ program (Raspbian)

Posted: Wed Jul 19, 2017 5:20 am
by 1dot0
LdB wrote:It's not complex it's just an error with using deprecated functions so use the new ones that replaced them.

Don't have a Pi with linux to try it but it compiles without error so you never know you could be lucky

Code: Select all

/* tinyplay.c
**
** Copyright 2011, The Android Open Source Project
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are met:
**     * Redistributions of source code must retain the above copyright
**       notice, this list of conditions and the following disclaimer.
**     * Redistributions in binary form must reproduce the above copyright
**       notice, this list of conditions and the following disclaimer in the
**       documentation and/or other materials provided with the distribution.
**     * Neither the name of The Android Open Source Project nor the names of
**       its contributors may be used to endorse or promote products derived
**       from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY The Android Open Source Project ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL The Android Open Source Project BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
** SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
** CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
*/

#include "tinyalsa/asoundlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <signal.h>

struct cmd {
    const char *filename;
    const char *filetype;
    unsigned int card;
    unsigned int device;
    int flags;
    struct pcm_config config;
    unsigned int bits;
};

void cmd_init(struct cmd *cmd)
{
    cmd->filename = NULL;
    cmd->filetype = NULL;
    cmd->card = 0;
    cmd->device = 0;
    cmd->flags = PCM_OUT;
    cmd->config.period_size = 1024;
    cmd->config.period_count = 2;
    cmd->config.channels = 2;
    cmd->config.rate = 48000;
    cmd->config.format = PCM_FORMAT_S16_LE;
    cmd->config.silence_threshold = 1024 * 2;
    cmd->config.stop_threshold = 1024 * 2;
    cmd->config.start_threshold = 1024;
    cmd->bits = 16;
}

int cmd_parse_arg(struct cmd *cmd, int argc, const char **argv)
{
    if (argc < 1) {
        return 0;
    }

    if ((strcmp(argv[0], "-M") == 0) || (strcmp(argv[0], "--mmap") == 0)) {
        cmd->flags |= PCM_MMAP;
        return 1;
    }

    if (argv[0][0] != '-') {
        cmd->filename = argv[0];
        return 1;
    }

    if (argc < 2) {
        fprintf(stderr, "option '%s' is missing argument\n", argv[0]);
        return -1;
    }

    if ((strcmp(argv[0], "-D") == 0) || (strcmp(argv[0], "--card") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->card) != 1) {
            fprintf(stderr, "failed parsing card number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-d") == 0) || (strcmp(argv[0], "--device") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->device) != 1) {
            fprintf(stderr, "failed parsing device number '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-size") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_size) != 1) {
            fprintf(stderr, "failed parsing period size '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-p") == 0) || (strcmp(argv[0], "--period-count") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.period_count) != 1) {
            fprintf(stderr, "failed parsing period count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-c") == 0) || (strcmp(argv[0], "--channels") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.channels) != 1) {
            fprintf(stderr, "failed parsing channel count '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-r") == 0) || (strcmp(argv[0], "--rate") == 0)) {
        if (sscanf(argv[1], "%u", &cmd->config.rate) != 1) {
            fprintf(stderr, "failed parsing rate '%s'\n", argv[1]);
            return -1;
        }
    } else if ((strcmp(argv[0], "-i") == 0) || (strcmp(argv[0], "--file-type") == 0)) {
        cmd->filetype = argv[1];
    } else {
        fprintf(stderr, "unknown option '%s'\n", argv[0]);
        return -1;
    }
    return 2;
}

int cmd_parse_args(struct cmd *cmd, int argc, const char **argv)
{
    int i = 0;
    while (i < argc) {
        int j = cmd_parse_arg(cmd, argc - i, &argv[i]);
        if (j < 0){
            break;
        }
        i += j;
    }

    if ((cmd->filename != NULL)
     && (cmd->filetype == NULL)) {
        cmd->filetype = strrchr(cmd->filename, '.');
        if (cmd->filetype != NULL) {
            cmd->filetype++;
        }
    }

    return i;
}

#define ID_RIFF 0x46464952
#define ID_WAVE 0x45564157
#define ID_FMT  0x20746d66
#define ID_DATA 0x61746164

struct riff_wave_header {
    uint32_t riff_id;
    uint32_t riff_sz;
    uint32_t wave_id;
};

struct chunk_header {
    uint32_t id;
    uint32_t sz;
};

struct chunk_fmt {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
};

struct ctx {
    struct pcm *pcm;

    struct riff_wave_header wave_header;
    struct chunk_header chunk_header;
    struct chunk_fmt chunk_fmt;

    FILE *file;
};

int ctx_init(struct ctx* ctx, const struct cmd *cmd)
{
    unsigned int bits = cmd->bits;
    struct pcm_config config = cmd->config;

    if (cmd->filename == NULL) {
        fprintf(stderr, "filename not specified\n");
        return -1;
    }

    ctx->file = fopen(cmd->filename, "rb");
    if (ctx->file == NULL) {
        fprintf(stderr, "failed to open '%s'\n", cmd->filename);
        return -1;
    }

    if ((cmd->filetype != NULL) && (strcmp(cmd->filetype, "wav") == 0)) {
        if (fread(&ctx->wave_header, sizeof(ctx->wave_header), 1, ctx->file) != 1){
            fprintf(stderr, "error: '%s' does not contain a riff/wave header\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
        if ((ctx->wave_header.riff_id != ID_RIFF) ||
            (ctx->wave_header.wave_id != ID_WAVE)) {
            fprintf(stderr, "error: '%s' is not a riff/wave file\n", cmd->filename);
            fclose(ctx->file);
            return -1;
        }
	unsigned int more_chunks = 1;
        do {
            if (fread(&ctx->chunk_header, sizeof(ctx->chunk_header), 1, ctx->file) != 1){
                fprintf(stderr, "error: '%s' does not contain a data chunk\n", cmd->filename);
                fclose(ctx->file);
                return -1;
            }
            switch (ctx->chunk_header.id) {
            case ID_FMT:
                if (fread(&ctx->chunk_fmt, sizeof(ctx->chunk_fmt), 1, ctx->file) != 1){
                    fprintf(stderr, "error: '%s' has incomplete format chunk\n", cmd->filename);
                    fclose(ctx->file);
                    return -1;
                }
                /* If the format header is larger, skip the rest */
                if (ctx->chunk_header.sz > sizeof(ctx->chunk_fmt))
                    fseek(ctx->file, ctx->chunk_header.sz - sizeof(ctx->chunk_fmt), SEEK_CUR);
                break;
            case ID_DATA:
                /* Stop looking for chunks */
                more_chunks = 0;
                break;
            default:
                /* Unknown chunk, skip bytes */
                fseek(ctx->file, ctx->chunk_header.sz, SEEK_CUR);
            }
        } while (more_chunks);
        config.channels = ctx->chunk_fmt.num_channels;
        config.rate = ctx->chunk_fmt.sample_rate;
        bits = ctx->chunk_fmt.bits_per_sample;
    }

    if (bits == 8) {
        config.format = PCM_FORMAT_S8;
    } else if (bits == 16) {
        config.format = PCM_FORMAT_S16_LE;
    } else if (bits == 24) {
        config.format = PCM_FORMAT_S24_3LE;
    } else if (bits == 32) {
        config.format = PCM_FORMAT_S32_LE;
    } else {
        fprintf(stderr, "bit count '%u' not supported\n", bits);
        fclose(ctx->file);
        return -1;
    }

    ctx->pcm = pcm_open(cmd->card,
                        cmd->device,
                        cmd->flags,
                        &config);
    if (ctx->pcm == NULL) {
        fprintf(stderr, "failed to allocate memory for pcm\n");
        fclose(ctx->file);
        return -1;
    } else if (!pcm_is_ready(ctx->pcm)) {
        fprintf(stderr, "failed to open for pcm %u,%u\n", cmd->card, cmd->device);
        fclose(ctx->file);
        pcm_close(ctx->pcm);
        return -1;
    }

    return 0;
}

void ctx_free(struct ctx *ctx)
{
    if (ctx == NULL) {
        return;
    }
    if (ctx->pcm != NULL) {
        pcm_close(ctx->pcm);
    }
    if (ctx->file != NULL) {
        fclose(ctx->file);
    }
}

static int close = 0;

int play_sample(struct ctx *ctx);

void stream_close(int sig)
{
    /* allow the stream to be closed gracefully */
    signal(sig, SIG_IGN);
    close = 1;
}

void print_usage(const char *argv0)
{
    fprintf(stderr, "usage: %s file.wav [options]\n", argv0);
    fprintf(stderr, "options:\n");
    fprintf(stderr, "-D | --card   <card number>    The device to receive the audio\n");
    fprintf(stderr, "-d | --device <device number>  The card to receive the audio\n");
    fprintf(stderr, "-p | --period-size <size>      The size of the PCM's period\n");
    fprintf(stderr, "-n | --period-count <count>    The number of PCM periods\n");
    fprintf(stderr, "-i | --file-type <file-type >  The type of file to read (raw or wav)\n");
    fprintf(stderr, "-c | --channels <count>        The amount of channels per frame\n");
    fprintf(stderr, "-r | --rate <rate>             The amount of frames per second\n");
    fprintf(stderr, "-b | --bits <bit-count>        The number of bits in one sample\n");
    fprintf(stderr, "-M | --mmap                    Use memory mapped IO to play audio\n");
}


int main(int argc, const char **argv)
{
    struct cmd cmd;
    struct ctx ctx;

    if (argc < 2) {
        print_usage(argv[0]);
        return EXIT_FAILURE;
    }

    cmd_init(&cmd);
    if (cmd_parse_args(&cmd, argc - 1, &argv[1]) < 0) {
        return EXIT_FAILURE;
    }

    if (ctx_init(&ctx, &cmd) < 0) {
        return EXIT_FAILURE;
    }

    /* TODO get parameters from context */
    printf("playing '%s': %u ch, %u hz, %u bit\n",
           cmd.filename,
           cmd.config.channels,
           cmd.config.rate,
           cmd.bits);

    if (play_sample(&ctx) < 0) {
        ctx_free(&ctx);
        return EXIT_FAILURE;
    }

    ctx_free(&ctx);
    return EXIT_SUCCESS;
}

int check_param(struct pcm_params *params, unsigned int param, unsigned int value,
                 char *param_name, char *param_unit)
{
    unsigned int min;
    unsigned int max;
    int is_within_bounds = 1;

    min = pcm_params_get_min(params, param);
    if (value < min) {
        fprintf(stderr, "%s is %u%s, device only supports >= %u%s\n", param_name, value,
                param_unit, min, param_unit);
        is_within_bounds = 0;
    }

    max = pcm_params_get_max(params, param);
    if (value > max) {
        fprintf(stderr, "%s is %u%s, device only supports <= %u%s\n", param_name, value,
                param_unit, max, param_unit);
        is_within_bounds = 0;
    }

    return is_within_bounds;
}

int sample_is_playable(const struct cmd *cmd)
{
    struct pcm_params *params;
    int can_play;

    params = pcm_params_get(cmd->card, cmd->device, PCM_OUT);
    if (params == NULL) {
        fprintf(stderr, "unable to open PCM %u,%u\n", cmd->card, cmd->device);
        return 0;
    }

    can_play = check_param(params, PCM_PARAM_RATE, cmd->config.rate, "sample rate", "hz");
    can_play &= check_param(params, PCM_PARAM_CHANNELS, cmd->config.channels, "sample", " channels");
    can_play &= check_param(params, PCM_PARAM_SAMPLE_BITS, cmd->bits, "bits", " bits");
    can_play &= check_param(params, PCM_PARAM_PERIOD_SIZE, cmd->config.period_size, "period size", "");
    can_play &= check_param(params, PCM_PARAM_PERIODS, cmd->config.period_count, "period count", "");

    pcm_params_free(params);

    return can_play;
}

int play_sample(struct ctx *ctx)
{
    char *buffer;
    int size;
    int num_read;

    size = pcm_frames_to_bytes(ctx->pcm, pcm_get_buffer_size(ctx->pcm));
    buffer = malloc(size);
    if (!buffer) {
        fprintf(stderr, "unable to allocate %d bytes\n", size);
        return -1;
    }

    /* catch ctrl-c to shutdown cleanly */
    signal(SIGINT, stream_close);

    do {
        num_read = fread(buffer, 1, size, ctx->file);
        if (num_read > 0) {
		if (pcm_writei(ctx->pcm, buffer,
			pcm_bytes_to_frames(ctx->pcm, num_read)) < 0) {
                fprintf(stderr, "error playing sample\n");
                break;
            }
        }
    } while (!close && num_read > 0);

    free(buffer);
    return 0;
}
that is also interesting to me, but where is a function to play just a .wav file by it's filename out of a proprietary source code, and how will the includes and the call have to look like, e.g.

Code: Select all

#include <cstring>
#include <whatever.h>

int main() {
   string filename;

   filename="/home/pi/sound/allsystemsworkperfectly.wav";   
   
   playwavfile(filename);  // <<<<<<<<<< ????
   
   return(0);
}
edit, test code simplified

and what will the compile parameters for Geany have to look like?

g++ -Wall -l?????? -L??????? -I??????? -o "%e" "%f" .... ?????