Jack MIDI消息无限重复发送求助:端口缓冲区异常输出
JACK MIDI 消息重复发送问题
问题描述
使用JACK MIDI时,端口缓冲区中的MIDI消息会被反复发送。调用Keyboard::dump_performance内的jack_midi_event_write能正常发送消息,但消息会无限重复——即使断开重连到其他客户端也无法停止,必须关闭自身客户端才行。程序无显式循环,但每条消息持续重复输出,已通过REAPER录制确认该问题,仅发送单条消息时现象依旧。
相关代码
const char* client_name {"Commander"}; jack_client_t* client {NULL}; jack_port_t* output_port {NULL}; jack_nframes_t local_nframes = {0}; int process(jack_nframes_t nframes, [[maybe_unused]]void* arg) {/*{{{*/ local_nframes = nframes; return 0; }/*}}}*/ void Keyboard::connect() noexcept{/*{{{*/ // Open a connection to the JACK server client = jack_client_open(client_name, JackNullOption, NULL); if (client == NULL) { std::cerr << "Failed to open " << client_name << "client at Keyboard::connect() "; std::exit(EXIT_FAILURE); } // Create a MIDI output port output_port = jack_port_register(client, "output", JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0); if (output_port == NULL) { // Failed to create output port jack_client_close(client); std::cerr << "Failed to open " << client_name << "port at Keyboard::connect() "; std::exit(EXIT_FAILURE); } // Set the process callback function jack_set_process_callback(client, process, 0); // Activate the client if (jack_activate(client) != 0) { // Failed to activate client jack_port_unregister(client, output_port); jack_client_close(client); std::cerr << "Failed to activate " << client_name << "client at Keyboard::connect() "; std::exit(EXIT_FAILURE); } MIDI = Switch::ON; }/*}}}*/ void Keyboard::dump_performance(const Performance& _Performance) noexcept/*{{{*/ { set_performance_buffer(_Performance); // Pass to the buffer // Sleep static struct timespec keyboardTimer; keyboardTimer.tv_sec = 0; keyboardTimer.tv_nsec = 200000000; // Change page [[maybe_unused]]jack_midi_data_t combiSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x00, 0xF7}; [[maybe_unused]]jack_midi_data_t timbreSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x01, 0xF7}; // MIDI Ch = msb[0], lsb[0], pc[0] are the same, only the last digit changes [[maybe_unused]]jack_midi_data_t msb[3] = {0xB0, 0x00, 0x3F}; [[maybe_unused]]jack_midi_data_t lsb[3] = {0xB0, 0x20, 0x00}; [[maybe_unused]]jack_midi_data_t pc[2] = {0xC0, 0x00}; // Adjustment lsb[2] = performance_buffer.patch.bnk - 65; /*LSB*/ pc[1] = performance_buffer.patch.num; /*PC*/ // Retrieve the output port buffer void* port_buffer = jack_port_get_buffer(output_port, local_nframes); // Clear the port buffer before writing MIDI events jack_midi_clear_buffer(port_buffer); // Send the MIDI messages to the output port if (output_port != nullptr) { jack_midi_event_write(port_buffer, 0, combiSysEx, sizeof(combiSysEx)); jack_midi_event_write(port_buffer, 0, timbreSysEx, sizeof(timbreSysEx)); jack_midi_event_write(port_buffer, 0, msb, sizeof(msb)); jack_midi_event_write(port_buffer, 0, lsb, sizeof(lsb)); jack_midi_event_write(port_buffer, 0, pc, sizeof(pc)); } // Sleep nanosleep(&keyboardTimer, NULL); set_scene(performance_buffer.default_scene); //dump_scene(); }/*}}}*/ void Keyboard::disconnect() noexcept {/*{{{*/ // Deactivate, unregister, and close the client jack_deactivate(client); jack_port_unregister(client, output_port); jack_client_close(client); // Set MIDI switch to OFF or perform any necessary cleanup MIDI = Switch::OFF; }/*}}}*/
解决方案
核心问题:JACK的端口缓冲区会在每个处理周期被自动发送,你的代码仅在dump_performance中写入一次消息,但后续的process回调周期内没有清空或覆盖缓冲区内容,导致旧消息被持续重复发送。
修复步骤
- 将MIDI消息发送逻辑移至
process回调:JACK的缓冲区属于当前处理周期,非回调时机操作缓冲区会导致数据残留到后续周期。 - 添加标志位控制单次发送:用布尔变量标记是否需要发送消息,仅在标记为
true的周期写入数据,写完后重置标志避免重复。 - 移除阻塞函数:不要在JACK回调或业务逻辑中使用
nanosleep,会导致JACK服务器卡顿,时序交由JACK处理周期控制。
修改后关键代码示例
// 添加成员变量控制消息发送(需保证线程安全,若跨线程调用dump_performance,建议用原子变量) bool _needs_send = false; Performance _pending_performance; int process(jack_nframes_t nframes, [[maybe_unused]]void* arg) { void* port_buffer = jack_port_get_buffer(output_port, nframes); jack_midi_clear_buffer(port_buffer); // 每次周期先清空缓冲区 if (_needs_send) { // 构建并发送MIDI消息 jack_midi_data_t combiSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x00, 0xF7}; jack_midi_data_t timbreSysEx[7] = {0xF0, 0x42, 0x30, 0x7A, 0x4E, 0x01, 0xF7}; jack_midi_data_t msb[3] = {0xB0, 0x00, 0x3F}; jack_midi_data_t lsb[3] = {0xB0, 0x20, 0x00}; jack_midi_data_t pc[2] = {0xC0, 0x00}; lsb[2] = _pending_performance.patch.bnk - 65; pc[1] = _pending_performance.patch.num; jack_midi_event_write(port_buffer, 0, combiSysEx, sizeof(combiSysEx)); jack_midi_event_write(port_buffer, 0, timbreSysEx, sizeof(timbreSysEx)); jack_midi_event_write(port_buffer, 0, msb, sizeof(msb)); jack_midi_event_write(port_buffer, 0, lsb, sizeof(lsb)); jack_midi_event_write(port_buffer, 0, pc, sizeof(pc)); _needs_send = false; // 发送完成后重置标志 set_scene(_pending_performance.default_scene); } return 0; } void Keyboard::dump_performance(const Performance& _Performance) noexcept { _pending_performance = _Performance; _needs_send = true; // 标记需要发送消息 // 移除原有的缓冲区操作和nanosleep }
额外注意
- 若
dump_performance在非JACK实时线程调用,需确保_needs_send和_pending_performance的线程安全,可使用std::atomic<bool>或无锁队列传递消息。 - 避免在
process回调中执行耗时操作,防止JACK触发实时线程超时警告。
内容的提问来源于stack exchange,提问作者Juanca
相关产品推荐
相关产品推荐

