Generating puretone sinewave in raspberry pi zero using C++ and ALSA api
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
Generating puretone sinewave in raspberry pi zero using C++ and ALSA api
I am trying to figure out how to generate 1 kHz tone in raspberry pi zero W board using ALSA api and C++, but I don't seem to be getting anywhere. Strangely there seems to be no beginner friendly step-by-step tutorial on generating sound using ALSA, when I thought it was the most common way of generating sound in linux.
I am using a pi zero W board with raspberry pi OS. I have a USB sound card connected to a USB hub, which is connected to the USB port on the pi zero board. I can play wav files from both VLC and command line using aplay, so there is no problem with the hardware. I can also run the small test program given here: https://forums.raspberrypi.com/viewtopic.php?t=15075, so I don't think there is any problem with my ALSA installation either.
I get a couple of warnings and then an error saying no match for operator++ for snd_pcm_format_t (which is an enum type). the error is specifically at the line
Code:
for (format = 0; format < SND_PCM_FORMAT_LAST; format++) {
(under case 'o' in the part that parses the command line arguments). I am stuck after this and not sure what to do.
Attempt 2:
I am trying out the program given here: http://equalarea.com/paul/alsa-audio.html, under "A Minimal Interrupt-Driven Program". This program compiles after some modifications, and I am manually calculating 1kHz sinewave samples at 48000 Hz sampling frequency. Problem is the generated tone doesn't sound anything like 1 kHz pure tone. I have a hunch this distortion is because the output is saturated, since it is quite loud, but I need some confirmation that this is the case. I have tried to simply reduce the amplitude of the sine function by multiplying it with 0.1, but that still generates the same distorted tone sound, only a lot smaller. I can't find the proper way to reduce the volume in ALSA api either. This is the code I am testing:
Code:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <poll.h>
#include <math.h>
#include <alsa/asoundlib.h>
snd_pcm_t *playback_handle;
// short buf[4096];
short buf[48*2]; // try 1000 Hz @48000, 48 samples @48000 = 1 ms
void sine_gen(){
// call once to generate the sine values
// if this works do this inside callback function later on
double tempSin = 0.0;
#if 1
// check if buf[] contains 2 channels interleaved
for(int i = 0; i<48*2; i+=2){
tempSin = sin(2.0*M_PI*1000*i/48000);
buf[i] = (short)(tempSin * (pow(2.0, 15)- 1);)
buf[i+1] = buf[i];
}
#else
// check if buf[] contains single channel data
for(int i = 0; i<48*2; i++){
tempSin = sin(2*M_PI*1000*i/48000);
buf[i] = (short)(tempSin * (pow(2.0, 15)-1) );
}
#endif
}
void SetAlsaMasterVolume(long volume)
{
long min, max;
snd_mixer_t *handle;
snd_mixer_selem_id_t *sid;
const char *card = "default";
const char *selem_name = "Master";
snd_mixer_open(&handle, 0);
snd_mixer_attach(handle, card);
snd_mixer_selem_register(handle, NULL, NULL);
snd_mixer_load(handle);
snd_mixer_selem_id_alloca(&sid);
snd_mixer_selem_id_set_index(sid, 0);
snd_mixer_selem_id_set_name(sid, selem_name);
snd_mixer_elem_t* elem = snd_mixer_find_selem(handle, sid);
snd_mixer_selem_get_playback_volume_range(elem, &min, &max);
snd_mixer_selem_set_playback_volume_all(elem, volume * max / 100);
snd_mixer_close(handle);
}
int
playback_callback (snd_pcm_sframes_t nframes)
{
int err;
// printf ("playback callback called with %u frames\n", nframes);
/* ... fill buf with data ... */
// if this part is empty, buf[] should have been filled correctly inside sine_gen()
if ((err = snd_pcm_writei (playback_handle, buf, nframes)) < 0) {
fprintf (stderr, "write failed (%s)\n", snd_strerror (err));
}
return err;
}
int main (int argc, char *argv[])
{
snd_pcm_hw_params_t *hw_params;
snd_pcm_sw_params_t *sw_params;
snd_pcm_sframes_t frames_to_deliver;
int nfds;
int err;
struct pollfd *pfds;
sine_gen(); // call this once to generate sinewave
if ((err = snd_pcm_open (&playback_handle, argv[1], SND_PCM_STREAM_PLAYBACK, 0)) < 0) {
fprintf (stderr, "cannot open audio device %s (%s)\n",
argv[1],
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_malloc (&hw_params)) < 0) {
fprintf (stderr, "cannot allocate hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_any (playback_handle, hw_params)) < 0) {
fprintf (stderr, "cannot initialize hardware parameter structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_access (playback_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
fprintf (stderr, "cannot set access type (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_format (playback_handle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) {
fprintf (stderr, "cannot set sample format (%s)\n",
snd_strerror (err));
exit (1);
}
unsigned int f_s = 48000;
// if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, 44100, 0)) < 0) { // causes segmentation fault; see // https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m___h_w___params.html#ga6014e0e1ec7934f8c745290e83e59199
if ((err = snd_pcm_hw_params_set_rate_near (playback_handle, hw_params, &f_s, 0)) < 0) {
fprintf (stderr, "cannot set sample rate (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params_set_channels (playback_handle, hw_params, 2)) < 0) {
fprintf (stderr, "cannot set channel count (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_hw_params (playback_handle, hw_params)) < 0) {
fprintf (stderr, "cannot set parameters (%s)\n",
snd_strerror (err));
exit (1);
}
snd_pcm_hw_params_free (hw_params);
/* tell ALSA to wake us up whenever 4096 or more frames
of playback data can be delivered. Also, tell
ALSA that we'll start the device ourselves.
*/
if ((err = snd_pcm_sw_params_malloc (&sw_params)) < 0) {
fprintf (stderr, "cannot allocate software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_current (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot initialize software parameters structure (%s)\n",
snd_strerror (err));
exit (1);
}
// if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 4096)) < 0) { // change this as per the size of the buffer used
if ((err = snd_pcm_sw_params_set_avail_min (playback_handle, sw_params, 48*2)) < 0) {
fprintf (stderr, "cannot set minimum available count (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params_set_start_threshold (playback_handle, sw_params, 0U)) < 0) {
fprintf (stderr, "cannot set start mode (%s)\n",
snd_strerror (err));
exit (1);
}
if ((err = snd_pcm_sw_params (playback_handle, sw_params)) < 0) {
fprintf (stderr, "cannot set software parameters (%s)\n",
snd_strerror (err));
exit (1);
}
/* the interface will interrupt the kernel every 4096 frames, and ALSA
will wake up this program very soon after that.
*/
if ((err = snd_pcm_prepare (playback_handle)) < 0) {
fprintf (stderr, "cannot prepare audio interface for use (%s)\n",
snd_strerror (err));
exit (1);
}
// SetAlsaMasterVolume(0); // attempt changing volume // didn't make any difference
while (1) {
/* wait till the interface is ready for data, or 1 second
has elapsed.
*/
if ((err = snd_pcm_wait (playback_handle, 1000)) < 0) {
fprintf (stderr, "poll failed (%s)\n", strerror (errno));
break;
}
/* find out how much space is available for playback data */
if ((frames_to_deliver = snd_pcm_avail_update (playback_handle)) < 0) {
if (frames_to_deliver == -EPIPE) {
fprintf (stderr, "an xrun occured\n");
break;
} else {
fprintf (stderr, "unknown ALSA avail update return value (%d)\n",
frames_to_deliver);
break;
}
}
// frames_to_deliver = frames_to_deliver > 4096 ? 4096 : frames_to_deliver;
frames_to_deliver = frames_to_deliver > (48*2) ? (48*2) : frames_to_deliver;
/* deliver the data */
if (playback_callback (frames_to_deliver) != frames_to_deliver) {
fprintf (stderr, "playback callback failed\n");
break;
}
}
snd_pcm_close (playback_handle);
exit (0);
return 0;
}
Can someone please point to me what I am doing wrong in attempts 1 and 2 above?
Issue one is because you're trying to compile C code as C++.
"format" is a snd_pcm_format_t, which in turn is an enum. Unlike C, where enums are just type int, in C++ they are their own unique class type, and that class won't have a ++ operator unless you define one with an operator overload.
Your best bet is to just write/compile it as C, but if you insist on using C++ rather than C then you can either implement a operator++ for the enum class, or try using static_cast<int>() in the for loop. Either way, it's going to be a little ugly, and you're possibly going to hit further issues.
Can't help you with issue two. PCM stream sign waves not sounding "right" is a little out of my comfort zone.
Compiling as C++ turned out to be the issue, if I change the filename to main.c it compiles without any errors. But I need to use the audio interfacing features in this program later on in a C++ application so I have to do this as C++. Thanks for your help, I'll find some other way to do this instead.
You're welcome. I guess you have a little bit of C to C++ porting to do then.
The way they find the format value in that '-o' switch section is a bit weird anyway. I can't see why they didn't just use something like:
Code:
format = snd_pcm_format_value(optarg);
if (format == SND_PCM_FORMAT_UNKNOWN) {
printf("Unknown format, setting to default S16\n");
format = SND_PCM_FORMAT_S16;
}
Last edited by GazL; 10-14-2021 at 04:24 AM.
Reason: removed endianness from example.
short buf[48*2]; // try 1000 Hz @48000, 48 samples @48000 = 1 ms
My experience with networked music programs (which try to have the minimum possible latency) is that most soundcards won't be able to work well with a buffer size of 1ms (though I'm not entirely sure if the buffer size you have here really corresponds to the option in those programs). Have you tried with a larger buffer? Maybe aim for around 10ms to start.
Quote:
Originally Posted by GazL
The way they find the format value in that '-o' switch section is a bit weird anyway. I can't see why they didn't just use something like:
My experience with networked music programs (which try to have the minimum possible latency) is that most soundcards won't be able to work well with a buffer size of 1ms (though I'm not entirely sure if the buffer size you have here really corresponds to the option in those programs). Have you tried with a larger buffer? Maybe aim for around 10ms to start.
That is my theory as well. Audio frames typically have 4-8K samples.
Another potential problem is that the ALSA hardware calls will fail if PulseAudio is in use. The OP needs to avoid those calls or switch to the PulseAudio API.
Ed
mahaju, if you want my €0.02́¢ worth, that's a dumb idea.
I'm an Electronics hardware guy, and sinewaves happen naturally around inductors, capacitors, valves and Analogue circuitry. Computers are not Analogue, they are Digital; it's the difference between rounded edges (Sine waves) and Square ones(Digital). It can be done, but not easily. You are using a slow, intellectually challenged cpu with no (sorely needed) maths processor, low memory and probably using linux, which is not a Real Time Operating System (RTOS). In other words, you have stacked the deck against yourself. It's also totally unnecessary work when you can buy wave generator ICs which do the job for cheap.
Perhaps that example dates back to a time when it wasn't. *shrug*
Or programming defensively against a time in the future when it won't be?
Quote:
Originally Posted by EdGr
Another potential problem is that the ALSA hardware calls will fail if PulseAudio is in use.
OP reported getting some (distorted) sound out, so it's probably not that.
Quote:
Originally Posted by business_kid
Computers are not Analogue, they are Digital; it's the difference between rounded edges (Sine waves) and Square ones(Digital). It can be done, but not easily.
Uh, do you really think any human would be able to hear the difference between a perfectly rounded edge sine wave, versus the approximation produced by the standard C library sin() function?
Uh, do you really think any human would be able to hear the difference between a perfectly rounded edge sine wave, versus the approximation produced by the standard C library sin() function?
Having come through the teeth of the "Valve sound" vs. "Transistor Sound" argument, I gave up trying to divine what people would hear the difference between decades ago, and never got into serious hi-fi.The valve sound lasted longer here than in most places. For those who never went there, valves added about 10%THD and some other forms of distortion as well, but the sound was preferred by many. Valves lost out economically, and servicing costs were much higher. They also needed various power supplies, power resistors and transformers, meaning you had to build big and plug it in. Transistorised radios could be made to fit in your pocket with batteries.
I had a pi 0. There's no FPU, and it's slow. I wouldn't write anything for it. The sound of a 1khz sine wave sucks in any case. The OP will do what he likes. I gave my opinion.
Last edited by business_kid; 10-15-2021 at 05:47 AM.
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.