You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NanoPi NEO使用ALSA录音时出现杂音或尖锐噪声问题求助

ALSA单声道转立体声后静态杂音的问题修复

问题背景

我使用NanoPi NEO搭配UGO USB声卡,将Linuxjournal上的ALSA示例代码编译为record和play工具。直接执行./record | ./play时背景出现尖锐噪声,排查后发现是单声道麦克风输出与立体声耳机不匹配导致的。添加单声道转立体声的代码后,尖锐噪声消失,但出现了静态杂音。arecord录音正常,可排除硬件问题。

提供的修改后代码

Playback_Listing3

/*
This example reads standard from input and writes
to the default PCM device for 5 seconds of data.
*/

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>

int main() {
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;

  /* Open PCM device for playback. */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_PLAYBACK, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* Set the desired hardware parameters. */

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params,
                      SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);

  /* Two channels (stereo) */
  snd_pcm_hw_params_set_channels(handle, params, 2);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params,
                                  &val, &dir);

  /* Set period size to 32 frames. */
  frames = 32;
  snd_pcm_hw_params_set_period_size_near(handle,
                              params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params, &frames,
                                    &dir);
  size = frames * 4; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);

  /* We want to loop for 5 seconds */
  snd_pcm_hw_params_get_period_time(params,
                                    &val, &dir);
  /* 5 seconds in microseconds divided by
   * period time */
  loops = 5000000 / val;

  while (loops > 0) {
    loops--;
    rc = read(0, buffer, size);
    if (rc == 0) {
      fprintf(stderr, "end of file on input\n");
      break;
    } else if (rc != size) {
      fprintf(stderr,
              "short read: read %d bytes\n", rc);
    }
    rc = snd_pcm_writei(handle, buffer, frames);
    if (rc == -EPIPE) {
      /* EPIPE means underrun */
      fprintf(stderr, "underrun occurred\n");
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from writei: %s\n",
              snd_strerror(rc));
    }  else if (rc != (int)frames) {
      fprintf(stderr,
              "short write, write %d frames\n", rc);
    }
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}

Record_Listing4

/*
This example reads from the default PCM device
and writes to standard output for 5 seconds of data.
*/

/* Use the newer ALSA API */
#define ALSA_PCM_NEW_HW_PARAMS_API

#include <alsa/asoundlib.h>

void interleave(const char * in_L,     // mono input buffer (left channel)
                const char * in_R,     // mono input buffer (right channel)
                char * out,            // stereo output buffer
                const size_t num_samples)  // number of samples
{
    for (size_t i = 0; i < num_samples; ++i)
    {
        out[i * 4] = in_L[i];
        out[i * 4 + 1] = in_L[i+1];
        out[i * 4 + 2] = 0;
        out[i * 4 + 3] = 0;
    }
}

int main() {
  long loops;
  int rc;
  int size;
  snd_pcm_t *handle;
  snd_pcm_hw_params_t *params;
  unsigned int val;
  int dir;
  snd_pcm_uframes_t frames;
  char *buffer;
  char *stereo;

  /* Open PCM device for recording (capture). */
  rc = snd_pcm_open(&handle, "default",
                    SND_PCM_STREAM_CAPTURE, 0);
  if (rc < 0) {
    fprintf(stderr,
            "unable to open pcm device: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Allocate a hardware parameters object. */
  snd_pcm_hw_params_alloca(&params);

  /* Fill it in with default values. */
  snd_pcm_hw_params_any(handle, params);

  /* Set the desired hardware parameters. */

  /* Interleaved mode */
  snd_pcm_hw_params_set_access(handle, params,
                      SND_PCM_ACCESS_RW_INTERLEAVED);

  /* Signed 16-bit little-endian format */
  snd_pcm_hw_params_set_format(handle, params,
                              SND_PCM_FORMAT_S16_LE);

  /* Two channels (stereo) */
  snd_pcm_hw_params_set_channels(handle, params, 1);

  /* 44100 bits/second sampling rate (CD quality) */
  val = 44100;
  snd_pcm_hw_params_set_rate_near(handle, params, &val, &dir);

  /* Set period size to 32 frames. */
  frames = 32;
  snd_pcm_hw_params_set_period_size_near(handle, params, &frames, &dir);

  /* Write the parameters to the driver */
  rc = snd_pcm_hw_params(handle, params);
  if (rc < 0) {
    fprintf(stderr,
            "unable to set hw parameters: %s\n",
            snd_strerror(rc));
    exit(1);
  }

  /* Use a buffer large enough to hold one period */
  snd_pcm_hw_params_get_period_size(params,
                                      &frames, &dir);
  size = frames * 2; /* 2 bytes/sample, 2 channels */
  buffer = (char *) malloc(size);
  stereo = (char *) malloc(size*2);

  /* We want to loop for 5 seconds */
  snd_pcm_hw_params_get_period_time(params,
                                         &val, &dir);
  loops = 5000000 / val;

  while (loops > 0) {
    loops--;
    rc = snd_pcm_readi(handle, buffer, frames);
    if (rc == -EPIPE) {
      /* EPIPE means overrun */
      fprintf(stderr, "overrun occurred\n");
      snd_pcm_prepare(handle);
    } else if (rc < 0) {
      fprintf(stderr,
              "error from read: %s\n",
              snd_strerror(rc));
    } else if (rc != (int)frames) {
      fprintf(stderr, "short read, read %d frames\n", rc);
    }
    interleave(buffer, buffer, stereo, frames);
    rc = write(1, stereo, size*2);
    if (rc != size*2)
      fprintf(stderr,
              "short write: wrote %d bytes\n", rc);
  }

  snd_pcm_drain(handle);
  snd_pcm_close(handle);
  free(buffer);

  return 0;
}

问题根源:Interleave函数的内存访问错误

你的interleave函数存在两个关键错误:

  1. 字节索引逻辑错误:每个单声道样本是16位(2字节),原代码用in_L[i]和in_L[i+1]访问样本,会导致相邻样本的字节被错误拼接,破坏音频数据结构。
  2. 内存越界:当i循环到最后一个样本时,i+1会超出当前缓冲区的内存范围,读取到无效数据,产生静态杂音。

修复方案

1. 修正Interleave函数

重新实现单声道转立体声的逻辑,正确处理16位样本的字节结构:

void interleave(const char *in_mono, char *out_stereo, const size_t num_frames)
{
    // 每个单声道帧占2字节,转立体声后每帧占4字节(左+右)
    for (size_t i = 0; i < num_frames; ++i) {
        // 左声道:复制原单声道样本的2个字节
        out_stereo[4*i] = in_mono[2*i];
        out_stereo[4*i + 1] = in_mono[2*i + 1];
        // 右声道:可选择复制左声道(立体声)或填0(单声道转双声道静音右声道)
        out_stereo[4*i + 2] = in_mono[2*i];
        out_stereo[4*i + 3] = in_mono[2*i + 1];
    }
}

2. 修正函数调用

将main函数中的调用替换为:

interleave(buffer, stereo, frames);

3. 修复内存泄漏

在free(buffer);前添加:

free(stereo);

4. 更简洁的类型处理(可选)

用int16_t直接操作16位样本,避免字节操作的错误:

#include <stdint.h>

void interleave(const int16_t *in_mono, int16_t *out_stereo, const size_t num_frames)
{
    for (size_t i = 0; i < num_frames; ++i) {
        out_stereo[2*i] = in_mono[i];    // 左声道
        out_stereo[2*i + 1] = in_mono[i];// 右声道
    }
}

调用时强制转换指针类型:

interleave((int16_t*)buffer, (int16_t*)stereo, frames);

内容的提问来源于stack exchange,提问作者Robert Grzesiak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 18:05:17