我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛
想法与设计
想法是完成一个用声音控制龙船去接住落下的粽子的游戏,拆开功能来看,我们需要获取声音,并将音量转化为数据,声音和数据应该有线性关系,声音数据转换为龙船在屏幕上X轴坐标来控制龙船的移动。而粽子的落点位置也应该随机,让游戏有更多的不确定性,同时接住的粽子越多粽子下落的速度越快,逐步加大游戏的难度。最终根据接住粽子的数量做出游戏的评价。
着手实现
2.1、音频能量转换
理论基础第一个是音频的录制,第二个是db的计算。两篇详细的我都有在我的博客里讲到,我们大可以直接将二者组合起来使用,并将数据进行实时的回调。音频录制用的是OpenSL ES,这里只需要一个db计算,不要多高的音质还原,我们完全可以降低音质参数来获取更快的计算速度,使得我们的声音控制更加灵敏。db计算原理就是传统的增益计算,以16位的音频数据计算代码如下:
void getPcmDB16(const unsigned char *pcmdata, size_t size) {
int db = 0;
short int value = 0;
double sum = 0;
for(int i = 0; i < size; i += bit_format/8)
{
memcpy(&value, pcmdata+i, bit_format/8); //获取2个字节的大小(值)
sum += abs(value); //绝对值求和
}
sum = sum / (size / (bit_format/8)); //求平均值(2个字节表示一个振幅,所以振幅个数为:size/2个)
if(sum > 0)
{
db = (int)(20.0*log10(sum));
}
memcpy(wave_buffer+wave_index,(char*)&db,1);
wave_index++;
}
2.2、数据的回调
音频流的获取和db的计算都在native,而db的展现要在Java所以需要通过jni将native的数据回调给Java层,Java调起native和native回调Java的方式都在之前的JNI常用开发技巧的文章中有讲到
2.3、绘制
主要是素材的绘制,一个龙舟,一个粽子的图片绘制,使用原生的view自定义view实现,第一步绘制bitmap,要考虑到bitmap的大小,避免大的内存占用,其次考虑到bitmap的大小,进行合适的缩小达到更好的显示效果,稍微注意点可以考虑一些bitmap的内存回收问题。
val matrix = Matrix()
val srczz=BitmapFactory.decodeResource(resources, R.drawable.zz)
matrix.postScale(90f/srczz.width,90f/srczz.height)
zzbitmap = Bitmap.createBitmap(
srczz, 0, 0,
srczz.width, srczz.height, matrix, true
)
srczz.recycle()
其次就是粽子随时间下落的y轴坐标变换的位置与速度,和龙舟随分贝变化的x轴,我们都需要动态的改变bitmap的绘制位置。
三、效果和代码
最终的效果如下
由于是声音控制,所以它应该是要动起来的,有兴趣的可以下载源码跑起来玩一下。
源码在这里。其实还可以拓展一下,加入开源的语音转文字库进行控制,如果说转换速度有限的话,可以将特定语音字设为彩蛋,例如识别到“端午”,龙船铺满屏幕十秒钟,类似无敌挂。推荐的语音转文字的开源库Vosk,支持Android端的小体积模型转换,目前对英语的识别率很不错,中文的话亲测好像还有待加强,不过现在各大家语音转文字都是按分钟收费,能有这样一款免费的替代产品也是非常不错,毕竟也是有维护,肯定是一直在完善的。