ALSA音序器已调度事件无法输出的问题求助
ALSA Sequencer Queue Not Triggering Scheduled MIDI Events
我帮你排查了下代码,问题出在一个很容易忽略的ALSA细节上:你创建了音序器队列,但没有给它绑定时钟源,导致队列的tick根本不会自动推进,预先调度的Note On/Off事件自然永远不会被发送出去。
下面是具体的问题分析和修复方案:
为什么事件没触发?
ALSA的snd_seq_alloc_queue只是创建了一个空的队列容器,默认没有任何时钟驱动它的tick计数。没有tick推进,那些按tick调度的事件就永远到不了触发时间点,midisnoop自然收不到任何消息。
修复步骤
1. 给队列配置时钟和节拍参数
在创建队列后,添加代码给队列绑定系统实时时钟,并设置BPM和PPQ(每四分音符的tick数)——这是让队列自动推进tick的关键:
snd_seq_queue_tempo_t *tempo; snd_seq_queue_tempo_malloc(&tempo); // 设置120 BPM,每四分音符对应96 tick(和你代码里用的tick间隔一致) snd_seq_queue_tempo_set_tempo(tempo, 120 * 1000); // 速度单位是微拍/分钟 snd_seq_queue_tempo_set_ppq(tempo, 96); snd_seq_set_queue_tempo(seq_handle, queue, tempo); snd_seq_queue_tempo_free(tempo); // 让队列使用实时调度,自动推进tick snd_seq_queue_set_sched_param(seq_handle, queue, SND_SEQ_SCHED_REALTIME, 0); snd_seq_queue_set_sched_flags(seq_handle, queue, SND_SEQ_SCHED_AUTO);
2. 补充错误检查(可选但推荐)
原来的代码没有检查snd_seq_open、snd_seq_alloc_queue等关键函数的返回值,出问题时很难定位。我在修改后的代码里补上了这些检查,方便你排查潜在问题。
3. 优化端口和客户端配置
给客户端设置名称、给端口添加MIDI通用类型标识,能让midisnoop更容易识别和订阅你的端口。
修改后的完整代码
#include <stdio.h> #include <alsa/asoundlib.h> #include <unistd.h> int queue, out_port; snd_seq_t *seq_handle; snd_seq_tick_time_t current_tick; snd_seq_queue_status_t *status; int main (int argc, char **argv) { // 打开音序器 if (snd_seq_open(&seq_handle, "default", SND_SEQ_OPEN_INPUT | SND_SEQ_OPEN_OUTPUT, 0) < 0) { fprintf(stderr, "Failed to open ALSA sequencer\n"); return 1; } // 设置客户端名称,方便midisnoop识别 snd_seq_set_client_name(seq_handle, "Test MIDI Sequencer"); // 分配队列 queue = snd_seq_alloc_queue(seq_handle); if (queue < 0) { fprintf(stderr, "Failed to allocate sequencer queue\n"); snd_seq_close(seq_handle); return 1; } fprintf(stderr,"Queue ID: %d\n", queue); // --- 新增:配置队列时钟和节拍 --- snd_seq_queue_tempo_t *tempo; snd_seq_queue_tempo_malloc(&tempo); snd_seq_queue_tempo_set_tempo(tempo, 120 * 1000); // 120 BPM snd_seq_queue_tempo_set_ppq(tempo, 96); // 每四分音符96个tick snd_seq_set_queue_tempo(seq_handle, queue, tempo); snd_seq_queue_tempo_free(tempo); // 设置队列自动调度,使用实时时钟 snd_seq_queue_set_sched_param(seq_handle, queue, SND_SEQ_SCHED_REALTIME, 0); snd_seq_queue_set_sched_flags(seq_handle, queue, SND_SEQ_SCHED_AUTO); // --- 新增结束 --- // 创建输出端口 out_port = snd_seq_create_simple_port(seq_handle, "test:out", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_EVENT_OUTPUT, SND_SEQ_PORT_TYPE_MIDI_GENERIC | SND_SEQ_PORT_TYPE_APPLICATION); if( out_port < 0 ) { fprintf(stderr, "Failed to create output port\n"); snd_seq_free_queue(seq_handle, queue); snd_seq_close(seq_handle); return 1; } // 初始化MIDI事件 snd_seq_event_t event; snd_seq_ev_clear( &event ); snd_seq_addr_t addr; addr.client = snd_seq_client_id(seq_handle); addr.port = out_port; event.source = addr; snd_seq_ev_set_subs( &event ); // 发送给所有订阅者 // 配置音符参数 int chan = 0; int note = 64; int veloc = 127; int tick = 0; // 调度Note On事件(tick=0) snd_seq_ev_set_noteon( &event, chan, note, veloc); snd_seq_ev_schedule_tick( &event, queue, 0, tick ); snd_seq_event_output( seq_handle, &event ); // 调度Note Off事件(tick=96,即1个四分音符后) tick = 96; snd_seq_ev_set_noteoff( &event, chan, note, veloc); snd_seq_ev_schedule_tick( &event, queue, 0, tick ); snd_seq_event_output( seq_handle, &event ); // 刷新输出缓冲区 snd_seq_drain_output( seq_handle ); // 等待按键,方便设置midisnoop订阅 fprintf(stderr, "Press any key to start the queue...\n"); fgetc( stdin ); // 启动队列 if (snd_seq_start_queue(seq_handle, queue, NULL) < 0) { fprintf(stderr, "Failed to start queue\n"); snd_seq_free_queue(seq_handle, queue); snd_seq_close(seq_handle); return 1; } snd_seq_drain_output( seq_handle ); // 打印实时tick,验证队列运行 for(int i=0;i<4;i++) { snd_seq_queue_status_malloc(&status); snd_seq_get_queue_status(seq_handle, queue, status); current_tick = snd_seq_queue_status_get_tick_time(status); snd_seq_queue_status_free(status); fprintf(stderr,"Current tick: %lu\n", (unsigned long)current_tick ); sleep(1); } // 清理资源 snd_seq_stop_queue( seq_handle, queue, NULL ); snd_seq_free_queue( seq_handle, queue ); snd_seq_close(seq_handle); return 0; }
验证方法
- 编译代码:
gcc -o test_seq test_seq.c -lasound - 运行程序,此时它会等待你按键
- 打开midisnoop,找到
Test MIDI Sequencer下的test:out端口并订阅 - 按下任意键启动队列,你应该能在midisnoop里看到Note On和Note Off事件被发送,同时终端会打印持续增长的tick值
内容的提问来源于stack exchange,提问作者johnnyO
相关产品推荐
相关产品推荐

