Jacquelyn.Marquardt
Jacquelyn.Marquardt

Reputation: 630

Strange sine wave on output of an alsa linux c program

I'm studying alsa programming on ubuntu.

I'm trying to output a sine wave to line-out of my laptop soundcard, and then redirecting to line-in (microphone) via an audio cable.

  ___LINE_IN(microphone)\
 /                       \
|                         \ _____________
|                          |soundcard-pc |
cable                     /
|                        /
 \ ___LINE_OUT(speakers)/

I'm using this code

#include<stdio.h>
#include<stdlib.h>
#include<alsa/asoundlib.h>
#include<math.h>

#define SIZEBUF 2048

int main(void)
{
    int i;
    int err;
    double x;
    double cost;
    double frequency=500;
    unsigned int rate=44100;
    short buf[SIZEBUF];
    snd_pcm_t *phandle;
    snd_pcm_hw_params_t *hw_params;

    snd_pcm_open(&phandle, "default", SND_PCM_STREAM_PLAYBACK, 0);
    snd_pcm_hw_params_malloc(&hw_params);
    snd_pcm_hw_params_any(phandle, hw_params);

    if ((err = snd_pcm_hw_params_set_access(phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) {
        printf("Cannot set access type.\n");
        exit(1);
    }
    snd_pcm_hw_params_set_format(phandle, hw_params, SND_PCM_FORMAT_S16_LE);
    snd_pcm_hw_params_set_rate_near(phandle, hw_params, &rate, 0);
    snd_pcm_hw_params_set_channels(phandle, hw_params, 1);
    snd_pcm_hw_params(phandle, hw_params);
    snd_pcm_hw_params_free(hw_params);
    snd_pcm_prepare(phandle);

    cost = 2.0 * M_PI * frequency / (double)rate;

    printf("cost=%f.\n", cost);
    for (i = 1 ; i < SIZEBUF ; i++) {
        x      = sin(i * cost);
        buf[i] = (short)(32767 * x + 32768);
    }

    for (i = 0 ; i < 50 ; i++) {
        snd_pcm_writei(phandle, buf, SIZEBUF);
    }
    snd_pcm_close(phandle);

    exit(0);
}

I'm using audacity to see the wave, but it appears strange, like in this image

enter image description here

It seems not to have the behaviour of a sine wave. Why?

Upvotes: 3

Views: 1999

Answers (2)

user1157391
user1157391

Reputation:

To convert float samples to SND_PCM_FORMAT_S16_LE do:

#include <endian.h>
#include <math.h>
#include <stdint.h>

// assume -1.0 <= x <= 1.0                                                      
int16_t float_to_s16le (float x)
{
    int16_t s16 = trunc (x * 32767); // rounds toward zero                      
    return htole16 (s16);
}

Some remarks:

  • You are converting to a signed number, not an unsigned one. Furthermore it is important to avoid clipping or wrap-around of large values.
  • You need a type with a specific number of bits. Hence use int16_t, not short.
  • Normally you would choose host endianness (SND_PCM_FORMAT_S16). But since you explicitly chose Little Endian you need to make sure that this is what you produce (with htole16 or an equivalent).

Upvotes: 2

CL.
CL.

Reputation: 180172

You are using samples of type short, and have correctly declared them as S16_LE. However, your code then computes the values as if they were unsigned:

buf[i] = (short)(32767 * x + 32768);

You do not need an offset; with signed samples, zero actually is zero. Use this:

buf[i] = (short)(32767 * x);

Upvotes: 3

Related Questions