调试Pro Tools发送给macOS应用的MMC定位消息解析异常问题
调试Pro Tools发送给macOS应用的MMC「locate」定位消息时,无法解析接收的字节,疑似存在编码或封装问题。开发环境:macOS Sequoia、Xcode 16.1,最低部署版本macOS 13.5,Objective-C开发。
通过Core MIDI创建输入端口的代码:
MIDIInputPortCreateWithProtocol(client, CFSTR("Input port"), kMIDIProtocol_1_0, &inPort, gReceiveBlock);
能在gReceiveBlock回调中收到MIDI消息,端点、端口、MMC ID等配置无误,但接收的包字节不符合MIDI规范。MIDI Monitor抓包显示Pro Tools发送的包为:
16:48:30.173 From IAC Driver Bus 1 SysEx Universal Real Time 13 bytes F0 7F 01 06 44 06 01 01 01 0F 23 00 F7
但应用中查看该包时显示:
timeStamp MIDITimeStamp 0x0000000000000000 wordCount UInt32 0x00000004 words UInt32[64] [0] UInt32 0x30167f01 [1] UInt32 0x06440601 [2] UInt32 0x30350101 [3] UInt32 0x0f210000
已发现的问题:缺少SysEx起始字节0xF0,控制字节与时间码字节间插入额外字节,时间码的帧数值错误,且无结束字节0xF7。接收字节的代码如下:
MIDIReceiveBlock const gReceiveBlock = ^(const MIDIEventList *eventList, void *srcConnRefCon) { // Process each packet in the event list const MIDIEventPacket *packet = &eventList->packet[0]; for (int i = 0; i < eventList->numPackets; ++i) { // Get the MIDI words (32-bit data chunks) for (int w = 0; w < packet->wordCount; ++w) { uint32_t word = packet->words[w]; // Process the packet } packet = MIDIEventPacketNext(packet); } };
尝试将创建输入端口时的kMIDIProtocol_1_0改为kMIDIProtocol_2_0,结果依旧。
核心问题是错误地直接读取MIDIEventPacket的words字段,没有按照Core MIDI的事件结构解析SysEx数据。Core MIDI传递的不是原始字节流,而是封装成特定结构的MIDI事件,每个words元素包含事件类型、状态码和数据,必须按规范拆解。
具体修复步骤
识别SysEx事件类型
Core MIDI会把SysEx事件标记为kMIDIEventTypeSysEx,需要先判断事件类型,再提取对应数据,不能直接遍历words。MIDIEventPacket的第一个word包含事件类型和数据长度信息。正确的SysEx解析代码
修改接收回调,按Core MIDI规范提取原始SysEx字节:MIDIReceiveBlock const gReceiveBlock = ^(const MIDIEventList *eventList, void *srcConnRefCon) { const MIDIEventPacket *packet = &eventList->packet[0]; for (NSUInteger i = 0; i < eventList->numPackets; ++i) { NSUByte eventType = MIDIEventGetType(packet->words[0]); if (eventType == kMIDIEventTypeSysEx) { // 获取SysEx数据实际长度(不含起始F0和结束F7) UInt32 sysexLength = MIDIEventGetSysExLength(packet->words[0]); NSMutableData *sysexData = [NSMutableData dataWithCapacity:sysexLength + 2]; // 手动添加SysEx起始字节F0 [sysexData appendBytes:"\xF0" length:1]; // 提取SysEx数据字节:words是大端字节序,需转换为主机字节序 const UInt32 *words = &packet->words[1]; NSUInteger remainingBytes = sysexLength; NSUInteger wordIndex = 0; while (remainingBytes > 0) { UInt32 word = CFSwapInt32BigToHost(words[wordIndex]); NSUInteger bytesToCopy = MIN(remainingBytes, 4); [sysexData appendBytes:&word length:bytesToCopy]; remainingBytes -= bytesToCopy; wordIndex++; } // 手动添加SysEx结束字节F7 [sysexData appendBytes:"\xF7" length:1]; // 此时sysexData中的字节流与MIDI Monitor捕获的完全一致 NSLog(@"Received SysEx: %@", sysexData); } packet = MIDIEventPacketNext(packet); } };关键注意事项
- Core MIDI会自动剥离SysEx的起始
F0和结束F7,需要手动补回 words中的数据采用大端字节序,必须用CFSwapInt32BigToHost转换后才能正确读取- SysEx数据被打包到32位word中,最后一个word可能包含填充字节,需按实际长度提取,避免读取无效数据
- Core MIDI会自动剥离SysEx的起始
协议版本说明
无论使用kMIDIProtocol_1_0还是kMIDIProtocol_2_0,SysEx事件的解析逻辑完全一致,修改协议版本无法解决当前问题。
内容的提问来源于stack exchange,提问作者slondike

