Phát hiệu ứng âm thanh trong Android với SoundPool
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Giới thiệu

Tài liệu được viết dựa trên:
  • Android Studio 1.5

2- SoundPool & AudioManager

Trước hết tôi đặt ra một tình huống, bạn đang tạo một trò chơi, nó phát ra các âm thanh chẳng hạn như tiếng súng, tiếng bom, đó là các hiệu ứng âm thanh trong trò chơi. Android cung cấp cho bạn class SoundPool, nó giống như một bể chứa các nguồn nhạc và sẵn sàng phát ra khi có yêu cầu.

SoundPool chứa một tập hợp các nguồn nhạc, nguồn âm có thể từ file nhạc trong ứng dụng hoặc trong file hệ thống,.. SoundPool hỗ trợ phát đồng loạt nhiều nguồn nhạc cùng một lúc. 

SourcePool sử dụng  MediaPlayer service để phát ra âm thanh.
 
Bạn có thể chỉ định để SoundPool phát ra âm thanh tại loại luồng (stream) cụ thể. Có một số loại luồng (Stream) sau:
Loại luồng âm thanh Mô tả
AudioManager.STREAM_ALARM Các dòng âm thanh cho báo động
AudioManager.STREAM DTMF Các dòng âm thanh cho DTMF
AudioManager.STREAM_MUSIC Các dòng âm thanh để phát lại âm nhạc
AudioManager.STREAM_NOTIFICATION Các dòng âm thanh cho thông báo
AudioManager.STREAM_RING Các dòng âm thanh cho điện thoại reo
AudioManager.STREAM_SYSTEM Các dòng âm thanh cho âm thanh hệ thống
AudioManager.STREAM_VOICE_CALL Các dòng âm thanh cho các cuộc gọi điện thoại
AudioManager cho phép bạn điều chỉnh âm lượng trên các luồng âm thanh khác nhau. Chẳng hạn bạn đang nghe nhạc bài hát trên thiết bị đồng thời chơi Game, bạn có thể thay đổi âm lượng của Game mà không ảnh hưởng tới âm lượng của bài hát.
Một vấn đề khác được đề cập là thiết bị âm thanh sẽ phát ra âm thanh.

Sử dụng STREAM_MUSIC âm thanh sẽ được phát ra thông qua một thiết bị âm thanh (loa điện thoại, tai nghe, bluetooth loa hay cái gì khác) kết nối với điện thoại.

Sử dụng STREAM_RING âm thanh sẽ được phát ra thông qua tất cả các thiết bị âm thanh kết nối với điện thoại. Hành vi này có thể khác nhau đối với mỗi thiết bị.

SoundPool API:

Phương thức Mô tả
int play(int soundID, float leftVolume, float rightVolume,
            int priority, int loop, float rate)
Phát một nguồn nhạc, mà trả về một số là ID của luồng âm đang phát (streamID).
void pause(int streamID) Tạm dừng luồng âm đang phát có ID là streamID.
void stop(int streamID) Ngừng luồng âm ứng với streamID.
void setVolume(int streamID, float leftVolume, float rightVolume) Sét đặt âm lượng cho luồng âm với ID là streamID
void setLoop(int streamID, int loop) Sét đặt số lần phát lại luồng âm với ID là streamID.

3- Ví dụ với SoundPool

Tạo mới một project SoundPoolDemo.
Tạo thư mục raw con của res. Copy 2 file nhạc vào thư mục raw, ở đây tôi copy 2 file mô phỏng âm thanh vật thể bị phá hủy (destroy.wav) và âm thanh của tiếng súng (gun.wav). Chú ý bạn có thể sử dụng file nhạc đuôi mp3 hoặc wav.
Giao diện ứng dụng Demo.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="org.o7planning.soundpooldemo.MainActivity">

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Destroy"
        android:id="@+id/button_destroy"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_marginLeft="40dp"
        android:layout_marginStart="40dp"
        android:layout_marginTop="54dp"
        android:onClick="playSoundDestroy" />

    <Button
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Gun"
        android:id="@+id/button_gun"
        android:layout_alignTop="@+id/button_destroy"
        android:layout_toRightOf="@+id/button_destroy"
        android:layout_toEndOf="@+id/button_destroy"
        android:layout_marginLeft="74dp"
        android:layout_marginStart="74dp"
        android:onClick="playSoundGun" />
    
</RelativeLayout>
MainActivity.java
package org.o7planning.soundpooldemo;

import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {


  private SoundPool soundPool;

  private AudioManager audioManager;

  // Số luồng âm thanh phát ra tối đa.
  private static final int MAX_STREAMS = 5;

  // Chọn loại luồng âm thanh để phát nhạc.
  private static final int streamType = AudioManager.STREAM_MUSIC;

  private boolean loaded;

  private int soundIdDestroy;
  private int soundIdGun;
  private float volume;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);


      // Đối tượng AudioManager sử dụng để điều chỉnh âm lượng.
      audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);

      // Chỉ số âm lượng hiện tại của loại luồng nhạc cụ thể (streamType).
      float currentVolumeIndex = (float) audioManager.getStreamVolume(streamType);


      // Chỉ số âm lượng tối đa của loại luồng nhạc cụ thể (streamType).
      float maxVolumeIndex  = (float) audioManager.getStreamMaxVolume(streamType);

      // Âm lượng  (0 --> 1)
      this.volume = currentVolumeIndex / maxVolumeIndex;

      // Cho phép thay đổi âm lượng các luồng kiểu 'streamType' bằng các nút
      // điều khiển của phần cứng.
      this.setVolumeControlStream(streamType);

      // Với phiên bản Android SDK >= 21
      if (Build.VERSION.SDK_INT >= 21 ) {

          AudioAttributes audioAttrib = new AudioAttributes.Builder()
                  .setUsage(AudioAttributes.USAGE_GAME)
                  .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                  .build();

          SoundPool.Builder builder= new SoundPool.Builder();
          builder.setAudioAttributes(audioAttrib).setMaxStreams(MAX_STREAMS);

          this.soundPool = builder.build();
      }
      // Với phiên bản Android SDK < 21
      else {
          // SoundPool(int maxStreams, int streamType, int srcQuality)
          this.soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
      }

      // Sự kiện SoundPool đã tải lên bộ nhớ thành công.
      this.soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
          @Override
          public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
              loaded = true;
          }
      });

      // Tải file nhạc tiếng vật thể bị phá hủy (destroy.war) vào SoundPool.
      this.soundIdDestroy = this.soundPool.load(this, R.raw.destroy,1);

      // Tải file nhạc tiếng súng (gun.wav) vào SoundPool.
      this.soundIdGun = this.soundPool.load(this, R.raw.gun,1);

  }

  // Khi người dùng nhấn vào button "Gun".
  public void playSoundGun(View view)  {
    if(loaded)  {
        float leftVolumn = volume;
        float rightVolumn = volume;
        // Phát âm thanh tiếng súng. Trả về ID của luồng mới phát ra.
        int streamId = this.soundPool.play(this.soundIdGun,leftVolumn, rightVolumn, 1, 0, 1f);
    }
  }

  // Khi người dùng nhấn vào button "Destroy".
  public void playSoundDestroy(View view)  {
      if(loaded)  {
          float leftVolumn = volume;
          float rightVolumn = volume;

          // Phát âm thanh tiếng vật thể bị phá hủy.
          // Trả về ID của luồng mới phát ra.
          int streamId = this.soundPool.play(this.soundIdDestroy,leftVolumn, rightVolumn, 1, 0, 1f);
      }
  }

}
Chạy ứng dụng: