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(¶ms); /* 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(¶ms); /* 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函数存在两个关键错误:
- 字节索引逻辑错误:每个单声道样本是16位(2字节),原代码用
in_L[i]和in_L[i+1]访问样本,会导致相邻样本的字节被错误拼接,破坏音频数据结构。 - 内存越界:当
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
相关产品推荐
相关产品推荐

