天天看點

【ANDROID遊戲開發之八】遊戲中添加音頻-詳解MEDIAPLAYER與SOUNDPOO!并講解兩者的差別和遊戲中的用途!

 ———————————————————————

『很多童鞋說我的代碼運作後,點選home或者back後會程式異常,如果你也這樣遇到過,那麼你肯定沒有仔細讀完himi的博文,第十九篇himi專門寫了關于這些錯誤的原因和解決方法,這裡我在部落格都補充說明下,省的童鞋們總疑惑這一塊;請點選下面聯系進入閱讀:

——————————————————————-

遊戲開發中,通過資料和書籍了解到在有兩種播放音頻形式可以用在我們的遊戲開發中,第一個:mediaplayer 類 ;第二個:soundpool 類!

ps:當然還有一個jetplayer 但是 播放的檔案格式比較麻煩,是以這裡抛開不解釋,有興趣的可以去自己研究下、呵呵;

運作效果圖:

【ANDROID遊戲開發之八】遊戲中添加音頻-詳解MEDIAPLAYER與SOUNDPOO!并講解兩者的差別和遊戲中的用途!

mediaplayer 和:soundpool 類!那麼他們之間的利弊各是什麼呢?或者說,我們遊戲開發到底用哪一個更佳呢?

答案就是:兩者都必須要!!!分析利弊與各自的用途後,等各位童鞋熟習每個播放形式實作之後我會詳細道來!

下面仍然是先上代碼:(先看代碼 然後我講解兩個播放形式的利弊關系和各個用途以及其中解釋代碼中的幾個備注!)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

package com.himi;

import java.util.hashmap;

import android.content.context;

import android.graphics.canvas;

import android.graphics.color;

import android.graphics.paint;

import android.media.audiomanager;

import android.media.mediaplayer;

import android.media.soundpool;

import android.view.keyevent;

import android.view.motionevent;

import android.view.surfaceholder;

import android.view.surfaceview;

import android.view.surfaceholder.callback;

public class mysurfaceview extends surfaceview implements callback, runnable {

    private thread th;

    private surfaceholder sfh;

    private canvas canvas;

    private mediaplayer player;

    private paint paint;

    private boolean on = true;

    private int currentvol, maxvol;

    private audiomanager am;

    private hashmap<integer, integer> soundpoolmap;//備注1

    private int loadid;

    private soundpool soundpool;

    public mysurfaceview(context context) {

        super(context);

// 擷取音頻服務然後強轉成一個音頻管理器,後面友善用來控制音量大小用

        am = (audiomanager) mainactivity.instance

                .getsystemservice(context.audio_service);

        maxvol = am.getstreammaxvolume(audiomanager.stream_music);

        // 擷取最大音量值(15最大! .不是100!)

        sfh = this.getholder();

        sfh.addcallback(this);

        th = new thread(this);

        this.setkeepscreenon(true);

        setfocusable(true);

        paint = new paint();

        paint.setantialias(true);

        //mediaplayer的初始化

        player = mediaplayer.create(context, r.raw.himi);

        player.setlooping(true);//設定循環播放

        //soundpool的初始化

        soundpool = new soundpool(4, audiomanager.stream_music, 100);

        soundpoolmap = new hashmap<integer, integer>();

        soundpoolmap.put(1, soundpool.load(mainactivity.content,

                r.raw.himi_ogg, 1));

        loadid = soundpool.load(context, r.raw.himi_ogg, 1);

//load()方法的最後一個參數他辨別優先考慮的聲音。目前沒有任何效果。使用了也隻是對未來的相容性價值。

    }

