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

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

验证方法

  1. 编译代码:gcc -o test_seq test_seq.c -lasound
  2. 运行程序,此时它会等待你按键
  3. 打开midisnoop,找到Test MIDI Sequencer下的test:out端口并订阅
  4. 按下任意键启动队列,你应该能在midisnoop里看到Note On和Note Off事件被发送,同时终端会打印持续增长的tick值

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:12:18