Skip to content

Instantly share code, notes, and snippets.

@ghedo
Last active March 2, 2024 08:47
Show Gist options
  • Star 90 You must be signed in to star a gist
  • Fork 20 You must be signed in to fork a gist
  • Save ghedo/963382 to your computer and use it in GitHub Desktop.
Save ghedo/963382 to your computer and use it in GitHub Desktop.
Simple sound playback using ALSA API and libasound
/*
* Simple sound playback using ALSA API and libasound.
*
* Compile:
* $ cc -o play sound_playback.c -lasound
*
* Usage:
* $ ./play <sample_rate> <channels> <seconds> < <file>
*
* Examples:
* $ ./play 44100 2 5 < /dev/urandom
* $ ./play 22050 1 8 < /path/to/file.wav
*
* Copyright (C) 2009 Alessandro Ghedini <alessandro@ghedini.me>
* --------------------------------------------------------------
* "THE BEER-WARE LICENSE" (Revision 42):
* Alessandro Ghedini wrote this file. As long as you retain this
* notice you can do whatever you want with this stuff. If we
* meet some day, and you think this stuff is worth it, you can
* buy me a beer in return.
* --------------------------------------------------------------
*/
#include <alsa/asoundlib.h>
#include <stdio.h>
#define PCM_DEVICE "default"
int main(int argc, char **argv) {
unsigned int pcm, tmp, dir;
int rate, channels, seconds;
snd_pcm_t *pcm_handle;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
char *buff;
int buff_size, loops;
if (argc < 4) {
printf("Usage: %s <sample_rate> <channels> <seconds>\n",
argv[0]);
return -1;
}
rate = atoi(argv[1]);
channels = atoi(argv[2]);
seconds = atoi(argv[3]);
/* Open the PCM device in playback mode */
if (pcm = snd_pcm_open(&pcm_handle, PCM_DEVICE,
SND_PCM_STREAM_PLAYBACK, 0) < 0)
printf("ERROR: Can't open \"%s\" PCM device. %s\n",
PCM_DEVICE, snd_strerror(pcm));
/* Allocate parameters object and fill it with default values*/
snd_pcm_hw_params_alloca(&params);
snd_pcm_hw_params_any(pcm_handle, params);
/* Set parameters */
if (pcm = snd_pcm_hw_params_set_access(pcm_handle, params,
SND_PCM_ACCESS_RW_INTERLEAVED) < 0)
printf("ERROR: Can't set interleaved mode. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_format(pcm_handle, params,
SND_PCM_FORMAT_S16_LE) < 0)
printf("ERROR: Can't set format. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_channels(pcm_handle, params, channels) < 0)
printf("ERROR: Can't set channels number. %s\n", snd_strerror(pcm));
if (pcm = snd_pcm_hw_params_set_rate_near(pcm_handle, params, &rate, 0) < 0)
printf("ERROR: Can't set rate. %s\n", snd_strerror(pcm));
/* Write parameters */
if (pcm = snd_pcm_hw_params(pcm_handle, params) < 0)
printf("ERROR: Can't set harware parameters. %s\n", snd_strerror(pcm));
/* Resume information */
printf("PCM name: '%s'\n", snd_pcm_name(pcm_handle));
printf("PCM state: %s\n", snd_pcm_state_name(snd_pcm_state(pcm_handle)));
snd_pcm_hw_params_get_channels(params, &tmp);
printf("channels: %i ", tmp);
if (tmp == 1)
printf("(mono)\n");
else if (tmp == 2)
printf("(stereo)\n");
snd_pcm_hw_params_get_rate(params, &tmp, 0);
printf("rate: %d bps\n", tmp);
printf("seconds: %d\n", seconds);
/* Allocate buffer to hold single period */
snd_pcm_hw_params_get_period_size(params, &frames, 0);
buff_size = frames * channels * 2 /* 2 -> sample size */;
buff = (char *) malloc(buff_size);
snd_pcm_hw_params_get_period_time(params, &tmp, NULL);
for (loops = (seconds * 1000000) / tmp; loops > 0; loops--) {
if (pcm = read(0, buff, buff_size) == 0) {
printf("Early end of file.\n");
return 0;
}
if (pcm = snd_pcm_writei(pcm_handle, buff, frames) == -EPIPE) {
printf("XRUN.\n");
snd_pcm_prepare(pcm_handle);
} else if (pcm < 0) {
printf("ERROR. Can't write to PCM device. %s\n", snd_strerror(pcm));
}
}
snd_pcm_drain(pcm_handle);
snd_pcm_close(pcm_handle);
free(buff);
return 0;
}
@mark222
Copy link

mark222 commented Jan 3, 2015

