Mari kita meriahkan! Dalam codelab ini, kita akan menggunakan AAudio API untuk mem-build aplikasi synthesizer berkontrol sentuh dengan latensi rendah untuk Android.
Aplikasi kita akan menghasilkan suara secepat mungkin setelah pengguna menyentuh layar. Keterlambatan antara input dan output disebut sebagai latensi. Memahami dan meminimalkan latensi adalah kunci dalam membuat pengalaman audio yang luar biasa. Faktanya, alasan utama kita menggunakan AAudio adalah karena kemampuannya untuk membuat streaming audio dengan latensi rendah.
Yang akan Anda pelajari
- Konsep dasar untuk membuat aplikasi audio dengan latensi rendah
- Cara membuat streaming audio
- Cara menangani perangkat audio yang terhubung dan terputus
- Cara menghasilkan data audio dan meneruskannya ke streaming audio
- Praktik terbaik berkomunikasi antara Java dan C++
- Cara mendengarkan peristiwa sentuh di UI
Yang Anda butuhkan
- Android Studio 2.3.3 atau yang lebih baru
- Android 8.0 (API level 26) SDK
- NDK dan Build Tools yang terinstal
- Simulator atau perangkat Android yang menjalankan Android 8.0 (API level 26) atau yang lebih baru untuk pengujian
- Sedikit pengetahuan tentang C++ akan membantu, tetapi tidak diwajibkan
Aplikasi menghasilkan suara sintesis saat pengguna mengetuk layar. Berikut arsitekturnya:
Aplikasi synthesizer kita memiliki empat komponen:
- UI - Ditulis dalam Java, class MainActivity bertanggung jawab menerima peristiwa sentuh dan meneruskannya ke jembatan JNI
- Jembatan JNI - File C++ ini menggunakan JNI untuk menyediakan mekanisme komunikasi antara UI dan objek C++. Jembatan ini meneruskan peristiwa dari UI ke Mesin Audio.
- Mesin audio - Class C++ ini membuat streaming audio pemutaran dan menyiapkan callback data yang digunakan untuk menyediakan data ke streaming
- Osilator - Class C++ ini menghasilkan data audio digital menggunakan formula matematika sederhana untuk menghitung bentuk gelombang sinusoidal
Mulailah dengan membuat project baru di Android Studio:
- File -> New -> New Project...
- Beri nama project Anda "WakeMaker"
Saat mengikuti wizard penyiapan project, ubah nilai default menjadi:
- Menyertakan dukungan C++
- SDK Minimum Ponsel dan Tablet: API 26: Android O
- C++ Standar: C++11
Catatan: Jika Anda perlu merujuk ke kode sumber yang telah selesai untuk aplikasi WaveMaker, kode sumber ada di sini.
Karena osilator adalah objek yang menghasilkan data audio, sebaiknya mulai dengan hal ini. Kita akan membuatnya tetap sederhana dan membuatnya menghasilkan gelombang sinus 440 Hz.
Dasar sintesis digital
Osilator adalah elemen penyusun dasar sintesis digital. Osilator perlu menghasilkan serangkaian angka, yang dikenal sebagai sampel. Setiap sampel mewakili nilai amplitudo yang dikonversi oleh hardware audio menjadi tegangan untuk mendorong headphone atau speaker.
Berikut adalah plot sampel yang mewakili gelombang sinus:
Sebelum memulai implementasi, berikut beberapa istilah penting untuk data audio digital:
- Format sampel - Jenis data yang digunakan untuk mewakili setiap sampel. Format sampel umum termasuk PCM16 dan floating point. Kita akan menggunakan floating point karena resolusi 24-bit-nya dan presisi yang disempurnakan pada volume rendah, di antara alasan lainnya.
- Frame - Saat membuat audio multi-saluran, sampel dikelompokkan dalam frame. Setiap sampel dalam frame sesuai dengan saluran audio yang berbeda. Misalnya, audio stereo memiliki 2 saluran (kiri dan kanan) sehingga frame audio stereo memiliki 2 sampel, satu untuk saluran kiri dan satu untuk saluran kanan.
- Kecepatan frame - Jumlah frame per detik. Ini sering disebut sebagai frekuensi sampel. Kecepatan frame dan frekuensi sampel biasanya memiliki arti yang sama dan digunakan secara bergantian. Nilai kecepatan frame yang umum adalah 44.100 dan 48.000 frame per detik. AAudio menggunakan istilah frekuensi sampel, jadi kita akan menggunakan konvensi tersebut dalam aplikasi.
Membuat file sumber dan file header
Klik kanan pada folder /app/cpp
dan buka New->C++ class.
Beri nama class dengan "Oscillator".
Tambahkan file sumber C++ ke build dengan menambahkan baris berikut ke CMakeLists.txt
. Ini dapat ditemukan di bagian External Build Files
pada jendela Project.
add_library(...existing source filenames...
src/main/cpp/Oscillator.cpp)
Pastikan project Anda berhasil dibuat.
Menambahkan kode
Tambahkan kode berikut ke file Oscillator.h
:
#include <atomic>
#include <stdint.h>
class Oscillator {
public:
void setWaveOn(bool isWaveOn);
void setSampleRate(int32_t sampleRate);
void render(float *audioData, int32_t numFrames);
private:
std::atomic<bool> isWaveOn_{false};
double phase_ = 0.0;
double phaseIncrement_ = 0.0;
};
Berikutnya, tambahkan kode berikut ke file Oscillator.cpp
:
#include "Oscillator.h"
#include <math.h>
#define TWO_PI (3.14159 * 2)
#define AMPLITUDE 0.3
#define FREQUENCY 440.0
void Oscillator::setSampleRate(int32_t sampleRate) {
phaseIncrement_ = (TWO_PI * FREQUENCY) / (double) sampleRate;
}
void Oscillator::setWaveOn(bool isWaveOn) {
isWaveOn_.store(isWaveOn);
}
void Oscillator::render(float *audioData, int32_t numFrames) {
if (!isWaveOn_.load()) phase_ = 0;
for (int i = 0; i < numFrames; i++) {
if (isWaveOn_.load()) {
// Calculates the next sample value for the sine wave.
audioData[i] = (float) (sin(phase_) * AMPLITUDE);
// Increments the phase, handling wrap around.
phase_ += phaseIncrement_;
if (phase_ > TWO_PI) phase_ -= TWO_PI;
} else {
// Outputs silence by setting sample value to zero.
audioData[i] = 0;
}
}
}
void setSampleRate(int32_t sampleRate)
memungkinkan kita menetapkan frekuensi sampel yang diinginkan untuk data audio (selengkapnya terkait mengapa kita membutuhkan ini akan dijelaskan nanti). Berdasarkan sampleRate
dan FREQUENCY
, ini akan menghitung nilai phaseIncrement_
yang digunakan di render
. Jika Anda ingin mengubah nada gelombang sinus, cukup perbarui FREQUENCY
dengan nilai baru.
void setWaveOn(bool isWaveOn)
adalah metode penyetel untuk kolom isWaveOn_
. Ini digunakan di render
untuk menentukan apakah akan menghasilkan gelombang sinus atau senyap.
void render(float *audioData, int32_t numFrames)
menempatkan nilai gelombang sinus floating point ke dalam array audioData
setiap kali dipanggil.
numFrames
adalah jumlah frame audio yang harus kita render. Agar tetap sederhana, osilator menghasilkan satu sampel per frame, yaitu mono.
phase_
menyimpan fase gelombang saat ini, dan ditingkatkan oleh phaseIncrement_
setelah setiap sampel dihasilkan.
Jika isWaveOn_
false
, kita hanya menghasilkan nol (senyap).
Osilator kita sudah selesai! Namun, bagaimana kita dapat mendengar gelombang sinus? Untuk itu, kita memerlukan mesin audio...
Mesin audio bertanggung jawab untuk:
- Menyiapkan streaming audio ke perangkat audio default
- Menghubungkan osilator ke streaming audio menggunakan callback data
- Mengaktifkan dan menonaktifkan output gelombang osilator
- Menutup streaming jika tidak diperlukan lagi
Jika belum, sebaiknya Anda membiasakan diri dengan AAudio API, karena ini membahas konsep penting di balik pembuatan streaming dan pengelolaan status streaming.
Membuat sumber dan header
Seperti langkah sebelumnya, buat class C++ bernama "AudioEngine".
Tambahkan file sumber C++ dan library AAudio ke build dengan menambahkan baris berikut ke CMakeLists.txt
add_library(...existing source files...
src/main/cpp/AudioEngine.cpp )
target_link_libraries(...existing libraries...
aaudio)
Menambahkan kode
Tambahkan kode berikut ke file AudioEngine.h
:
#include <aaudio/AAudio.h>
#include "Oscillator.h"
class AudioEngine {
public:
bool start();
void stop();
void restart();
void setToneOn(bool isToneOn);
private:
Oscillator oscillator_;
AAudioStream *stream_;
};
Berikutnya, tambahkan kode berikut ke file AudioEngine.cpp
:
#include <android/log.h>
#include "AudioEngine.h"
#include <thread>
#include <mutex>
// Double-buffering offers a good tradeoff between latency and protection against glitches.
constexpr int32_t kBufferSizeInBursts = 2;
aaudio_data_callback_result_t dataCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames) {
((Oscillator *) (userData))->render(static_cast<float *>(audioData), numFrames);
return AAUDIO_CALLBACK_RESULT_CONTINUE;
}
void errorCallback(AAudioStream *stream,
void *userData,
aaudio_result_t error){
if (error == AAUDIO_ERROR_DISCONNECTED){
std::function<void(void)> restartFunction = std::bind(&AudioEngine::restart,
static_cast<AudioEngine *>(userData));
new std::thread(restartFunction);
}
}
bool AudioEngine::start() {
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT);
AAudioStreamBuilder_setChannelCount(streamBuilder, 1);
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, &oscillator_);
AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this);
// Opens the stream.
aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream_);
if (result != AAUDIO_OK) {
__android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error opening stream %s",
AAudio_convertResultToText(result));
return false;
}
// Retrieves the sample rate of the stream for our oscillator.
int32_t sampleRate = AAudioStream_getSampleRate(stream_);
oscillator_.setSampleRate(sampleRate);
// Sets the buffer size.
AAudioStream_setBufferSizeInFrames(
stream_, AAudioStream_getFramesPerBurst(stream_) * kBufferSizeInBursts);
// Starts the stream.
result = AAudioStream_requestStart(stream_);
if (result != AAUDIO_OK) {
__android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error starting stream %s",
AAudio_convertResultToText(result));
return false;
}
AAudioStreamBuilder_delete(streamBuilder);
return true;
}
void AudioEngine::restart(){
static std::mutex restartingLock;
if (restartingLock.try_lock()){
stop();
start();
restartingLock.unlock();
}
}
void AudioEngine::stop() {
if (stream_ != nullptr) {
AAudioStream_requestStop(stream_);
AAudioStream_close(stream_);
}
}
void AudioEngine::setToneOn(bool isToneOn) {
oscillator_.setWaveOn(isToneOn);
}
Inilah fungsi kode tersebut...
Memulai mesin
Metode start()
menyiapkan streaming audio. Streaming audio dalam AAudio diwakili oleh objek AAudioStream
, dan untuk membuat objek, kita memerlukan AAudioStreamBuilder
:
AAudioStreamBuilder *streamBuilder;
AAudio_createStreamBuilder(&streamBuilder);
Sekarang kita dapat menggunakan streamBuilder
untuk menetapkan berbagai parameter pada streaming.
Format audio adalah bilangan floating point:
AAudioStreamBuilder_setFormat(streamBuilder, AAUDIO_FORMAT_PCM_FLOAT);
Kita akan menghasilkan output dalam mono (satu saluran):
AAudioStreamBuilder_setChannelCount(streamBuilder, 1);
Catatan: Kita tidak menetapkan beberapa parameter agar AAudio melakukannya secara otomatis, termasuk:
- ID perangkat audio - kita ingin menggunakan perangkat audio default, bukan menentukannya secara eksplisit, seperti speaker bawaan. Daftar perangkat audio yang memungkinkan dapat diperoleh menggunakan
AudioManager.getDevices()
. - Arah aliran data - aliran data output dibuat secara default. Jika ingin membuat rekaman, kita akan menentukan aliran data input.
- Frekuensi sampel (selengkapnya tentang hal ini akan dibahas nanti).
Mode performa
Kita menginginkan latensi serendah mungkin sehingga menetapkan mode performa latensi rendah:
AAudioStreamBuilder_setPerformanceMode(streamBuilder, AAUDIO_PERFORMANCE_MODE_LOW_LATENCY);
AAudio tidak menjamin bahwa aliran data yang dihasilkan memiliki mode performa latensi rendah ini. Alasan tidak memiliki mode ini mencakup:
- Anda menentukan frekuensi sampel non-native, format sampel, atau sampel per frame (baca selengkapnya di bawah), yang dapat menyebabkan resampling atau konversi format. Resampling adalah proses penghitungan ulang nilai sampel ke dalam frekuensi yang berbeda. Resampling dan konversi format dapat menambah beban komputasi dan/atau latensi.
- Tidak ada aliran data latensi rendah yang tersedia, mungkin karena semuanya digunakan oleh aplikasi Anda atau aplikasi lain
Anda dapat memeriksa mode performa aliran data menggunakan AAudioStream_getPerformanceMode
.
Membuka aliran data
Setelah semua parameter ditetapkan (callback data akan dibahas nanti), kita membuka aliran data dan memeriksa hasilnya:
aaudio_result_t result = AAudioStreamBuilder_openStream(streamBuilder, &stream_);
Jika hasilnya apa saja kecuali AAUDIO_OK
, kita akan mencatat output ke jendela Android Monitor
di Android Studio dan menampilkan false
.
if (result != AAUDIO_OK){
__android_log_print(ANDROID_LOG_ERROR, "AudioEngine", "Error opening stream", AAudio_convertResultToText(result));
return false;
}
Menetapkan frekuensi sampel osilator
Kita sengaja tidak menetapkan frekuensi sampel aliran data, karena ingin menggunakan frekuensi sampel native - yaitu frekuensi yang menghindari resampling dan penambahan latensi. Setelah aliran data dibuka, kita dapat mengkuerinya untuk mengetahui apa itu frekuensi sampel native:
int32_t sampleRate = AAudioStream_getSampleRate(stream_);
Lalu kita memberi perintah osilator untuk menghasilkan data audio menggunakan frekuensi sampel ini:
oscillator_.setSampleRate(sampleRate);
Menetapkan ukuran buffer
Ukuran buffer internal aliran data langsung memengaruhi latensi aliran data. Semakin besar ukuran buffer, semakin besar latensinya.
Kita akan menetapkan ukuran buffer menjadi dua kali ukuran burst. Sebuah burst adalah jumlah data terpisah yang ditulis selama setiap callback. Hal ini menawarkan kompromi yang baik antara perlindungan latensi dan underrun. Anda dapat membaca selengkapnya tentang penyempurnaan ukuran buffer di dokumentasi AAudio.
AAudioStream_setBufferSizeInFrames(
stream_, AAudioStream_getFramesPerBurst(stream_) * kBufferSizeInBursts);
Memulai aliran data
Setelah semuanya siap, kita dapat memulai aliran data yang menyebabkannya mulai memakai data audio dan memicu callback data.
result = AAudioStream_requestStart(stream_);
Callback data
Jadi, bagaimana cara mendapatkan data audio ke aliran data? Kita memiliki dua opsi:
- Menulis langsung ke aliran data menggunakan AAudioStream_write
- Gunakan fungsi callback data AAudioStream_dataCallback
Kita akan menggunakan pendekatan kedua karena lebih baik untuk aplikasi dengan latensi rendah; fungsi callback data dipanggil dari thread prioritas tinggi setiap kali aliran data memerlukan data audio.
Fungsi dataCallback
Kita mulai dengan menentukan fungsi callback dalam namespace global:
aaudio_data_callback_result_t dataCallback(
AAudioStream *stream,
void *userData,
void *audioData,
int32_t numFrames){
...
}
Bagian pintar di sini adalah parameter userData
adalah pointer ke objek Oscillator
. Jadi, kita dapat menggunakannya untuk merender data audio ke dalam array audioData
. Berikut caranya:
((Oscillator *)(userData))->render(static_cast<float*>(audioData), numFrames);
Perhatikan bahwa kita juga mentransmisikan array audioData
ke bilangan floating point karena itulah format yang diinginkan oleh metode render()
.
Terakhir, metode ini menampilkan nilai yang memberi tahu aliran data untuk terus menggunakan data audio.
return AAUDIO_CALLBACK_RESULT_CONTINUE;
Menyiapkan callback
Setelah kita memiliki fungsi dataCallback
, memberi tahu aliran data untuk menggunakannya dari metode start()
menjadi mudah (::
menunjukkan bahwa fungsi tersebut ada dalam namespace global):
AAudioStreamBuilder_setDataCallback(streamBuilder, ::dataCallback, &oscillator_);
Memulai dan menghentikan osilator
Mengaktifkan dan menonaktifkan output gelombang osilator sangat mudah, kita hanya memiliki satu metode yang meneruskan status nada ke osilator:
void AudioEngine::setToneOn(bool isToneOn) {
oscillator_.setWaveOn(isToneOn);
}
Perlu diperhatikan bahwa meskipun gelombang osilator nonaktif, metode render()
-nya tetap menghasilkan data audio yang berisi nol (lihat menghindari pemanasan latensi di atas).
Merapikan
Kita telah menyediakan metode start()
yang membuat aliran data, jadi kita juga harus menyediakan metode stop()
terkait yang menghapusnya. Metode ini dapat dipanggil kapan pun aliran data tidak lagi diperlukan (misalnya saat aplikasi ditutup). Metode ini menghentikan aliran data yang menghentikan callback, dan menutup aliran data yang menyebabkannya dihapus.
AAudioStream_requestStop(stream_);
AAudioStream_close(stream_);
Menangani aliran data terputus menggunakan callback error
Saat aliran data pemutaran dimulai, perangkat audio default akan digunakan. Ini mungkin berupa speaker bawaan, headphone, atau perangkat audio lainnya seperti antarmuka audio USB.
Apa yang terjadi jika perangkat audio default berubah? Misalnya, jika pengguna memulai pemutaran melalui speaker lalu menghubungkan headphone. Dalam hal ini, aliran data audio menjadi terputus dari speaker dan aplikasi tidak lagi dapat menulis sampel audio ke output. Itu hanya berhenti berputar.
Pengguna mungkin tidak mengharapkan hal ini. Audio harus terus diputar melalui headphone. (Namun, terdapat skenario lain saat menghentikan pemutaran mungkin lebih sesuai.)
Kita memerlukan callback untuk mendeteksi pemutusan aliran data, dan fungsi untuk memulai ulang aliran data ke perangkat audio baru, jika sesuai.
Menyiapkan callback error
Untuk menperhatikan peristiwa pemutusan aliran data, tentukan fungsi jenis AAudioStream_errorCallback
.
void errorCallback(AAudioStream *stream,
void *userData,
aaudio_result_t error){
if (error == AAUDIO_ERROR_DISCONNECTED){
std::function<void(void)> restartFunction = std::bind(&AudioEngine::restart,
static_cast<AudioEngine *>(userData));
new std::thread(restartFunction);
}
}
Fungsi ini akan dipanggil setiap kali terjadi error pada aliran data. Jika errornya adalah AAUDIO_ERROR_DISCONNECTED
, kita dapat memulai ulang aliran data.
Perhatikan bahwa callback tidak dapat memulai ulang aliran data audio secara langsung. Sebagai gantinya, untuk memulai ulang aliran data, kita membuat std::function
yang mengarah ke AudioEngine::restart()
, lalu memanggil fungsi dari std::thread
yang terpisah.
Terakhir, kita menetapkan errorCallback
dengan cara yang sama seperti yang dilakukan untuk dataCallback
di start()
.
AAudioStreamBuilder_setErrorCallback(streamBuilder, ::errorCallback, this);
Memulai ulang aliran data
Karena fungsi mulai ulang dapat dipanggil dari beberapa thread (misalnya, jika kita menerima beberapa peristiwa pemutusan secara berurutan), kita melindungi bagian penting dari kode dengan std::mutex
.
void AudioEngine::restart(){
static std::mutex restartingLock;
if (restartingLock.try_lock()){
stop();
start();
restartingLock.unlock();
}
}
Cukup untuk mesin audio, dan tidak ada lagi yang perlu dilakukan...
Kita memerlukan cara agar UI di Java dapat berbicara dengan class C++, di sinilah JNI berperan. Tanda tangan metodenya mungkin bukan yang terbaik untuk dilihat, tetapi untungnya hanya ada tiga tanda tangan!
Ganti nama file native-lib.cpp
menjadi jni-bridge.cpp
. Anda dapat membiarkan nama file apa adanya, namun perlu diingat bahwa file C++ ini adalah untuk metode JNI. Pastikan untuk mengupdate CMakeLists.txt
dengan file yang telah diganti namanya (tetapi biarkan nama library sebagai native-lib
).
Tambahkan kode berikut ke jni-bridge.cpp
:
#include <jni.h>
#include <android/input.h>
#include "AudioEngine.h"
static AudioEngine *audioEngine = new AudioEngine();
extern "C" {
JNIEXPORT void JNICALL
Java_com_example_wavemaker_MainActivity_touchEvent(JNIEnv *env, jobject obj, jint action) {
switch (action) {
case AMOTION_EVENT_ACTION_DOWN:
audioEngine->setToneOn(true);
break;
case AMOTION_EVENT_ACTION_UP:
audioEngine->setToneOn(false);
break;
default:
break;
}
}
JNIEXPORT void JNICALL
Java_com_example_wavemaker_MainActivity_startEngine(JNIEnv *env, jobject /* this */) {
audioEngine->start();
}
JNIEXPORT void JNICALL
Java_com_example_wavemaker_MainActivity_stopEngine(JNIEnv *env, jobject /* this */) {
audioEngine->stop();
}
}
Jembatan JNI kita cukup sederhana:
- Kita membuat instance statis
AudioEngine
startEngine()
danstopEngine()
memulai dan menghentikan mesin audiotouchEvent()
menerjemahkan peristiwa sentuh menjadi panggilan metode untuk mengaktifkan dan menonaktifkan nada
Terakhir, mari buat UI dan menghubungkannya ke back end...
Tata Letak
Tata letak kita sangat sederhana (kita akan menyempurnakannya dalam codelab berikutnya). Yang terpenting hanyalah FrameLayout dengan TextView di tengah:
Update res/layout/activity_main.xml
ke berikut ini:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/touchArea"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.wavemaker.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/tap_anywhere"
android:textAppearance="@android:style/TextAppearance.Material.Display1" />
</FrameLayout>
Tambahkan resource string untuk @string/tap_anywhere
ke res/values/strings.xml
:
<resources>
<string name="app_name">WaveMaker</string>
<string name="tap_anywhere">Tap anywhere</string>
</resources>
Aktivitas Utama
Sekarang update MainActivity.java
dengan kode berikut:
package com.example.wavemaker;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MotionEvent;
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}
private native void touchEvent(int action);
private native void startEngine();
private native void stopEngine();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startEngine();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
touchEvent(event.getAction());
return super.onTouchEvent(event);
}
@Override
public void onDestroy() {
stopEngine();
super.onDestroy();
}
}
Inilah fungsi kode tersebut:
- Metode
private native void
sudah ditentukan dalamjni-bridge.cpp
, kita perlu mendeklarasikannya di sini agar dapat menggunakannya - Peristiwa siklus proses aktivitas
onCreate()
danonDestroy()
memanggil jembatan JNI untuk memulai dan menghentikan mesin audio - Kita mengganti
onTouchEvent()
untuk menerima semua peristiwa sentuh untukActivity
dan meneruskannya langsung ke jembatan JNI untuk mengaktifkan dan menonaktifkan nada
Aktifkan perangkat uji atau emulator Anda dan jalankan aplikasi WaveMaker. Saat mengetuk layar, Anda akan mendengar gelombang sinus yang jelas dihasilkan!
Oke, jadi aplikasi kita tidak akan memenangkan penghargaan untuk kreativitas musik, tetapi aplikasi ini menunjukkan teknik dasar yang diperlukan untuk menghasilkan audio sintesis dengan latensi rendah di Android.
Jangan khawatir, dalam codelab berikutnya, kita akan membuat aplikasi ini menjadi lebih menarik! Terima kasih telah menyelesaikan codelab ini. Jika ada pertanyaan, hubungi grup android-ndk.
Bacaan lebih lanjut
Sampel audio berperforma tinggi
Panduan audio berperforma tinggi pada dokumentasi Android NDK