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_MUSICvà volume keys thiết bị.