Hi, just came across this as I am trying to learn ALSA programming. This all makes sense to me except the reading of the WAV file input... it does not seem to read the WAV headers, it just reads starting from the first byte and interprets them as sound samples. But a WAV file has metadata before the sample data starts (e.g. "RIFF...WAVE..., etc).

Copy link

ghost commented Feb 12, 2015

Yeah, WAV-file has a header which contains metadata, the player should skip this section by fseek(fp,44, SEEK_SET);

@heatblazer
Copy link

You can add reflexive structure for a wav to handle and cast the 44 bytes to it so you`ll fill it with sample rate, fmt, RIFF, data, etc.

@Sorebit
Copy link

Sorebit commented Aug 26, 2016

Is this actually the same as aplay?

@tvlooy
Copy link

tvlooy commented Sep 13, 2016

having a small simple example when you start learning something is priceless. Thanks for sharing this

Copy link

ghost commented Sep 3, 2017

according to the link below:

WAVE files often have information chunks that precede or follow the sound data (data chunk). Some programs (naively) assume that for PCM data, the preamble in the file header is exactly 44 bytes long (as in the table above) and that the rest of the file contains sound data.
This is not a safe assumption.

and taking into consideration that there are a few format (like ~128) code for the RIFF file and each can have a different fmt chunk, with different sizes, then making the assumption that the data chunk will start from 44 it is not safe.

link http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html

@etale-cohomology
Copy link

This is so beautiful! Thank you!!!! Who said ALSA programming need be hard?

@busterb
Copy link

busterb commented Oct 24, 2018

Thanks!

@minhhieuec
Copy link

Could you give me link to download wav audio for test this code?

Thanks!

@gorluxgit
Copy link

I'd like to echo other positive comments. I've tried a few other example programs and got them to compile but not successfully play .wav files. Got this one playing sound in just a few minutes.
When compiling with g++ got an error about a type mismatch for the "rate" variable. This can be fixed by editing just under main and substituting:
unsigned int rate;
then
int channels, seconds;
instead of:
int rate, channels, seconds;

My environment is Raspberry Pi B Rev 2. running Raspbian Jessie.

You can get the necessary parameters (sample_rate, channels) for your wave file (say waveFile.wav) by running aplay waveFile.wav.

@Rime2509
Copy link

I tried using this code it works for /dev/urandom but it doesn't work for a wave file.
I tried using fseek(fp,44, SEEK_SET); but it returns core dumped.
I am just a beginner at programming with C and I might have misused the fseek if anyone can tell me how to use it or how can I make it work for wave file, thanks.

@JacekRuzyczka
Copy link

Question: Will this code run if the target device is a mixer (dmix)?

@maxmbed
Copy link

maxmbed commented Jul 21, 2020

Question: Will this code run if the target device is a mixer (dmix)?

Yep, it seems helpuf to use with Soundpipe.

Can confirm the code of OP is a quite good way to use alsa pcm.

@alpereira7
Copy link

I dont' like the idea of pcm being an unsigned int when later, we have pcm = snd_pcm_open(....
snd_pcm_open can return a negative number, so we should have int pcm;

@pavanreddy13
Copy link

This worked, This is a great start for the beginners.
This is the first working program i found online.

Please share a program to capture the audio as well.
Thank you.

@pavanreddy13
Copy link

Where in the program the data read from the .wav file ?

@maxmbed
Copy link

maxmbed commented Oct 8, 2020

Where in the program the data read from the .wav file ?

It reads from the standard input stream (stdint = 0):

		if (pcm = read(0, buff, buff_size) == 0) {
			printf("Early end of file.\n");
			return 0;
		}

Where stdint is the content of .wav file you pass using < character in command line (./play 22050 1 8 < /path/to/file.wav)

@happytong
Copy link

happytong commented Oct 14, 2020

did anyone encounter the same problem as me? the playback quit after 3-4s with "Early end of file." Tried different files:
//./playwave 32000 1 20 /audio/11_en.wav...loops=1932/2500, tmp=8000, quit after 4s
//./playwave 16000 1 20 /audio/test1.raw...loops=2007/2500, tmp=8000, quit after 3s
I modified a little bit to allow taking argv[4] as filename.

Ps. Remove 'return 0' will finish the broadcast

@tachang
Copy link

tachang commented Oct 29, 2020

Thank you for this.

		if (pcm = snd_pcm_writei(pcm_handle, buff, frames) == -EPIPE) {
			printf("XRUN.\n");

In this code won't it always be a buffer overrun if you are trying to write frames out and can't?

@happytong
Copy link

happytong commented Nov 2, 2020

The application can run in command line, but when added to startup script (eg. rc.local) so after power on it can run automatically, it crashes. The core dump file show it crashed at following:

root@linaro-ubuntu-desktop:/test# gdb ./playback /opt/core.playback.6238.1604316560.6
GNU gdb (Ubuntu/Linaro 7.3-0ubuntu2) 7.3-2011.08
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabi".
For bug reporting instructions, please see:
<http://bugs.launchpad.net/gdb-linaro/>...
Reading symbols from /test/playback...(no debugging symbols found)...done.
[New LWP 6238]
[Thread debugging using libthread_db enabled]
Core was generated by `/test/playback'.
Program terminated with signal 6, Aborted.
#0  0x2ad93ed6 in ?? () from /lib/arm-linux-gnueabi/libc.so.6
(gdb) bt full
#0  0x2ad93ed6 in ?? () from /lib/arm-linux-gnueabi/libc.so.6
No symbol table info available.
#1  0x2ada20da in raise () from /lib/arm-linux-gnueabi/libc.so.6
No symbol table info available.
#2  0x2ada4506 in abort () from /lib/arm-linux-gnueabi/libc.so.6
No symbol table info available.
#3  0x2ad9d1ec in __assert_fail () from /lib/arm-linux-gnueabi/libc.so.6
No symbol table info available.
#4  0x2ad1fb72 in snd_pcm_hw_refine () from /usr/lib/arm-linux-gnueabi/libasound.so.2
No symbol table info available.
#5  0x00008cd4 in main ()
No symbol table info available.

Note snd_pcm_hw_refine() is not directly called by this application.

I have hardcoded all parameters to 32000hz / 1chn / 30s and "/opt/test1.wav". The read changed to

	int fd = open("/opt/test1.wav", O_RDONLY);   ///added
	for (loops = (seconds * 1000000) / tmp; loops > 0; loops--) {

		if (pcm = read(fd, buff, buff_size) == 0) {   ///changed to fd from 0
			printf("Early end of file.\n");
			return 0;
		}

Further tracing found it crashed at snd_pcm_hw_params_any() after snd_pcm_open(), snd_pcm_hw_params_alloca().

Note: The application only crashes after auto-run from power on; if run the script in putty (command line), no problem.

Anyone encounter this problem? How have you solved it? Thank you.

@happytong
Copy link

happytong commented Nov 4, 2020

Got another issue, XRUN in alternative cycle (this would happen after the playback from 2nd time onwards):
2020-11-02 21:03:42 [audio]XRUN:0,1 --- cycle 1, 0=previous is ok
2020-11-02 21:03:42 [audio]XRUN:0,3 --- cycle 3, 0=previous is ok
2020-11-02 21:03:42 [audio]XRUN:0,5 --- cycle 5, 0=previous is ok
2020-11-02 21:03:42 [audio]XRUN:0,7
2020-11-02 21:03:42 [audio]XRUN:0,9

The effect of XRUN is heard either partially or no sound.

@nonetrix
Copy link

Looks easier than I thought

@rsingh2083
Copy link

rsingh2083 commented Nov 11, 2021

./play 44100 2 2 < hello.wav
Just gives out a very short click sound and then Im getting early end of file. Can anyone help ?

pi@raspberrypi:~/tinyalsa $ ./play 44100 2 2 < hello.wav 
PCM name: 'default'
PCM state: PREPARED
channels: 2 (stereo)
rate: 44100 bps
seconds: 2
Early end of file.

@FlexW
Copy link

FlexW commented Jan 24, 2022

@rsingh2083 The click sound is probably because it plays the metadata of the wav file. In a real player you would need to skip the header of the wav file.

@Qwinth
Copy link

Qwinth commented Aug 23, 2022

How to play 24-bit audio?

@HamzaMehboob
Copy link

HamzaMehboob commented Sep 28, 2022

How to play 24-bit audio?

You can use SND_PCM_FORMAT_S24_LE instead of SND_PCM_FORMAT_S16_LE

@hackerb9
Copy link

It's nice to see an attempt at a minimal ALSA program. There are some bugs that should probably be cleaned up, though. Examples:

  • &rate should be just rate,

  • variable pcm should be called err as in the ALSA docs,

  • the audio file should be played to the end instead of for a certain number of seconds,

  • buff_size should not presume sample width is 2 bytes. For calculating buff_size, one can use _physical_width

      snd_pcm_format_t sampfmt;
      snd_pcm_hw_params_get_format(params, &sampfmt);
      printf("sample format: %s\n", snd_pcm_format_description(sampfmt));
    
      snd_pcm_format_t sampwidth = snd_pcm_format_physical_width(sampfmt);
      printf("sample width: %d bits\n", sampwidth);

@dedobbin
Copy link

dedobbin commented Nov 26, 2023

Hey! Thanks for sharing. It does run flawless but i'm running into memory corruption when wrapping logic into functions. I probably am thinking about it wrong but i don't see a problem with my code, but i want to rule out ALSA is doing something odd. If anyone could take a look it would be greatly appreciated.

https://gist.github.com/dedobbin/29adb1dae10932b4a88721bb00a1fc45

edit: oh, already found it. very silly, i used snd_pcm_hw_params_alloca which allocated on the stack obviously
should have used snd_pcm_hw_params_malloc

@prajoshpremdas
Copy link

I have modified the code to play wav files instead of stdin, I find that the wav files are played at a faster tempo. The problem is described here https://stackoverflow.com/questions/77920231/wav-files-are-played-by-alsa-at-a-faster-tempo-is-there-a-way-to-fix-the-tempo

Could anyone kindly point me my mistake?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment