Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Phát âm thanh tùy ý (Arbitrary Tone) trên Android

Giới thiệu

Bài viết này hướng dẫn cách tạo và phát một âm thanh tùy ý (sine wave tone) trên Android bằng cách sử dụng AudioTrack ở chế độ MODE_STATIC. Kỹ thuật này dùng cho: tạo tiếng beep, dial tone, game sound effects, test audio, synthesizer đơn giản.

Các bước thực hiện

Bước 1: Tạo dự án mới

Mở Android Studio, chọn File → New Project và điền thông tin cần thiết.

Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <ImageView
        android:layout_width="200dp"
        android:layout_height="300dp"
        android:src="@drawable/ic_music_note_black_24dp" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tăng âm lượng emulator, nghe âm thanh tùy ý!"
        android:textSize="16sp"
        android:textStyle="bold|italic"
        android:layout_marginTop="16dp"
        android:gravity="center" />

</LinearLayout>

Bước 3: Viết code tạo và phát âm thanh (src/MainActivity.java)

import androidx.appcompat.app.AppCompatActivity;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;

public class MainActivity extends AppCompatActivity {

    // Thông số âm thanh
    private final int duration = 10;        // Thời lượng (giây)
    private final int sampleRate = 8000;    // Sample rate (Hz) - 8kHz đủ cho voice/tone
    private final int numSamples = duration * sampleRate;
    private final double[] sample = new double[numSamples];
    private final byte[] generatedSnd = new byte[2 * numSamples]; // 16-bit = 2 bytes/sample
    
    private Handler handler = new Handler();

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

    @Override
    protected void onResume() {
        super.onResume();
        
        // Chạy generation trên background thread để không block UI
        new Thread(() -> {
            genTone();
            handler.post(this::playSound);
        }).start();
    }

    /** Tạo sóng sine wave */
    private void genTone() {
        // Công thức: sin(2π * f * t) 
        // t = i / sampleRate, f = tần số (Hz)
        double freqOfTone = 440; // A4 = 440Hz (La chuẩn)
        
        for (int i = 0; i < numSamples; ++i) {
            sample[i] = Math.sin(2 * Math.PI * i * freqOfTone / sampleRate);
        }
        
        // Chuyển double [-1,1] → short 16-bit [-32768, 32767] → byte[] little-endian
        int idx = 0;
        for (double dVal : sample) {
            short val = (short) (dVal * 32767); // Scale to 16-bit range
            generatedSnd[idx++] = (byte) (val & 0x00FF);        // Low byte
            generatedSnd[idx++] = (byte) ((val & 0xFF00) >>> 8); // High byte
        }
    }

    /** Phát âm thanh qua AudioTrack */
    private void playSound() {
        AudioTrack audioTrack = new AudioTrack.Builder()
            .setAudioAttributes(new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                .build())
            .setAudioFormat(new AudioFormat.Builder()
                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                .setSampleRate(sampleRate)
                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
                .build())
            .setBufferSizeInBytes(generatedSnd.length)
            .setTransferMode(AudioTrack.MODE_STATIC)
            .build();
        
        audioTrack.write(generatedSnd, 0, generatedSnd.length);
        audioTrack.play();
        
        // Tự dừng sau khi phát xong (duration giây)
        handler.postDelayed(audioTrack::release, duration * 1000L);
    }
}

Bước 4: Cấu hình Manifest (androidManifest.xml)

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="app.com.sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                
                
            </intent-filter>
        </activity>

    </application>
</manifest>

Chạy ứng dụng

Kết nối thiết bị thật (emulator thường không có loa hoặc âm lượng nhỏ), nhấn Run. Tăng âm lượng thiết bị lên tối đa. Sẽ nghe tiếng beep 440Hz (nốt La) kéo dài 10 giây.

Giải thích kỹ thuật

Tham số Giá trị Ý nghĩa
Sample Rate 8000 Hz Số mẫu/giây. 8kHz đủ cho voice/tone đơn giản. CD quality = 44100Hz
Duration 10 giây Thời lượng phát
Frequency 440 Hz Tần số âm thanh (nốt La chuẩn - A4)
Encoding PCM 16-bit Độ phân giải mẫu (2 bytes/mẫu)
Channel Mono 1 kênh (stereo = 2 kênh)
Mode MODE_STATIC Dữ liệu nhỏ, load hết vào buffer rồi phát. MODE_STREAM cho dữ liệu lớn/livestream

Mở rộng: Tạo nhiều nốt nhạc (Melody)

// Tần số các nốt nhạc (octave 4)
double[] notes = {
    261.63, // C4 (Do)
    293.66, // D4 (Re)
    329.63, // E4 (Mi)
    349.23, // F4 (Fa)
    392.00, // G4 (Sol)
    440.00, // A4 (La)
    493.88, // B4 (Si)
    523.25  // C5 (Do cao)
};

// Tạo melody: nối nhiều tone liên tiếp
private void genMelody() {
    int noteDuration = sampleRate / 4; // Mỗi nốt 0.25s
    int totalSamples = notes.length * noteDuration;
    double[] melody = new double[totalSamples];
    
    for (int n = 0; n < notes.length; n++) {
        double freq = notes[n];
        for (int i = 0; i < noteDuration; i++) {
            int idx = n * noteDuration + i;
            melody[idx] = Math.sin(2 * Math.PI * i * freq / sampleRate);
        }
    }
    // Convert to byte[] như genTone()...
}

Lưu ý quan trọng

  • AudioTrack.Builder (API 23+) khuyên dùng thay constructor cũ.
  • Luôn gọi release() sau khi phát xong để giải phóng native resources.
  • Chạy genTone() trên background thread (Thread, Executor, Coroutine) - tính toán nặng có thể gây ANR.
  • Để phát liên tục (loop): audioTrack.setLoopPoints(0, generatedSnd.length/2, -1) (API 23+).
  • Âm lượng điều khiển bởi AudioManager.STREAM_MUSIC và volume keys thiết bị.