在MIDP应用程序中播放声音
扫描二维码
随时随地手机看文章
■前言
在上一讲中我们讲述的是如何制作JAVA手机多媒体功能中的动画,本讲中我们将介绍如何制作JAVA手机的另一个多媒体功能——声音,即N800的音乐播放功能。若是在动画上再配以音乐播放功能,就能制作出具有丰富表现力的应用程序了。
■音乐播放
现在我们利用MIDP应用程序播放音乐。但是,MIDP标准API不支持声音播放功能。机种不同,则声音播放方法也不同,所以每个厂商都会使用他们各自扩展的API。N800使用NEC扩展的API,所以能用MIDP应用程序播放声音。
■N800的音乐播放功能
N800只能播放SMF(format0)格式的音乐数据,最大文件尺寸为10Kbyte。SMF格式即StandardMIDIFile的缩略语,就是为了能实现互换性而设定的文件形式,互换性是指在不同的应用程序中也具有能处理数据的特性。SMF分为format0和format1两种格式。N800所采用的是format0格式。这两种格式具有以下不同特点。(表1)
表1
■播放音乐
接下来我们实际操作读取文件播放音乐的功能。
●音乐数据的读取
利用扩展API上的Media类的static方法读取音乐数据。自变量中记述了音乐数据文件的通过。AudioClipaudio=Media.getAudioClip(“/test.mid”);
另外,能够从web上获得音乐数,并且能够从RMS中得到音乐数据。但,由于形式相同,这里就不特别讲述了。详细情况请参考扩展API文档。
●播放
播放读取的音乐数据。使用AudioClip例子(在这称为audio)play方法播放。
audio.play();
而且也能同时播放两个以上的音乐数据。此时,使用AudioClip例子(在这称为audio1、audio2)play方法播放。
audio1.play();
audio2.play();
上述情况下能够同时播放audio1,audio2。
●停止
能够暂停音乐的播放。使用AudioClip例子的stop方法能够停止。
audio.stop();
●其他功能
AudioClip定义了读取其他音乐数据信息的方法和决定反复播放次数的方法。(表2)
表2
■音频事件
音频演奏过程中,演奏开始时、停止时、结束时都会发生音频事件,能定义此时的处理。要定义音频事件发生时的处理,有必要安装AudioListener接口和记述audioAction方法内的处理。
然后,使用AudioClip对象的addAudioListener方法进入AudioListener。
publicclassAudioTestimplementsAudioListener{
/**
*构造函数
**/
publicAudioTest(){
AudioClipaudio=Media.getAudioClip("/test.mid");//读取音乐数据
audio.addAudioListener(this);//注册audio事务监听器
}
/**
*音频事件的处理
**/
publicvoidaudioAction(AudioClipsound,intevent,intparam){
//记述处理
•
•
}
}
ex.1
记述处理的audioAction方法的自变量如下所示。
表3
此外,事件的种类(audioAction方法的自变量、事件)在AudioListener接口文件夹中定义如下。(表4)
表4
以下展示的是只播放音频数据的简单范例。
importjavax.microedition.lcdui.Display;
importjavax.microedition.midlet.MIDlet;
importjavax.microedition.midlet.MIDletStateChangeException;
/**
*音乐播放的简单范例
*/
publicclassAudioextendsMIDlet{
Displaydisplay;
AudioCanvascanvas;
/**
*构造函数
*/
publicAudio(){
canvas=newAudioCanvas();
display=Display.getDisplay(this);
}
protectedvoidstartApp()throwsMIDletStateChangeException{
display.setCurrent(canvas);
}
protectedvoidpauseApp(){}
protectedvoiddestroyApp(booleanarg0)throwsMIDletStateChangeException{}
}
importjavax.microedition.lcdui.*;
importcom.nec.media.*;
/**
*音频canvas
**/
publicclassAudioCanvas
extendsCanvas
implementsRunnable,CommandListener,AudioListener{
CommandSTART=newCommand("play",Command.OK,0);
CommandSTOP=newCommand("stop",Command.OK,0);
AudioClipa;//音乐数据
Threadth;
/**
*构造函数
**/
publicAudioCanvas(){
a=Media.getAudioClip("/_test.mid");//读取音乐数据
a.addAudioListener(this);//注册AudioListener
this.addCommand(START);
this.addCommand(STOP);
this.setCommandListener(this);
th=newThread(this);
th.start();
}
/**
*音频事件的处理
*/
publicvoidaudioAction(AudioClipsound,intevent,intparam){
if(sound==a){
if(event==AudioListener.AUDIO_COMPLETE){
sound.play();
}
}[!--empirenews.page--]
}
/**
*描绘处理
*/
protectedvoidpaint(Graphicsg){
g.setColor(255,255,255);
g.fillRect(0,0,getHeight(),getWidth());
g.setColor(0,0,0);
g.drawString("MusicPlay?",50,52,Graphics.TOP|Graphics.LEFT);
g.drawString(
"channel="+a.getChannel(),
30,
64,
Graphics.TOP|Graphics.LEFT);
g.drawString(
"lapsedtime="+a.getLapsedTime(),
30,
76,
Graphics.TOP|Graphics.LEFT);
g.drawString(
"tempo="+a.getTempo(),
30,
88,
Graphics.TOP|Graphics.LEFT);
g.drawString(
"time="+a.getTime(),
30,
100,
Graphics.TOP|Graphics.LEFT);
}
/**
*命令事件的处理
*/
publicvoidcommandAction(Commandc,Displayabled){
System.out.println("test");
if(c.equals(START)){
a.play();
}elseif(c.equals(STOP)){
a.stop();
}
}
/**
*线程的处理
*刷新查看
*/
publicvoidrun(){
while(true){
repaint();
try{
Thread.sleep(500);
}catch(Exceptione){
}
}
}
}
ex.2
接受表示音乐播放结束的事件后,根据明确的开始播放音乐菜单可以无限循环地播放音乐。下面的演示详细记述了上述例子中的audioAction方法,能够实现无限循环播放。(ex.3)
/**
*音频事件的处理
*/
publicvoidaudioAction(AudioClipsound,intevent,intparam){
if(sound==a){
if(event==AudioListener.AUDIO_COMPLETE){
sound.play();
}
}
}
ex.3
制作应用程序
接下来制作实际的发声应用程序。
本讲中制作的是简单的“泡泡龙”游戏。
■游戏方法
移动小棒接住反弹的球使其不掉下去,使上方的彩球逐渐消失的游戏。彩球完全消失并清除后,球再落下则此游戏通过。
■准备工作
准备游戏必备的图片和音效。
准备以下图片。
图1球的图片
图2小棒的图片
图3彩球的图片
·背景音乐(bgm.mid)
·球反弹时的音效(ball.mid)
·彩球破碎时的音效(block.mid)
■设计
以下是状态连接图(図4)
本讲中为了简单化,在启动应用程序的同时立刻就启动游戏。形成游戏开始、球落下后游戏结束、全部清除彩球后游戏过关。
Figure4
■制作应用程序
现在我们按照以下顺序制作应用程序。
1.类结构
2.变量、常量的定义
3.查看图片和音效
4.使图片运动
5.球的反弹
6.音乐的播放
1.类结构
下表内容是类结构。(表5)
表5
2.变量、常量的定义
将下面的应用变量、定量作为BlockCanvas例子的属性并定义。(ex.4)
//状态设定
privateintstate;//状态
privatefinalintACTIVE=1;
privatefinalintGAME_OVER=2;
privatefinalintCLEAR=3;
//彩球的设定
privatefinalintBLOCK_H=7;//彩球横向的个数
privatefinalintBLOCK_V=5;//彩球纵向的个数
privatefinalintBLOCK_WIDTH=getWidth()/BLOCK_H;
privatefinalintBLOCK_HEIGHT=BLOCK_WIDTH/2;
privatebooleanblock[][]=newboolean[BLOCK_H][BLOCK_V];
privateintblockCount;//彩球个数
//小棒的设定
privatefinalintBAR_HEIGHT=11;
privatefinalintBAR_WIDTH=23;
privateintbarX=0;
privateintbarY=getHeight()-BAR_HEIGHT;
privateintbarMovCodeExample=0;
//球的设定
privatefinalintBALL_HEIGHT=10;
privatefinalintBALL_WIDTH=10;
privateintballX;
privateintballY;
privateintballMovCodeExample=5;
privateintballMoveY=5;
privateThreadth;
//画面类
privateImagebarImg=null;
privateImageballImg=null;
privateImageblockImg=null;
//音效类
privateAudioClipbgm;//Backgroundmusic
privateAudioClipballSound;//Soundofbouncingball
privateAudioClipblockSound;//Soundofdestroyingblocks
ex.4
3.查看图片和音效
查看准备好的图片和音效。BlockCanvas的构造函数内分别读取小棒、球、彩球的图片。(ex.5)
//读取图片
try{
barImg=Image.createImage("/bar.png");
ballImg=Image.createImage("/ball.png");
blockImg=Image.createImage("/block.png");
}catch(CodeExampleceptione){
e.printStackTrace();
}
ex.5
能查看读取后的图片。
彩球在图中的分配为横7纵5,读取彩球图片并描画在画面中。用旗表示彩球的状态。保持原来的排列。将一个一个的彩球使用原来的排列并计算出坐标,安排在画面中。(ex.6)
//查看彩球
g.setColor(0,0,255);
for(inti=0;i
for(intj=0;j[!--empirenews.page--]
if(block[i][j]){
g.drawImage(
blockImg,
i*BLOCK_WIDTH,
(j+1)*BLOCK_HEIGHT,
Graphics.LEFT|Graphics.TOP);
}
}
}
ex.6
接着查看球和小棒。在paint方法中有以下叙述。(ex.7)
//查看球
g.drawImage(ballImg,ballX,ballY,Graphics.LEFT|Graphics.TOP);
//查看小棒
g.drawImage(barImg,barX,barY,Graphics.LEFT|Graphics.TOP);
ex.7
图片安装完成后出现如下画面。
4.使图片运动
接下来使用线程和按键事件移动球和小棒。为了使用线程就得在AudioCanvas类中安装Runnable接口、记述run方法。使用球和小棒定义的移动速度分别变化球和小棒的坐标。此外,小棒的移动速度根据按键处理而变化。以下记述了run方法。(ex.8)
/**
*线程的运行处理
*/
publicvoidrun(){
while(state==ACTIVE){
moveBall();//使球运动
moveBar();//移动小棒
repaint();//再次描画
try{
Thread.sleep(50);
}catch(InterruptedCodeExampleceptione){
e.printStackTrace();
break;
}
}
}
/**
//使球运动
*/
publicvoidmoveBall(){
ballX+=ballMovCodeExample;
ballY+=ballMoveY;
}
/**
*移动小棒
*/
publicvoidmoveBar(){
barX+=barMovCodeExample;
//不能向画面外移动
if(barX<0){
barX=0;
}elseif(barX+BAR_WIDTH>getWidth()){
barX=getWidth()-BAR_WIDTH;
}
}
ex.8
以下表示的是按键处理。(ex.9)
/*****************************************
*按键处理
*****************************************/
/**
*按按键时
*/
protectedvoidkeyPressed(intkey){
if(state==ACTIVE){//正在运动
if(getGameAction(key)==Canvas.RIGHT){
barMovCodeExample=6;
}elseif(getGameAction(key)==Canvas.LEFT){
barMovCodeExample=-6;
}
repaint();
}else{//停止运动后
//再次启动
this.initialize();
}
}
/**
*释放按键时
*/
protectedvoidkeyReleased(intkey){
barMovCodeExample=0;
}
ex.9
5.球的反弹
下面是球的反弹。
球的反弹形式有以下3种。
·碰边壁后反弹
·碰小棒后反弹
·碰彩球后反弹
记述了每个moveBall方法。(ex.10)
碰彩球的反弹时
block[i][j]=false;
blockCount--;
彩球立刻就破碎。彩球破碎后余下的彩球数量blockCount将有所减少。
另外,球掉落时,改变游戏状态后游戏结束。
/**
//使球运动
*/
publicvoidmoveBall(){
ballX+=ballMovCodeExample;
ballY+=ballMoveY;
//反弹
//碰边壁后反弹
if(ballX<0){
ballMovCodeExample*=-1;
ballX=0;
}elseif(getWidth()
ballX=getWidth()-BALL_HEIGHT;
ballMovCodeExample*=-1;
}
if(ballY<0){
ballMoveY*=-1;
ballY=0;
}elseif(ballY>getHeight()){//球落下后
//游戏结束
state=GAME_OVER;
}
//碰上小棒后反弹
if(ballY+BALL_HEIGHT>barY
&&ballX+BALL_WIDTH>barX
&&ballX
ballMoveY*=-1;
ballY=barY-BALL_HEIGHT;
if(barMovCodeExample<0){
ballMovCodeExample-=2;
}elseif(barMovCodeExample>0){
ballMovCodeExample+=2;
}
}
//碰上彩球后反弹
for(inti=0;i
for(intj=0;j
if(block[i][j]){
if(ballX+BALL_WIDTH>i*BLOCK_WIDTH
&&ballX<(i+1)*BLOCK_WIDTH){
if(ballY+BALL_HEIGHT>(j+1)*BLOCK_HEIGHT
&&ballY<(j+2)*BLOCK_HEIGHT){
//清除彩球
block[i][j]=false;
blockCount--;
ballMoveY*=-1;}
}
}
}
}
}
}
ex.10
碰小棒后反弹情况如下所示:向右按键时,球就会让右方快速移动,反之,向左按键时,球则向左方快速移动。(ex.11)
if(barMoveX<0){
ballMoveX-=2;
}elseif(barMoveX>0){
ballMoveX+=2;
}
ex.11
·清除检查
至此安装完毕游戏就有雏形了。但是,在现在的程序中即使彩球全部消失,游戏也不能清除。那么,球与彩球相撞时,数出彩球的剩余数。当该数值为0时,则游戏清除。以下记述的是该处理。(ex.12)
//清除彩球
block[i][j]=false;
blockCount--;
ballMoveY*=-1;
//播放音效
blockSound.play();
//检查游戏清除
if(blockCount==0){
state=CLEAR;
}
ex.12
6.音乐播放
在本讲中的泡泡龙游戏的应用程序中最好使用BGM和音效。游戏开始的同时演奏BGM,音效则是球在碰壁、碰小棒反弹时,以及彩球破碎时才播放的。
·读取
用BlockApplication构造函数读取音乐数据。而且,这里的BGM能够循环播放,所以可以使用音频事件处理。(ex.13)
[!--empirenews.page--]//声音数据的读取
bgm=Media.getAudioClip("/bgm.mid");//背景音乐
ballSound=Media.getAudioClip("/ball.mid");//球反弹后的音效
blockSound=Media.getAudioClip("/block.mid");//球破碎的音效
bgm.addAudioListener(this);//增加AudioListener
ex.13
·播放
读取音乐数据后,接下来进行播放。BGM在游戏开始的同时能够播放,所以在AudioCanvas类的start方法中记述播放处理并能够播放出来。
球的音效:用moveBall方法进行下面反弹判断时,能够播放音效。(ex.14)
//反弹
//碰边壁后反弹
if(ballX<0){
ballMoveX*=-1;
ballX=0;
//播放音效
ballSound.play();
}elseif(getWidth()
ballX=getWidth()-BALL_HEIGHT;
ballMoveX*=-1;
//播放音效
ballSound.play();
}
if(ballY<0){
ballMoveY*=-1;
ballY=0;
//播放音效
ballSound.play();
}elseif(ballY>getHeight()){//球落下后
//游戏结束
state=GAME_OVER;
}
//碰上小棒反弹
if(ballY+BALL_HEIGHT>barY
&&ballX+BALL_WIDTH>barX
&&ballX
ballMoveY*=-1;
ballY=barY-BALL_HEIGHT;
if(barMoveX<0){
ballMoveX-=2;
}elseif(barMoveX>0){
ballMoveX+=2;
}
//播放音效
ballSound.play();
}
ex.14
彩球的音效:用moveBall方法判定彩球的碰撞时,如下记述并能够播放。(ex.15)
//碰上彩球后反弹
for(inti=0;i
for(intj=0;j
if(block[i][j]){
if(ballX+BALL_WIDTH>i*BLOCK_WIDTH
&&ballX<(i+1)*BLOCK_WIDTH){
if(ballY+BALL_HEIGHT>(j+1)*BLOCK_HEIGHT
&&ballY<(j+2)*BLOCK_HEIGHT){
block[i][j]=false;
ballMoveY*=-1;
//播放音效
blockSound.play();
}
}
}
}
}
ex.15
■完成
下面是实际制作的程序一式。(BlockApplication.zip)
运行结果如下所示。
游戏进行中游戏结束
游戏结束
总结
在本讲的讲解中能够自由播放音乐数据了。因此,能够制作成创造性的应用程序。但是,扩展应用程序时,不能保存高分、数据等。在下讲我们将学习如何使用固定存储器保存数据的方法。
查看png格式的画面文件
播放smf格式的音乐文件
http通信,socket通信
逆光、双感光板控制
Sprite功能
ImageMap功能
各种各样的制图扩展功能
3D引擎
由于N820具有256Kbyte的较大存储空间,所以能制作容量稍大、自由度较高的手机应用程序。另外,也能制作对应http、socket通信的自由度较高网络应用程序。因此,也能够搭载3D引擎、3D描画。而且还能安装N800对应的Sprite功能、ImageMap功能的描画功能。
■与N800的比较
下表是N800和N820的比较。(表6)
项目N800N820
显示屏尺寸180x162(纵x横)255x240(纵x横)
JAD文件尺寸最大2KB最大2KB
JAR文件尺寸最大50KB最大1MB
RMS尺寸最大10KB最大10KB
记录存储数量最大3records最大3records
通信协议只有httpHTTPandsocket
画像文件PNGPNG
音乐文件SMF(format0)最大10KbyteSMF(format0)最大10Kbyte
表6
■NECN820Application模拟器
下面是模拟N820工作的模拟器,称为「NEC820ApplicationEmulator」。与以前我们所介绍的「NECN800ApplicationEmulator」在外观上没有什么区别。(図5)
图5
打开模拟器,就是现在的手机画面表示。与N800相比,手机设计多少有些变化,手机的内显示屏变大了。下图是用N820ApplicationEmulator制作的“泡泡龙”游戏画面。“泡泡龙”游戏由于是假定在N800的屏幕上应用的。因此画面尺寸要比N820中的内屏尺寸稍小。因此,彩球之间存有空隙。(図6)
図6
■总结
N820的优点是具有256Kbyte的大容量存储空间,而且使用3D图表引擎、3D图表应用程序、能够制作成对应socket通信的TCP/IP网络应用程序。对于应用开发者而言,N820是一部制作JAVA应用程序非常有价值的终端。对于寻求高级机种的用户而言,应该是一部高精细画面、高功能的极大满足用户需要的终端。今后N820的用户应该会大幅度增加的。