What is TiMidity++?
TiMidity++ is a software synthesizer that can play MIDI files without a hardware synthesizer. It can either render to the sound card in real time, or it can save the result to a file, such as a PCM .wav file.
要在android系统中使用timidity播放midi指令/文件,可以移植timidity++代码做成一个native service。架构不是很复杂,时序图如下:
timidity++服务与其他应用通信可以采用传统linux IPC通信(例如 fifo,input event),或者实现一个android binder服务来响应客户端发送的指令流,这样android设备就变成了一台可弹奏midi设备了。
但在上述框架实施后,测试过程中发现有一定概率timidity++服务会进入无声的状态,无法响应客户端发送过来的指令,要等很久才会听到一两声指令的声音。刚开始怀疑是IPC通信问题, 我从 fifo换成input event,再换成binder等通信方式,问题依然存在。好在采用binder通信框架时我采用了midi指令接收与执行线程独立执行,接收线程在无声状态下依然可以继续接受客户端指令,因此排除IPC通信挂起导致问题。经过大量trace跟踪打印分析,最终定位到timidity++引擎一处bug。
playmidi.c->play_event,源码如下:
#if ! defined(IA_WINSYN) && ! defined(IA_PORTMIDISYN) && ! defined(IA_W32G_SYN)
if (midi_streaming != 0)
if ((cet - current_sample) * 1000 / play_mode->rate
> stream_max_compute) {
kill_all_voices();
* reset_voices(); */
/* ctl->cmsg(CMSG_INFO, VERB_DEBUG_SILLY,
"play_event: discard %d samples", cet - current_sample); */
current_sample = cet;
}
#endif
rc = compute_data(cet - current_sample);//此处为note转wav计算 如果参数越大,计算时间越久
cet,current_sample
是int32数据类型
32比特整型,数据范围为 -2147483648~2147483647(-231 ~231-1)
再看代码条件
if ((cet - current_sample) * 1000 / play_mode->rate > stream_max_compute)
其中
cet
为当前midi note的采样时间 cet = MIDI_EVENT_TIME(ev);
current_sample
为上一次计算note的时间Number of calclated samples
play_mode->rate
为44100
stream_max_compute
为500
所以cet与current_sample的差只要大于 2147484 ,以下表达式
(cet - current_sample)*1000
就会产生整型溢出变为负数,导致条件内代码无法执行。
(源码在两个采样时间差大到一定程度则重置当前采用时间为现在时间 current_sample = cet;
)
以下代码填入一个巨大的参数(例如 2147484)
rc = compute_data(cet - current_sample);//此处代码为note转wav计算,如果参数越大,计算时间越久
从而导致整个timidity++服务处于大量计算当中,当然无法继续响应midi指令了。
解决办法
由于本人使用的android平台是32位的, timidity++也未提供int64位数据,所以提高数据宽度不可行,因此 只需要调整运算顺序即可解决这个问题
if (cet - current_sample > stream_max_compute * play_mode->rate / 1000)
这样不等式左边不会溢出(cet,current_sample
都是正整数), 右边基本上也是一个比较小的常数。
实测问题得到完美解决。