支持ALSA的嵌入式Linux运行C++音频程序出现1秒延迟的排查
ADSL路由器USB音频直通链路固定1秒延迟排查问题
问题背景
- 目标设备为搭载Ralink rt63365 4核CPU(运行频率约400Hz)的ADSL路由器,运行Linux v2.6.22.15内核,ALSA驱动版本为1.0.14
- 已基于同芯片路由器的GPL授权源码完成内核、根文件系统编译构建,为内核添加ALSA支持后成功部署到目标路由器
- 调试目标:实现USB声卡采集信号直通播放端的功能,测量并优化音频链路延迟
已验证的现象与已完成调试
- 已消除链路中所有抖动,尝试调整运行线程调度优先级、测试大量硬件参数组合,始终无法将延迟降低到1秒以下
- 初始化设备时设置缓冲区为128帧、周期大小为1帧,单声道44100Hz采样率配置下延迟固定为1秒;切换为8000Hz采样率配置,延迟数值完全一致
- 程序运行期间CPU、内存剩余资源充足,已关闭telnet、samba等可能干扰程序运行的非必要系统服务
- 仅初始化播放设备播放本地PCM文件时不存在可感知延迟,程序启动后可立刻正常输出声音
- 先后测试三款不同定位的USB音频设备:廉价USB声卡、支持USB声卡模式的Zoom H1录音机、Presonus 22vsl音频接口,测得的延迟完全一致
- 同一份程序搭配上述所有声卡,在安装5.x以上版本内核、对应版本ALSA的PC上运行完全正常,无明显可感知延迟
核心程序代码
void setscheduler(void) { struct sched_param sched_param; if (sched_getparam(0, &sched_param) < 0) { printf("Scheduler getparam failed...\n"); return; } sched_param.sched_priority = sched_get_priority_max(SCHED_RR); if (!sched_setscheduler(0, SCHED_RR, &sched_param)) { printf("Scheduler set to Round Robin with priority %i...\n", sched_param.sched_priority); fflush(stdout); return; } printf("!!!Scheduler set to Round Robin with priority %i FAILED!!!\n", sched_param.sched_priority); } int setup_alsa_handle(snd_pcm_t *handle, snd_pcm_stream_t stream) { int err = 0; snd_pcm_hw_params_t *hw_params; snd_pcm_sw_params_t *sw_params; 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(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(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(handle, hw_params, AUDIO_FORMAT)) < 0) { fprintf(stderr, "cannot set sample format (%s)\n", snd_strerror(err)); exit(1); } unsigned int rate = SAMPLE_RATE; if ((err = snd_pcm_hw_params_set_rate_near(handle, hw_params, &rate, 0)) < 0) { fprintf(stderr, "cannot set sample rate (%s)\n", snd_strerror(err)); exit(1); } int dir; snd_pcm_uframes_t period_size = PERIOD_SIZE_IN_FRAMES; if ((err = snd_pcm_hw_params_set_period_size_near(handle, hw_params, &period_size, &dir)) < 0) { fprintf(stderr, "cannot set period size (%s)\n", snd_strerror(err)); exit(1); } int channels; if (stream == SND_PCM_STREAM_CAPTURE) { channels = CAPTURE_CHANNELS; } else { channels = PLAYBACK_CHANNELS; } if ((err = snd_pcm_hw_params_set_channels(handle, hw_params, channels)) < 0) { fprintf(stderr, "cannot set channel count (%s)\n", snd_strerror(err)); exit(1); } if ((err = snd_pcm_hw_params(handle, hw_params)) < 0) { fprintf(stderr, "cannot set parameters (%s)\n", snd_strerror(err)); exit(1); } snd_pcm_hw_params_free(hw_params); if ((err = snd_pcm_sw_params_malloc(&sw_params)) < 0) { fprintf(stderr, "cannot allocate software parameters structure(%s)\n", snd_strerror(err)); return err; } if ((err = snd_pcm_sw_params_current(handle, sw_params)) < 0) { fprintf(stderr, "cannot initialize software parameters structure(%s)\n", snd_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_avail_min(handle, sw_params, FRAMES_PER_BUFFER)) < 0) { fprintf(stderr, "cannot set minimum available count(%s)\n", snd_strerror(err)); return err; } if ((err = snd_pcm_sw_params_set_start_threshold(handle, sw_params, 0U)) < 0) { fprintf(stderr, "cannot set start mode(%s)\n", snd_strerror(err)); return err; } if ((err = snd_pcm_sw_params(handle, sw_params)) < 0) { fprintf(stderr, "cannot set software parameters(%s)\n", snd_strerror(err)); return err; } return 0; } void start_stream(snd_pcm_t *_playback_handle, snd_pcm_t *_capture_handle, void (*stream_callback)(const void *, void *, unsigned long, void *), void (*controls_callback)(void *), void *data) { audio_sample *in_buffer; audio_sample *out_buffer; FILE *fout = NULL; int in_result = 0; int out_result = 0; int temp_n = 0; int buffer_frames = FRAMES_PER_BUFFER; int frame_size_in_bytes = snd_pcm_format_width(AUDIO_FORMAT) / 8; int in_buffer_frames = buffer_frames * CAPTURE_CHANNELS; in_buffer = (audio_sample *)malloc(in_buffer_frames * frame_size_in_bytes); int out_buffer_frames = buffer_frames * PLAYBACK_CHANNELS; out_buffer = (audio_sample *)malloc(out_buffer_frames * frame_size_in_bytes); memset(in_buffer, SAMPLE_SILENCE, in_buffer_frames * frame_size_in_bytes); memset(out_buffer, SAMPLE_SILENCE, out_buffer_frames * frame_size_in_bytes); int err; while (1) { int avail; if ((err = snd_pcm_wait(_playback_handle, 1000)) < 0) { fprintf(stderr, "poll failed(%s)\n", strerror(errno)); break; } avail = snd_pcm_avail_update(_capture_handle); fprintf(stderr, "1 avail (%d)\n", avail); if (avail > 0) { if (avail > FRAMES_PER_BUFFER) avail = FRAMES_PER_BUFFER; snd_pcm_readi(_capture_handle, in_buffer, avail); } avail = snd_pcm_avail_update(_playback_handle); fprintf(stderr, "2 avail (%d)\n", avail); if (avail > 0) { if (avail > FRAMES_PER_BUFFER) avail = FRAMES_PER_BUFFER; snd_pcm_writei(_playback_handle, in_buffer, avail); } } } /** * Main */ int main(int argc, char *argv[]) { setscheduler(); if ((err = snd_pcm_open(&capture_handle, CAPTURE_AUDIO_DEVICE, SND_PCM_STREAM_CAPTURE, 0)) < 0) { fprintf(stderr, "cannot open audio device '%s'. Error: %s\n", CAPTURE_AUDIO_DEVICE, snd_strerror(err)); exit(1); } setup_alsa_handle(capture_handle, SND_PCM_STREAM_CAPTURE); // Init playback device if ((err = snd_pcm_open(&playback_handle, PLAYBACK_AUDIO_DEVICE, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { fprintf(stderr, "cannot open audio device '%s'. Error: %s\n", PLAYBACK_AUDIO_DEVICE, snd_strerror(err)); exit(1); } setup_alsa_handle(playback_handle, SND_PCM_STREAM_PLAYBACK); if ((err = snd_pcm_start(capture_handle)) < 0) { fprintf(stderr, "cannot prepare audio interface for use(%s)\n", snd_strerror(err)); return err; } if ((err = snd_pcm_prepare(playback_handle)) < 0) { fprintf(stderr, "cannot prepare audio interface for use(%s)\n", snd_strerror(err)); return err; } // Start stream start_stream(playback_handle, capture_handle, audio_processing_callback, controls_callback, &data, write_to_file, read_from_file); return 0; }
待解决问题
已尝试所有可想到的排查方向,需要明确可落地的测试步骤,定位该固定1秒延迟的产生根源。
内容的提问来源于stack exchange,提问作者pepinodelasuerte
相关产品推荐
相关产品推荐

