Phát hiệu ứng âm thanh trong Android với SoundPool

Xem thêm các chuyên mục:

1- Giới thiệu

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

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 lớp 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 stream (luồng) cụ thể. Có một số kiểu 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 (stream) â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.
  • Name: SoundPoolDemo
  • Package name: org.o7planning.soundpooldemo
Tạo thư mục raw là con của thư mục 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"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button_destroy"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="32dp"
        android:layout_marginLeft="32dp"
        android:layout_marginTop="50dp"
        android:text="Destroy"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_gun"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="50dp"
        android:text="Gun"
        app:layout_constraintStart_toEndOf="@+id/button_destroy"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.java
package org.o7planning.soundpooldemo;

import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {


    private SoundPool soundPool;

    private AudioManager audioManager;
    private Button buttonDestroy;
    private Button buttonGun;

    // Maximumn sound stream.
    private static final int MAX_STREAMS = 5;

    // Stream type.
    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);

        this.buttonDestroy = (Button) this.findViewById(R.id.button_destroy);
        this.buttonGun = (Button) this.findViewById(R.id.button_gun);

        this.buttonDestroy.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                playSoundDestroy();
            }
        });

        this.buttonGun.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                playSoundGun();
            }
        });

        // AudioManager audio settings for adjusting the volume
        audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);

        // Current volumn Index of particular stream type.
        float currentVolumeIndex = (float) audioManager.getStreamVolume(streamType);

        // Get the maximum volume index for a particular stream type.
        float maxVolumeIndex  = (float) audioManager.getStreamMaxVolume(streamType);

        // Volumn (0 --> 1)
        this.volume = currentVolumeIndex / maxVolumeIndex;

        // Suggests an audio stream whose volume should be changed by
        // the hardware volume controls.
        this.setVolumeControlStream(streamType);

        // For 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();
        }
        // for Android SDK < 21
        else {
            // SoundPool(int maxStreams, int streamType, int srcQuality)
            this.soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_MUSIC, 0);
        }

        // When Sound Pool load complete.
        this.soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                loaded = true;
            }
        });

        // Load sound file (destroy.wav) into SoundPool.
        this.soundIdDestroy = this.soundPool.load(this, R.raw.destroy,1);

        // Load sound file (gun.wav) into SoundPool.
        this.soundIdGun = this.soundPool.load(this, R.raw.gun,1);

    }



    // When users click on the button "Gun"
    public void playSoundGun( )  {
        if(loaded)  {
            float leftVolumn = volume;
            float rightVolumn = volume;
            // Play sound of gunfire. Returns the ID of the new stream.
            int streamId = this.soundPool.play(this.soundIdGun,leftVolumn, rightVolumn, 1, 0, 1f);
        }
    }

    // When users click on the button "Destroy"
    public void playSoundDestroy( )  {
        if(loaded)  {
            float leftVolumn = volume;
            float rightVolumn = volume;

            // Play sound objects destroyed. Returns the ID of the new stream.
            int streamId = this.soundPool.play(this.soundIdDestroy,leftVolumn, rightVolumn, 1, 0, 1f);
        }
    }

}

 
Chạy ứng dụng:

Xem thêm các chuyên mục: