You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于ALSA的MIDI技术:如何检测虚拟MIDI端口的连接订阅状态

要检测你创建的虚拟MIDI端口是否有外部设备通过aconnect或Patchage连接,核心是查询ALSA Sequencer的订阅关系——当外部设备连接到你的端口时,会在ALSA seq系统中生成一条订阅记录。下面提供两种实用方案:

方案1:主动查询当前连接状态

这种方法适合在生成MIDI事件前,一次性检查是否有设备连接。步骤如下:

  1. 先获取你的客户端ID和端口ID(snd_seq_create_simple_port的返回值就是端口ID)
  2. 通过subs结构体指定查询目标,遍历所有订阅到你端口的外部设备

代码示例

#include <alsa/asoundlib.h>
#include <stdio.h>

int main() {
    snd_seq_t *handle;
    int port_id;
    int client_id;

    // 初始化seq客户端(假设你已经完成这一步)
    if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
        fprintf(stderr, "Failed to open ALSA sequencer\n");
        return 1;
    }
    snd_seq_set_client_name(handle, "My MIDI App");

    // 创建你提到的端口
    port_id = snd_seq_create_simple_port(handle, "my port",
        SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
        SND_SEQ_PORT_TYPE_MIDI_GENERIC);
    if (port_id < 0) {
        fprintf(stderr, "Failed to create MIDI port\n");
        snd_seq_close(handle);
        return 1;
    }
    client_id = snd_seq_client_id(handle);

    // 开始查询连接状态
    snd_seq_subscription_t *subs;
    snd_seq_port_info_t *pinfo;
    int connected_devices = 0;

    snd_seq_subscription_alloca(&subs);
    snd_seq_port_info_alloca(&pinfo);

    // 设置查询目标为我们的端口,方向为“外部端口输出到我们的端口”
    snd_seq_subscription_set_dest(subs, client_id, port_id);
    snd_seq_subscription_set_dir(subs, SND_SEQ_SUBSCRIPTION_DIR_OUTPUT);

    // 启动查询
    if (snd_seq_query_subscribers(handle, subs, 1) < 0) {
        fprintf(stderr, "Failed to query subscribers\n");
        snd_seq_close(handle);
        return 1;
    }

    // 遍历所有匹配的订阅
    while (snd_seq_get_port_info(handle, pinfo) >= 0) {
        connected_devices++;
        const char *client_name = snd_seq_port_info_get_client_name(pinfo);
        const char *port_name = snd_seq_port_info_get_name(pinfo);
        printf("Connected device: %s (%s)\n", client_name, port_name);
    }

    if (connected_devices > 0) {
        printf("\n✅ 有%d个设备已连接,可以开始生成MIDI事件\n", connected_devices);
        // 在这里执行你的MIDI事件生成逻辑
    } else {
        printf("\n❌ 当前没有设备连接到端口\n");
    }

    snd_seq_close(handle);
    return 0;
}

关键说明

  • SND_SEQ_SUBSCRIPTION_DIR_OUTPUT表示我们要查询的是外部端口向我们的端口发送数据的订阅关系,这正是aconnect建立的连接类型
  • 编译时需要链接ALSA库:gcc your_code.c -o midi_check -lasound

方案2:实时监听连接/断开事件

如果你需要实时感知设备的连接或断开(比如动态启停MIDI事件生成),可以监听ALSA seq的特定事件:

代码示例

#include <alsa/asoundlib.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    snd_seq_t *handle;
    int port_id;
    int client_id;
    int connected = 0;

    // 初始化seq客户端和端口(同方案1)
    if (snd_seq_open(&handle, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0) {
        fprintf(stderr, "Failed to open ALSA sequencer\n");
        return 1;
    }
    snd_seq_set_client_name(handle, "My MIDI App");
    port_id = snd_seq_create_simple_port(handle, "my port",
        SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE,
        SND_SEQ_PORT_TYPE_MIDI_GENERIC);
    if (port_id < 0) {
        fprintf(stderr, "Failed to create MIDI port\n");
        snd_seq_close(handle);
        return 1;
    }
    client_id = snd_seq_client_id(handle);

    // 设置非阻塞模式,避免事件输入阻塞程序
    snd_seq_set_nonblock(handle, 1);

    snd_seq_event_t *ev;
    snd_seq_event_alloca(&ev);

    printf("等待设备连接...\n");
    while (1) {
        int result = snd_seq_event_input(handle, ev);
        if (result < 0) {
            if (result == -EAGAIN) {
                // 没有事件,短暂休眠后继续
                usleep(10000);
                continue;
            }
            fprintf(stderr, "Error reading seq event\n");
            break;
        }

        switch (ev->type) {
            case SND_SEQ_EVENT_PORT_SUBSCRIBED:
                // 确认订阅的目标是我们的端口
                if (ev->data.connect.dest.client == client_id && ev->data.connect.dest.port == port_id) {
                    connected = 1;
                    printf("✅ 设备已连接!开始生成MIDI事件\n");
                    // 启动你的MIDI事件生成逻辑
                }
                break;
            case SND_SEQ_EVENT_PORT_UNSUBSCRIBED:
                if (ev->data.connect.dest.client == client_id && ev->data.connect.dest.port == port_id) {
                    connected = 0;
                    printf("❌ 设备已断开!停止生成MIDI事件\n");
                    // 停止你的MIDI事件生成逻辑
                }
                break;
            default:
                // 忽略其他无关事件
                break;
        }
    }

    snd_seq_close(handle);
    return 0;
}

关键说明

  • SND_SEQ_EVENT_PORT_SUBSCRIBEDSND_SEQ_EVENT_PORT_UNSUBSCRIBED是ALSA seq专门用于通知端口订阅变化的事件
  • 非阻塞模式确保你的程序可以同时处理其他任务,不会卡在事件输入上

内容的提问来源于stack exchange,提问作者Folkert van Heusden

火山引擎 最新活动