简体   繁体   中英

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

在此处输入图片说明

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

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);

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM