I have a program that receives messages via a socket and starts or stops playing a certain sound file depending on the message. In order for the "stop" message to work, I need the sound to play from a separate thread. My solution is to play the sound using alsa from a function I invoke using pthread_create(), and upon receiving a stop message I end the thread using pthread_cancel(). The function that plays the sound is called play_sound(void *args);
Here's what works:
struct args *args;
args->fp = fopen("path/to/soundfile.wav", "r");
args->volume = 1;
play_sound((void *) args);
but as soon as I try to run the function from within a new thread, I get no sound and 100% CPU usage on both my threads:
struct args *args;
int sound_thread;
args->fp = fopen("path/to/soundfile.wav", "r");
args->volume = 1;
pthread_create(&sound_thread, NULL, (void *) play_sound, (void *) args);
I have no idea where to even begin troubleshooting.
My code looks as follows:
#include <alsa/asoundlib.h>
#include <alsa/mixer.h>
#include <stdbool.h>
#include <pthread.h>
#include "server.h"
#include "sound.h"
//#include "log.h"
int sound_thread;
struct args {
FILE *fp;
float volume;
};
void init_sound ()
{
sound_thread = -1;
}
void stop_sound ()
{
if (sound_thread != -1) {
pthread_cancel(sound_thread);
keep_playing = false;
sound_thread = -1;
}
}
void dispatch_sound (FILE *fp, float volume)
{
// this function serves to create a new thread for the
// sound to be played from. This is what's giving me
// headaches.
if (sound_thread != -1) {
stop_sound();
}
struct args *args = (struct args *) malloc(sizeof(struct args));
args->fp = fp;
args->volume = volume;
if (pthread_create(&sound_thread, NULL, (void *) play_sound, args) != 0)
sound_thread = -1;
}
}
bool play_sound (void *args)
{
// This function actually plays the sound using functions
// from the alsa lib. it works when invoked regularly without
// threading.
keep_playing = true;
FILE *fp;
int volume;
bool success;
unsigned int samplerate;
int bufsz;
char *buf;
snd_pcm_t *pcm;
snd_pcm_hw_params_t *params;
snd_pcm_uframes_t frames;
samplerate = SAMPLERATE;
fp = ((struct args *) args)->fp;
volume = ((struct args *) args)->volume;
// volume is not actually used right now, since I took out
// the function that sets the volume before playing the
// audio in order to make it easier to pinpoint the issue.
if (snd_pcm_open(&pcm, PCM_DEVICE, SND_PCM_STREAM_PLAYBACK, 0) < 0) {
success = false;
}
snd_pcm_hw_params_alloca(¶ms);
snd_pcm_hw_params_any(pcm, params);
if (snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) {
success = false;
}
if (snd_pcm_hw_params_set_format(pcm, params, SND_PCM_FORMAT_S16_LE) < 0) {
success = false;
}
if (snd_pcm_hw_params_set_channels(pcm, params, CHANNELS) < 0) {
success = false;
}
if (snd_pcm_hw_params_set_rate_near(pcm, params, &samplerate, 0) < 0) {
success = false;
}´
if (snd_pcm_hw_params(pcm, params) < 0) {
success = false;
}
snd_pcm_hw_params_get_period_size(params, &frames, 0);
bufsz = frames * CHANNELS * SAMPLE_SIZE;
buf = (char *) malloc(bufsz);
while (keep_playing) {
while (fread(buf, bufsz, 1, fp) != 0 && keep_playing) {
int err;
if ((err = snd_pcm_writei(pcm, buf, frames)) == -EPIPE) {
snd_pcm_prepare(pcm);
}
}
rewind(fp);
}
snd_pcm_drain(pcm);
snd_pcm_close(pcm);
free(buf);
return success;
}
From the man page of pthread_cancel:
In your while(keep_playing) loop, you aren't yielding the thread enough to handle the cancel signal; in your main thread; you aren't waiting for the result of the cancel request, ergo both threads hog the cpu.
A small delay before you restart playing the sound and pthread_join() after you call pthread_cancel should fix your problem.