    public void surfacecreated(surfaceholder holder) {

        /*

         * android os中,如果你去按手機上的調節音量的按鈕,會分兩種情況,

         * 一種是調整手機本身的鈴聲音量,一種是調整遊戲,軟體,音樂播放的音量

         * 當我們在遊戲中的時候 ,總是想調整遊戲的音量而不是手機的鈴聲音量,

         * 可是煩人的問題又來了,我在開發中發現,隻有遊戲中有聲音在播放的時候

         * ,你才能去調整遊戲的音量,否則就是手機的音量,有沒有辦法讓手機隻要是

         * 在運作遊戲的狀态就隻調整遊戲的音量呢?試試下面這段代碼吧!

         */

        mainactivity.instance.setvolumecontrolstream(audiomanager.stream_music);

        // 設定調整音量為媒體音量,當暫停播放的時候調整音量就不會再預設調整鈴聲音量了,娃哈哈  

        player.start();

        th.start();

    public void draw() {

        canvas = sfh.lockcanvas();

        canvas.drawcolor(color.white);

        paint.setcolor(color.red);

        canvas.drawtext("目前音量: " + currentvol, 100, 40, paint);

        canvas.drawtext("目前播放的時間" + player.getcurrentposition() + "毫秒", 100,

                70, paint);

        canvas.drawtext("方向鍵中間按鈕切換 暫停/開始", 100, 100, paint);

        canvas.drawtext("方向鍵←鍵快退5秒 ", 100, 130, paint);

        canvas.drawtext("方向鍵→鍵快進5秒 ", 100, 160, paint);

        canvas.drawtext("方向鍵↑鍵增加音量 ", 100, 190, paint);

        canvas.drawtext("方向鍵↓鍵減小音量", 100, 220, paint);

        sfh.unlockcanvasandpost(canvas);

    private void logic() {

        currentvol = am.getstreamvolume(audiomanager.stream_music);// 不斷擷取目前的音量值

    @override

    public boolean onkeydown(int key, keyevent event) {

        if (key == keyevent.keycode_dpad_center) {

            on = !on;

            if (on == false)

                player.pause();

            else

                player.start();

        } else if (key == keyevent.keycode_dpad_up) {// 按鍵這裡本應該是right,但是因為目前是橫屏模式,以下雷同

            player.seekto(player.getcurrentposition() + 5000);

        } else if (key == keyevent.keycode_dpad_down) {

            if (player.getcurrentposition() < 5000) {

                player.seekto(0);

            } else {

                player.seekto(player.getcurrentposition() - 5000);

            }

        } else if (key == keyevent.keycode_dpad_left) {

            currentvol += 1;

            if (currentvol > maxvol) {

                currentvol = 100;

            am.setstreamvolume(audiomanager.stream_music, currentvol,// 備注2

                    audiomanager.flag_play_sound);

        } else if (key == keyevent.keycode_dpad_right) {

            currentvol -= 1;

            if (currentvol <= 0) {

                currentvol = 0;

            am.setstreamvolume(audiomanager.stream_music, currentvol,

        }

        soundpool.play(loadid, currentvol, currentvol, 1, 0, 1f);// 備注3

//      soundpool.play(soundpoolmap.get(1), currentvol, currentvol, 1, 0, 1f);//備注4

//      soundpool.pause(1);//暫停soundpool的聲音

        return super.onkeydown(key, event);

    public boolean ontouchevent(motionevent event) {

        return true;

    public void run() {

        // todo auto-generated method stub

        while (true) {

            draw();

            logic();

            try {

                thread.sleep(100);

            } catch (exception ex) {

    public void surfacechanged(surfaceholder holder, int format, int width,

            int height) {

    public void surfacedestroyed(surfaceholder holder) {

}

一、 mediaplayer 播放音頻的實作步驟:

1. 調用mediaplayer.create(context, r.raw.himi); 利用mediaplayer類調用create方法并且傳入通過id索引的資源音頻檔案,得到執行個體;

2. 得到的執行個體就可以調用 mediaplayer.star();

簡單吧、其實mediaplayer還有幾個構造方法,大家有興趣可以去嘗試和實作,這裡主要是簡單的向大家介紹基本的,畢竟簡單實用最好!

二、 soundplayer 播放音頻的實作步驟:

1.   new出一個執行個體 ;   new soundpool(4, audiomanager.stream_music, 100);第一個參數是允許有多少個聲音流同時播放,第2個參數是聲音類型,第三個參數是聲音的品質;

2.loadid = soundpool.load(context, r.raw.himi_ogg, 1);

3. 使用執行個體調用play方法傳入對應的音頻檔案id即可!

下面講下兩個播放形式的利弊:

 使用mediaplayer來播放音頻檔案存在一些不足:

例如:資源占用量較高、延遲時間較長、不支援多個音頻同時播放等。

這些缺點決定了mediaplayer在某些場合的使用情況不會很理想,例如在對時間精準度要求相對較高的遊戲開發中。

最開始我使用的也是普通的mediaplayer的方式,但這個方法不适合用于遊戲開發,因為遊戲裡面同時播放多個音效是常有的事,用過mediaplayer的朋友都該知道,它是不支援實時播放多個聲音的,會出現或多或少的延遲,而且這個延遲是無法讓人忍受的,尤其是在快速連續播放聲音(比如連續猛點按鈕)時,會非常明顯,長的時候會出現3~5秒的延遲,【使用mediaplayer.seekto() 這個方法來解決此問題】;

相對于使用soundpool存在的一些問題:

1. soundpool最大隻能申請1m的記憶體空間,這就意味着我們隻能使用一些很短的聲音片段,而不是用它來播放歌曲或者遊戲背景音樂(背景音樂可以考慮使用jetplayer來播放)。

2. soundpool提供了pause和stop方法,但這些方法建議最好不要輕易使用,因為有些時候它們可能會使你的程式莫名其妙的終止。還有些朋友反映它們不會立即中止播放聲音,而是把緩沖區裡的資料播放完才會停下來,也許會多點傳播放一秒鐘。

3. 音頻格式建議使用ogg格式。使用wav格式的音頻檔案存放遊戲音效,經過反複測試,在音效播放間隔較短的情況下會出現異常關閉的情況(有說法是soundpool目前隻對16bit的wav檔案有較好的支援)。後來将檔案轉成ogg格式,問題得到了解決。

4.在使用soundpool播放音頻的時候,如果在初始化中就調用播放函數進行播放音樂那麼根本沒有聲音,不是因為沒有執行,而是soundpool需要一準備時間!囧。當然這個準備時間也很短,不會影響使用,隻是程式一運作就播放會沒有聲音罷了,是以我把soundpool播放寫在了按鍵中處理了、備注4的地方

大概看完了利弊解釋,那麼來看我的代碼備注的地方:

備注1:

這裡我定義了一個 hashmap ,這個是哈希表,如果大家不是很了解這個類,那建議百度 google學習下,它與hashtable很常用的,它倆的主要差別是: hashmap   不同步、空鍵值、效率高;  hashtable   同步、非空鍵值、效率略低 ;而在j2me中不支援hashmap ,因為me中不支援空鍵值,是以在me中隻能使用hashtable、咳咳、言歸正傳,我這裡使用hashmap主要是為了存入多個音頻的id,播放的時候可以同時播放多個音頻。

上面也介紹了,soundpool可以支援多個音頻同時播放,而且soundpool在播放的時候調用的這個方法(備注3)soundpool.play(loadid, currentvol, currentvol, 1, 0, 1f); 第一個參數指的就是之前的loadid !是通過 soundpool.load(context, r.raw.himi_ogg, 1);方法取出來的,

那麼除此之外還要注意一點的就是定義hashmap的時候一定要定義成這種形式hashmap<integer, integer> hm = new hash<integer, integer>,聲明此哈希表就是一個key和volue值都是integer的哈希表! 為什麼要這麼做,因為如果你隻是簡單的定義成 hashmap hm =new hashmap(),那麼當你在播放的時候,也就是備注4方法這裡的第一個id參數使用hashmap.get()這個方法的時候總會出現錯誤的提示!

《soundpool最大隻能申請1m的記憶體空間,這就意味着我們隻能使用一些很短的聲音片段》為什麼隻能使用一些很短的聲音呢?

大家還是看備注4方法的第一個參數,這裡要求傳入的id類型是個int值,那麼這個int其實對應的是通過load()方法傳回的音頻id,而且這個id會因音頻檔案的大小而變大變小,那麼一旦我們的音頻檔案超過int最大值,那麼就會報記憶體錯誤的異常。是以為什麼用soundpool隻能播放一些簡短的音頻這就是其原因了。當然os 裡為什麼這麼定義 我也無從查證和說明。

備注4 :此方法中參數的解釋

第一個參數是我通過soundpool.load()方法傳回的音頻對應id,第二個第三個參數表示左右聲道大小,第四個參數是優先級,第五個參數是循環次數,最後一個是播放速率(1.0 =正常播放,範圍是0.5至2.0)

備注2:

這裡是通過媒體服務得到一個音頻管理器,進而來對音量大小進行調整。這裡要強調一下,調整音頻是用這個音頻管理器調用setstreamvolume()的方式去調整,而不是mediaplayer.setvolue(int leftvolume,int rightvolume);這個方法的兩個參數也是調正左右聲道而不是調節聲音大小。

好了,對此我們對遊戲開發中到底需要用什麼來做進行了分析,總結就是soundpool适合做特效聲,其實播放背景音樂我感覺還是用mediaplayer比較好,當然啦,用什麼都看大家喜好和選擇啦!下面附上項目下載下傳位址:(項目10+mb因為含有res音頻檔案)

有童鞋問  怎麼才知道一首歌曲播放完了,那麼這裡給說下:

playbackcompleted狀态:檔案正常播放完畢,而又沒有設定循環播放的話就進入該狀态,并會觸發oncompletionlistener的oncompletion()方法。此時可以調用start()方法重新從頭播放檔案,也可以stop()停止mediaplayer,或者也可以seekto()來重新定位播放位置。

注意:1、 别忘記綁定操作! mp.setoncompletionlistener(this);

2、如果你設定了循環播放  mp.setlooping(true); 的話,那麼永遠都不會監聽到播放完成的狀态!!!!這裡一定要注意!

繼續閱讀